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