From a87b46cd2b7f11471c10a4b1ce093393926f96d9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 23 Jun 2021 22:23:44 +0300 Subject: [PATCH] Add alternative device syntax --- .../npm/controls/properties/DeviceBySpec.kt | 83 +++++++++++++++ .../controls/properties/DevicePropertySpec.kt | 77 ++++++++++++++ .../npm/controls/properties/DeviceSpec.kt | 100 ++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt create mode 100644 controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DevicePropertySpec.kt create mode 100644 controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt new file mode 100644 index 0000000..4a77084 --- /dev/null +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceBySpec.kt @@ -0,0 +1,83 @@ +package ru.mipt.npm.controls.properties + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import ru.mipt.npm.controls.api.ActionDescriptor +import ru.mipt.npm.controls.api.Device +import ru.mipt.npm.controls.api.PropertyDescriptor +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.Global +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MetaItem +import space.kscience.dataforge.meta.TypedMetaItem +import kotlin.jvm.Synchronized + +/** + * @param D recursive self-type for properties and actions + */ +public open class DeviceBySpec> : Device { + override var context: Context = Global + internal set + public var meta: Meta = Meta.EMPTY + internal set + public var properties: Map> = emptyMap() + internal set + public var actions: Map> = emptyMap() + internal set + + override val propertyDescriptors: Collection + get() = properties.values.map { it.descriptor } + + override val actionDescriptors: Collection + get() = actions.values.map { it.descriptor } + + override val scope: CoroutineScope get() = context + + private val logicalState: HashMap = HashMap() + + private val _propertyFlow: MutableSharedFlow>> = MutableSharedFlow() + + override val propertyFlow: SharedFlow> get() = _propertyFlow + + @Suppress("UNCHECKED_CAST") + internal val self: D get() = this as D + + @Synchronized + private fun setLogicalState(propertyName: String, value: MetaItem?) { + logicalState[propertyName] = value + } + + /** + * Force read physical value and push an update if it is changed + */ + public suspend fun readProperty(propertyName: String): MetaItem { + val newValue = properties[propertyName]?.readItem(self) + ?: error("A property with name $propertyName is not registered in $this") + if (newValue != logicalState[propertyName]) { + setLogicalState(propertyName, newValue) + _propertyFlow.emit(propertyName to newValue) + } + return newValue + } + + override suspend fun getProperty(propertyName: String): MetaItem = + logicalState[propertyName] ?: readProperty(propertyName) + + override suspend fun invalidateProperty(propertyName: String) { + logicalState.remove(propertyName) + } + + override suspend fun setProperty(propertyName: String, value: MetaItem) { + //If there is a physical property with given name, invalidate logical property and write physical one + (properties[propertyName] as? WritableDevicePropertySpec)?.let { + it.writeItem(self, value) + invalidateProperty(propertyName) + } ?: run { + setLogicalState(propertyName, value) + } + } + + override suspend fun execute(action: String, argument: MetaItem?): MetaItem? = + actions[action]?.executeItem(self, argument) +} \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DevicePropertySpec.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DevicePropertySpec.kt new file mode 100644 index 0000000..38fcb9f --- /dev/null +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DevicePropertySpec.kt @@ -0,0 +1,77 @@ +package ru.mipt.npm.controls.properties + +import ru.mipt.npm.controls.api.ActionDescriptor +import ru.mipt.npm.controls.api.Device +import ru.mipt.npm.controls.api.PropertyDescriptor +import space.kscience.dataforge.meta.MetaItem +import space.kscience.dataforge.meta.transformations.MetaConverter +import space.kscience.dataforge.meta.transformations.nullableItemToObject +import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem + +//TODO relax T restriction after DF 0.4.4 +public interface DevicePropertySpec { + /** + * Property name, should be unique in device + */ + public val name: String + + /** + * Property descriptor + */ + public val descriptor: PropertyDescriptor + + /** + * Meta item converter for resulting type + */ + public val converter: MetaConverter + + /** + * Read physical value from the given [device] + */ + public suspend fun read(device: D): T +} + +public suspend fun DevicePropertySpec.readItem(device: D): MetaItem = + converter.objectToMetaItem(read(device)) + + +public interface WritableDevicePropertySpec : DevicePropertySpec { + /** + * Write physical value to a device + */ + public suspend fun write(device: D, value: T) +} + +public suspend fun WritableDevicePropertySpec.writeItem(device: D, item: MetaItem) { + write(device, converter.itemToObject(item)) +} + +public interface DeviceActionSpec { + /** + * Action name, should be unique in device + */ + public val name: String + + /** + * Action descriptor + */ + public val descriptor: ActionDescriptor + + public val inputConverter: MetaConverter + + public val outputConverter: MetaConverter + + /** + * Execute action on a device + */ + public suspend fun execute(device: D, input: I?): O? +} + +public suspend fun DeviceActionSpec.executeItem( + device: D, + item: MetaItem? +): MetaItem? { + val arg = inputConverter.nullableItemToObject(item) + val res = execute(device, arg) + return outputConverter.nullableObjectToMetaItem(res) +} \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt new file mode 100644 index 0000000..fce0a81 --- /dev/null +++ b/controls-core/src/commonMain/kotlin/ru/mipt/npm/controls/properties/DeviceSpec.kt @@ -0,0 +1,100 @@ +package ru.mipt.npm.controls.properties + +import ru.mipt.npm.controls.api.ActionDescriptor +import ru.mipt.npm.controls.api.PropertyDescriptor +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.Factory +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.transformations.MetaConverter +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty + +public abstract class DeviceSpec>( + private val buildDevice: () -> D +) : Factory { + private val deviceProperties = HashMap>() + private val deviceActions = HashMap>() + + public fun property( + converter: MetaConverter, + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> T + ): PropertyDelegateProvider, ReadOnlyProperty, DevicePropertySpec>> = + PropertyDelegateProvider { _: DeviceSpec, property -> + val propertyName = name ?: property.name + val deviceProperty = object : DevicePropertySpec { + override val name: String = propertyName + override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder) + override val converter: MetaConverter = converter + + override suspend fun read(device: D): T = device.read() + } + deviceProperties[propertyName] = deviceProperty + ReadOnlyProperty, DevicePropertySpec> { _, _ -> + deviceProperty + } + } + + + public fun property( + converter: MetaConverter, + name: String? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + read: suspend D.() -> T, + write: suspend D.(T) -> Unit + ): PropertyDelegateProvider, ReadOnlyProperty, WritableDevicePropertySpec>> = + PropertyDelegateProvider { _: DeviceSpec, property -> + val propertyName = name ?: property.name + val deviceProperty = object : WritableDevicePropertySpec { + override val name: String = propertyName + override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder) + override val converter: MetaConverter = converter + + override suspend fun read(device: D): T = device.read() + + override suspend fun write(device: D, value: T) { + device.write(value) + } + } + deviceProperties[propertyName] = deviceProperty + ReadOnlyProperty, WritableDevicePropertySpec> { _, _ -> + deviceProperty + } + } + + + public fun action( + inputConverter: MetaConverter, + outputConverter: MetaConverter, + name: String? = null, + descriptorBuilder: ActionDescriptor.() -> Unit = {}, + execute: suspend D.(I?) -> O? + ): PropertyDelegateProvider, ReadOnlyProperty, DeviceActionSpec>> = + PropertyDelegateProvider { _: DeviceSpec, property -> + val actionName = name ?: property.name + val deviceAction = object : DeviceActionSpec { + override val name: String = actionName + override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply(descriptorBuilder) + + override val inputConverter: MetaConverter = inputConverter + override val outputConverter: MetaConverter = outputConverter + + override suspend fun execute(device: D, input: I?): O? { + return device.execute(input) + } + } + deviceActions[actionName] = deviceAction + ReadOnlyProperty, DeviceActionSpec> { _, _ -> + deviceAction + } + } + + + override fun invoke(meta: Meta, context: Context): D = buildDevice().apply { + this.context = context + this.meta = meta + this.properties = deviceProperties + this.actions = deviceActions + } +} \ No newline at end of file