Basic design for property builders
This commit is contained in:
parent
7ab80516e0
commit
69074fef9e
@ -10,7 +10,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain{
|
commonMain{
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-meta:$dataforgeVersion")
|
api("hep.dataforge:dataforge-context:$dataforgeVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,40 @@
|
|||||||
package hep.dataforge.control.api
|
package hep.dataforge.control.api
|
||||||
|
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
interface Device {
|
interface Device {
|
||||||
val descriptors: Collection<PropertyDescriptor>
|
/**
|
||||||
var controller: DeviceController?
|
* List of supported property descriptors
|
||||||
|
*/
|
||||||
|
val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of supported requests descriptors
|
||||||
|
*/
|
||||||
|
val requestDescriptors: Collection<RequestDescriptor>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scope encompassing all operations on a device. When canceled, cancels all running processes
|
||||||
|
*/
|
||||||
|
val scope: CoroutineScope
|
||||||
|
|
||||||
|
var controller: PropertyChangeListener?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the property or throw error if property in not defined. Suspend if property value is not available
|
||||||
|
*/
|
||||||
suspend fun getProperty(propertyName: String): MetaItem<*>
|
suspend fun getProperty(propertyName: String): MetaItem<*>
|
||||||
suspend fun setProperty(propertyName: String, propertyValue: MetaItem<*>)
|
|
||||||
|
|
||||||
suspend fun request(command: String, argument: MetaItem<*>?): MetaItem<*>?
|
/**
|
||||||
|
* Set property [value] for a property with name [propertyName].
|
||||||
|
* In rare cases could suspend if the [Device] supports command queue and it is full at the moment.
|
||||||
|
*/
|
||||||
|
suspend fun setProperty(propertyName: String, value: MetaItem<*>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request and suspend caller while request is being processed.
|
||||||
|
* Could return null if request does not return meaningful answer.
|
||||||
|
*/
|
||||||
|
suspend fun request(name: String, argument: MetaItem<*>? = null): MetaItem<*>?
|
||||||
}
|
}
|
@ -1,4 +0,0 @@
|
|||||||
package hep.dataforge.control.api
|
|
||||||
|
|
||||||
interface DeviceController {
|
|
||||||
}
|
|
@ -0,0 +1,23 @@
|
|||||||
|
package hep.dataforge.control.api
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hub that could locate multiple devices and redirect actions to them
|
||||||
|
*/
|
||||||
|
interface DeviceHub {
|
||||||
|
fun getDevice(deviceName: String): Device?
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun DeviceHub.getProperty(deviceName: String, propertyName: String): MetaItem<*> =
|
||||||
|
(getDevice(deviceName) ?: error("Device with name $deviceName not found in the hub"))
|
||||||
|
.getProperty(propertyName)
|
||||||
|
|
||||||
|
suspend fun DeviceHub.setProperty(deviceName: String, propertyName: String, value: MetaItem<*>) {
|
||||||
|
(getDevice(deviceName) ?: error("Device with name $deviceName not found in the hub"))
|
||||||
|
.setProperty(propertyName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun DeviceHub.request(deviceName: String, command: String, argument: MetaItem<*>?): MetaItem<*>? =
|
||||||
|
(getDevice(deviceName) ?: error("Device with name $deviceName not found in the hub"))
|
||||||
|
.request(command, argument)
|
@ -0,0 +1,7 @@
|
|||||||
|
package hep.dataforge.control.api
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
|
||||||
|
interface PropertyChangeListener {
|
||||||
|
fun propertyChanged(propertyName: String, value: MetaItem<*>)
|
||||||
|
}
|
@ -2,14 +2,13 @@ package hep.dataforge.control.api
|
|||||||
|
|
||||||
import hep.dataforge.meta.scheme.Scheme
|
import hep.dataforge.meta.scheme.Scheme
|
||||||
import hep.dataforge.meta.scheme.SchemeSpec
|
import hep.dataforge.meta.scheme.SchemeSpec
|
||||||
import hep.dataforge.meta.scheme.string
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A descriptor for property
|
* A descriptor for property
|
||||||
*/
|
*/
|
||||||
class PropertyDescriptor: Scheme() {
|
class PropertyDescriptor : Scheme() {
|
||||||
var name by string{ error("Property name is mandatory")}
|
//var name by string { error("Property name is mandatory") }
|
||||||
//var descriptor by spec(ItemDescriptor)
|
//var descriptor by spec(ItemDescriptor)
|
||||||
|
|
||||||
companion object: SchemeSpec<PropertyDescriptor>(::PropertyDescriptor)
|
companion object : SchemeSpec<PropertyDescriptor>(::PropertyDescriptor)
|
||||||
}
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package hep.dataforge.control.api
|
||||||
|
|
||||||
|
import hep.dataforge.meta.scheme.Scheme
|
||||||
|
import hep.dataforge.meta.scheme.SchemeSpec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A descriptor for property
|
||||||
|
*/
|
||||||
|
class RequestDescriptor : Scheme() {
|
||||||
|
//var name by string { error("Property name is mandatory") }
|
||||||
|
//var descriptor by spec(ItemDescriptor)
|
||||||
|
|
||||||
|
companion object : SchemeSpec<RequestDescriptor>(::RequestDescriptor)
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
|
import hep.dataforge.control.api.Device
|
||||||
|
import hep.dataforge.control.api.PropertyChangeListener
|
||||||
|
import hep.dataforge.control.api.PropertyDescriptor
|
||||||
|
import hep.dataforge.control.api.RequestDescriptor
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import kotlin.jvm.JvmStatic
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
abstract class DeviceBase : Device, PropertyChangeListener {
|
||||||
|
private val properties = HashMap<String, ReadOnlyProperty>()
|
||||||
|
private val requests = HashMap<String, Request>()
|
||||||
|
|
||||||
|
override var controller: PropertyChangeListener? = null
|
||||||
|
|
||||||
|
override fun propertyChanged(propertyName: String, value: MetaItem<*>) {
|
||||||
|
controller?.propertyChanged(propertyName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
|
get() = properties.values.map { it.descriptor }
|
||||||
|
|
||||||
|
override val requestDescriptors: Collection<RequestDescriptor>
|
||||||
|
get() = requests.values.map { it.descriptor }
|
||||||
|
|
||||||
|
fun <P : ReadOnlyProperty> initProperty(prop: P): P {
|
||||||
|
properties[prop.name] = prop
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initRequest(request: Request): Request {
|
||||||
|
requests[request.name] = request
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun initRequest(
|
||||||
|
name: String,
|
||||||
|
descriptor: RequestDescriptor = RequestDescriptor.empty(),
|
||||||
|
block: suspend (MetaItem<*>?) -> MetaItem<*>?
|
||||||
|
): Request {
|
||||||
|
val request = SimpleRequest(name, descriptor, block)
|
||||||
|
return initRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getProperty(propertyName: String): MetaItem<*> =
|
||||||
|
(properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
||||||
|
|
||||||
|
override suspend fun setProperty(propertyName: String, value: MetaItem<*>) {
|
||||||
|
(properties[propertyName] as? Property ?: error("Property with name $propertyName not defined")).write(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun request(name: String, argument: MetaItem<*>?): MetaItem<*>? =
|
||||||
|
(requests[name] ?: error("Request with name $name not defined")).invoke(argument)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
protected fun <D : DeviceBase, P : ReadOnlyProperty> D.initProperty(
|
||||||
|
name: String,
|
||||||
|
builder: PropertyBuilder<D>.() -> P
|
||||||
|
): P {
|
||||||
|
val property = PropertyBuilder(name, this).run(builder)
|
||||||
|
initProperty(property)
|
||||||
|
return property
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PropertyDelegateProvider<D : DeviceBase, T : Any, P : GenericReadOnlyProperty<D, T>>(
|
||||||
|
val owner: D,
|
||||||
|
val builder: PropertyBuilder<D>.() -> P
|
||||||
|
) {
|
||||||
|
operator fun provideDelegate(thisRef: D, property: KProperty<*>): P {
|
||||||
|
val name = property.name
|
||||||
|
return owner.initProperty(PropertyBuilder(name, owner).run(builder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <D : DeviceBase, T : Any> D.property(
|
||||||
|
builder: PropertyBuilder<D>.() -> GenericReadOnlyProperty<D, T>
|
||||||
|
): PropertyDelegateProvider<D, T, GenericReadOnlyProperty<D, T>> {
|
||||||
|
return PropertyDelegateProvider(this, builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <D : DeviceBase, T : Any> D.mutableProperty(
|
||||||
|
builder: PropertyBuilder<D>.() -> GenericProperty<D, T>
|
||||||
|
): PropertyDelegateProvider<D, T, GenericProperty<D, T>> {
|
||||||
|
return PropertyDelegateProvider(this, builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
|||||||
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
|
import hep.dataforge.control.api.PropertyDescriptor
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.meta.transformations.MetaCaster
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
open class GenericReadOnlyProperty<D: DeviceBase, T : Any>(
|
||||||
|
override val name: String,
|
||||||
|
override val descriptor: PropertyDescriptor,
|
||||||
|
override val owner: D,
|
||||||
|
internal val converter: MetaCaster<T>,
|
||||||
|
internal val getter: suspend D.() -> T
|
||||||
|
) : ReadOnlyProperty, kotlin.properties.ReadOnlyProperty<Any?, T?> {
|
||||||
|
|
||||||
|
protected val mutex = Mutex()
|
||||||
|
protected var value: T? = null
|
||||||
|
|
||||||
|
suspend fun updateValue(value: T) {
|
||||||
|
mutex.withLock { this.value = value }
|
||||||
|
owner.propertyChanged(name, converter.objectToMetaItem(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun readValue(): T =
|
||||||
|
value ?: withContext(owner.scope.coroutineContext) {
|
||||||
|
//all device operations should be run on device context
|
||||||
|
owner.getter().also { updateValue(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun peekValue(): T? = value
|
||||||
|
|
||||||
|
suspend fun update(item: MetaItem<*>) {
|
||||||
|
updateValue(converter.itemToObject(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun read(): MetaItem<*> = converter.objectToMetaItem(readValue())
|
||||||
|
|
||||||
|
override fun peek(): MetaItem<*>? = value?.let { converter.objectToMetaItem(it) }
|
||||||
|
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T? = peekValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenericProperty<D: DeviceBase, T : Any>(
|
||||||
|
name: String,
|
||||||
|
descriptor: PropertyDescriptor,
|
||||||
|
owner: D,
|
||||||
|
converter: MetaCaster<T>,
|
||||||
|
getter: suspend D.() -> T,
|
||||||
|
private val setter: suspend D.(oldValue: T?, newValue: T) -> Unit
|
||||||
|
) : Property, ReadWriteProperty<Any?, T?>, GenericReadOnlyProperty<D, T>(name, descriptor, owner, converter, getter) {
|
||||||
|
|
||||||
|
suspend fun writeValue(newValue: T) {
|
||||||
|
val oldValue = value
|
||||||
|
withContext(owner.scope.coroutineContext) {
|
||||||
|
//all device operations should be run on device context
|
||||||
|
invalidate()
|
||||||
|
owner.setter(oldValue, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
mutex.withLock { value = null }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun write(item: MetaItem<*>) {
|
||||||
|
writeValue(converter.itemToObject(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
||||||
|
owner.scope.launch {
|
||||||
|
if (value == null) {
|
||||||
|
invalidate()
|
||||||
|
} else {
|
||||||
|
writeValue(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
|
import hep.dataforge.control.api.Device
|
||||||
|
import hep.dataforge.control.api.PropertyDescriptor
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-only device property
|
||||||
|
*/
|
||||||
|
interface ReadOnlyProperty {
|
||||||
|
/**
|
||||||
|
* Property name, should be unique in device
|
||||||
|
*/
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
val owner: Device
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property descriptor
|
||||||
|
*/
|
||||||
|
val descriptor: PropertyDescriptor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached value and return null if value is invalid
|
||||||
|
*/
|
||||||
|
fun peek(): MetaItem<*>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read value either from cache if cache is valid or directly from physical device
|
||||||
|
*/
|
||||||
|
suspend fun read(): MetaItem<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single writeable property handler
|
||||||
|
*/
|
||||||
|
interface Property : ReadOnlyProperty {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update property logical value and notify listener without writing it to device
|
||||||
|
*/
|
||||||
|
suspend fun update(item: MetaItem<*>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase logical value and force re-read from device on next [read]
|
||||||
|
*/
|
||||||
|
suspend fun invalidate()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write value to physical device. Invalidates logical value, but does not update it automatically
|
||||||
|
*/
|
||||||
|
suspend fun write(item: MetaItem<*>)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
|
import hep.dataforge.control.api.PropertyDescriptor
|
||||||
|
import hep.dataforge.meta.transformations.MetaCaster
|
||||||
|
import hep.dataforge.values.Value
|
||||||
|
|
||||||
|
class PropertyBuilder<D : DeviceBase>(val name: String, val owner: D) {
|
||||||
|
var descriptor: PropertyDescriptor = PropertyDescriptor.empty()
|
||||||
|
|
||||||
|
inline fun descriptor(block: PropertyDescriptor.() -> Unit) {
|
||||||
|
descriptor.apply(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Any> get(converter: MetaCaster<T>, getter: (suspend D.() -> T)): GenericReadOnlyProperty<D, T> =
|
||||||
|
GenericReadOnlyProperty(name, descriptor, owner, converter, getter)
|
||||||
|
|
||||||
|
fun getDouble(getter: (suspend D.() -> Double)): GenericReadOnlyProperty<D, Double> =
|
||||||
|
GenericReadOnlyProperty(name, descriptor, owner, MetaCaster.double) { getter() }
|
||||||
|
|
||||||
|
fun getString(getter: suspend D.() -> String): GenericReadOnlyProperty<D, String> =
|
||||||
|
GenericReadOnlyProperty(name, descriptor, owner, MetaCaster.string) { getter() }
|
||||||
|
|
||||||
|
fun getBoolean(getter: suspend D.() -> Boolean): GenericReadOnlyProperty<D, Boolean> =
|
||||||
|
GenericReadOnlyProperty(name, descriptor, owner, MetaCaster.boolean) { getter() }
|
||||||
|
|
||||||
|
fun getValue(getter: suspend D.() -> Any?): GenericReadOnlyProperty<D, Value> =
|
||||||
|
GenericReadOnlyProperty(name, descriptor, owner, MetaCaster.value) { Value.of(getter()) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this read-only property to read-write property
|
||||||
|
*/
|
||||||
|
infix fun <T: Any> GenericReadOnlyProperty<D, T>.set(setter: (suspend D.(oldValue: T?, newValue: T) -> Unit)): GenericProperty<D,T> {
|
||||||
|
return GenericProperty(name, descriptor, owner, converter, getter, setter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create read-write property with synchronized setter which updates value after set
|
||||||
|
*/
|
||||||
|
fun <T: Any> GenericReadOnlyProperty<D, T>.set(synchronousSetter: (suspend D.(oldValue: T?, newValue: T) -> T)): GenericProperty<D,T> {
|
||||||
|
val setter: suspend D.(oldValue: T?, newValue: T) -> Unit = { oldValue, newValue ->
|
||||||
|
val result = synchronousSetter(oldValue, newValue)
|
||||||
|
updateValue(result)
|
||||||
|
}
|
||||||
|
return GenericProperty(name, descriptor, owner, converter, getter, setter)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
|
import hep.dataforge.control.api.RequestDescriptor
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
|
||||||
|
interface Request {
|
||||||
|
val name: String
|
||||||
|
val descriptor: RequestDescriptor
|
||||||
|
suspend operator fun invoke(arg: MetaItem<*>?): MetaItem<*>?
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleRequest(
|
||||||
|
override val name: String,
|
||||||
|
override val descriptor: RequestDescriptor,
|
||||||
|
val block: suspend (MetaItem<*>?)->MetaItem<*>?
|
||||||
|
): Request{
|
||||||
|
override suspend fun invoke(arg: MetaItem<*>?): MetaItem<*>? = block(arg)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package hep.dataforge.control.demo
|
||||||
|
|
||||||
|
import hep.dataforge.control.base.DeviceBase
|
||||||
|
import hep.dataforge.control.base.mutableProperty
|
||||||
|
import hep.dataforge.control.base.property
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
class VirtualDevice(val meta: Meta, override val scope: CoroutineScope) : DeviceBase() {
|
||||||
|
|
||||||
|
var scale by mutableProperty {
|
||||||
|
getDouble {
|
||||||
|
200.0
|
||||||
|
} set { _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sin by property {
|
||||||
|
getDouble {
|
||||||
|
val time = Instant.now()
|
||||||
|
sin(time.toEpochMilli().toDouble() / (scale ?: 1000.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val cos by property {
|
||||||
|
getDouble {
|
||||||
|
val time = Instant.now()
|
||||||
|
cos(time.toEpochMilli().toDouble() / (scale ?: 1000.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user