Add alternative device syntax
This commit is contained in:
parent
28e6e24cf7
commit
a87b46cd2b
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user