Basic design for property builders
This commit is contained in:
parent
7ab80516e0
commit
69074fef9e
@ -10,7 +10,7 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain{
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-meta:$dataforgeVersion")
|
||||
api("hep.dataforge:dataforge-context:$dataforgeVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,40 @@
|
||||
package hep.dataforge.control.api
|
||||
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
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 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.SchemeSpec
|
||||
import hep.dataforge.meta.scheme.string
|
||||
|
||||
/**
|
||||
* A descriptor for property
|
||||
*/
|
||||
class PropertyDescriptor: Scheme() {
|
||||
var name by string{ error("Property name is mandatory")}
|
||||
class PropertyDescriptor : Scheme() {
|
||||
//var name by string { error("Property name is mandatory") }
|
||||
//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