Add alternative device syntax

This commit is contained in:
Alexander Nozik 2021-06-23 22:23:44 +03:00
parent 28e6e24cf7
commit a87b46cd2b
3 changed files with 260 additions and 0 deletions

View File

@ -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<D: DeviceBySpec<D>> : Device {
override var context: Context = Global
internal set
public var meta: Meta = Meta.EMPTY
internal set
public var properties: Map<String, DevicePropertySpec<D, *>> = emptyMap()
internal set
public var actions: Map<String, DeviceActionSpec<D, *, *>> = emptyMap()
internal set
override val propertyDescriptors: Collection<PropertyDescriptor>
get() = properties.values.map { it.descriptor }
override val actionDescriptors: Collection<ActionDescriptor>
get() = actions.values.map { it.descriptor }
override val scope: CoroutineScope get() = context
private val logicalState: HashMap<String, MetaItem?> = HashMap()
private val _propertyFlow: MutableSharedFlow<Pair<String, TypedMetaItem<*>>> = MutableSharedFlow()
override val propertyFlow: SharedFlow<Pair<String, MetaItem>> 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<D, out Any>)?.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)
}

View File

@ -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<in D : Device, T : Any> {
/**
* 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<T>
/**
* Read physical value from the given [device]
*/
public suspend fun read(device: D): T
}
public suspend fun <D : Device, T : Any> DevicePropertySpec<D, T>.readItem(device: D): MetaItem =
converter.objectToMetaItem(read(device))
public interface WritableDevicePropertySpec<in D : Device, T : Any> : DevicePropertySpec<D, T> {
/**
* Write physical value to a device
*/
public suspend fun write(device: D, value: T)
}
public suspend fun <D : Device, T : Any> WritableDevicePropertySpec<D, T>.writeItem(device: D, item: MetaItem) {
write(device, converter.itemToObject(item))
}
public interface DeviceActionSpec<in D : Device, I : Any, O : Any> {
/**
* Action name, should be unique in device
*/
public val name: String
/**
* Action descriptor
*/
public val descriptor: ActionDescriptor
public val inputConverter: MetaConverter<I>
public val outputConverter: MetaConverter<O>
/**
* Execute action on a device
*/
public suspend fun execute(device: D, input: I?): O?
}
public suspend fun <D : Device, I : Any, O : Any> DeviceActionSpec<D, I, O>.executeItem(
device: D,
item: MetaItem?
): MetaItem? {
val arg = inputConverter.nullableItemToObject(item)
val res = execute(device, arg)
return outputConverter.nullableObjectToMetaItem(res)
}

View File

@ -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<D : DeviceBySpec<D>>(
private val buildDevice: () -> D
) : Factory<D> {
private val deviceProperties = HashMap<String, DevicePropertySpec<D, *>>()
private val deviceActions = HashMap<String, DeviceActionSpec<D, *, *>>()
public fun <T : Any> property(
converter: MetaConverter<T>,
name: String? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
read: suspend D.() -> T
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
val propertyName = name ?: property.name
val deviceProperty = object : DevicePropertySpec<D, T> {
override val name: String = propertyName
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
override val converter: MetaConverter<T> = converter
override suspend fun read(device: D): T = device.read()
}
deviceProperties[propertyName] = deviceProperty
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
deviceProperty
}
}
public fun <T : Any> property(
converter: MetaConverter<T>,
name: String? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
read: suspend D.() -> T,
write: suspend D.(T) -> Unit
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
val propertyName = name ?: property.name
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
override val name: String = propertyName
override val descriptor: PropertyDescriptor = PropertyDescriptor(this.name).apply(descriptorBuilder)
override val converter: MetaConverter<T> = 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<DeviceSpec<D>, WritableDevicePropertySpec<D, T>> { _, _ ->
deviceProperty
}
}
public fun <I : Any, O : Any> action(
inputConverter: MetaConverter<I>,
outputConverter: MetaConverter<O>,
name: String? = null,
descriptorBuilder: ActionDescriptor.() -> Unit = {},
execute: suspend D.(I?) -> O?
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
val actionName = name ?: property.name
val deviceAction = object : DeviceActionSpec<D, I, O> {
override val name: String = actionName
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply(descriptorBuilder)
override val inputConverter: MetaConverter<I> = inputConverter
override val outputConverter: MetaConverter<O> = outputConverter
override suspend fun execute(device: D, input: I?): O? {
return device.execute(input)
}
}
deviceActions[actionName] = deviceAction
ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>> { _, _ ->
deviceAction
}
}
override fun invoke(meta: Meta, context: Context): D = buildDevice().apply {
this.context = context
this.meta = meta
this.properties = deviceProperties
this.actions = deviceActions
}
}