New hierarchical structure based on CompositeDeviceSpec and ConfigurableCompositeDevice. Also added new builders for PropertyDescriptor and ActionDescriptor
This commit is contained in:
parent
e9a37f40fd
commit
7cde308114
@ -32,7 +32,7 @@ public class StateConstructorElement<T>(
|
||||
public val state: DeviceState<T>,
|
||||
) : ConstructorElement
|
||||
|
||||
public class ConnectionConstrucorElement(
|
||||
public class ConnectionConstructorElement(
|
||||
public val reads: Collection<DeviceState<*>>,
|
||||
public val writes: Collection<DeviceState<*>>,
|
||||
) : ConstructorElement
|
||||
@ -58,7 +58,7 @@ public interface StateContainer : ContextAware, CoroutineScope {
|
||||
reads: Collection<DeviceState<*>> = emptySet(),
|
||||
onChange: suspend (T) -> Unit,
|
||||
): Job = valueFlow.onEach(onChange).launchIn(this@StateContainer).also {
|
||||
registerElement(ConnectionConstrucorElement(reads + this, writes))
|
||||
registerElement(ConnectionConstructorElement(reads + this, writes))
|
||||
}
|
||||
|
||||
public fun <T> DeviceState<T>.onChange(
|
||||
@ -72,7 +72,7 @@ public interface StateContainer : ContextAware, CoroutineScope {
|
||||
onChange(pair.first, pair.second)
|
||||
}
|
||||
}.launchIn(this@StateContainer).also {
|
||||
registerElement(ConnectionConstrucorElement(reads + this, writes))
|
||||
registerElement(ConnectionConstructorElement(reads + this, writes))
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ public fun <T1, T2, R> StateContainer.combineState(
|
||||
* On resulting [Job] cancel the binding is unregistered
|
||||
*/
|
||||
public fun <T> StateContainer.bind(sourceState: DeviceState<T>, targetState: MutableDeviceState<T>): Job {
|
||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState), setOf(targetState))
|
||||
val descriptor = ConnectionConstructorElement(setOf(sourceState), setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return sourceState.valueFlow.onEach {
|
||||
targetState.value = it
|
||||
@ -186,7 +186,7 @@ public fun <T, R> StateContainer.transformTo(
|
||||
targetState: MutableDeviceState<R>,
|
||||
transformation: suspend (T) -> R,
|
||||
): Job {
|
||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState), setOf(targetState))
|
||||
val descriptor = ConnectionConstructorElement(setOf(sourceState), setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return sourceState.valueFlow.onEach {
|
||||
targetState.value = transformation(it)
|
||||
@ -208,7 +208,7 @@ public fun <T1, T2, R> StateContainer.combineTo(
|
||||
targetState: MutableDeviceState<R>,
|
||||
transformation: suspend (T1, T2) -> R,
|
||||
): Job {
|
||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState1, sourceState2), setOf(targetState))
|
||||
val descriptor = ConnectionConstructorElement(setOf(sourceState1, sourceState2), setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return kotlinx.coroutines.flow.combine(sourceState1.valueFlow, sourceState2.valueFlow, transformation).onEach {
|
||||
targetState.value = it
|
||||
@ -229,7 +229,7 @@ public inline fun <reified T, R> StateContainer.combineTo(
|
||||
targetState: MutableDeviceState<R>,
|
||||
noinline transformation: suspend (Array<T>) -> R,
|
||||
): Job {
|
||||
val descriptor = ConnectionConstrucorElement(sourceStates, setOf(targetState))
|
||||
val descriptor = ConnectionConstructorElement(sourceStates, setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return kotlinx.coroutines.flow.combine(sourceStates.map { it.valueFlow }, transformation).onEach {
|
||||
targetState.value = it
|
||||
|
@ -4,34 +4,109 @@ import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
|
||||
|
||||
//TODO add proper builders
|
||||
//TODO check proper builders
|
||||
|
||||
/**
|
||||
* A descriptor for property
|
||||
* A descriptor for a property
|
||||
*/
|
||||
@Serializable
|
||||
public class PropertyDescriptor(
|
||||
public val name: String,
|
||||
public var description: String? = null,
|
||||
public var metaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||
public var readable: Boolean = true,
|
||||
public var mutable: Boolean = false,
|
||||
public val description: String? = null,
|
||||
public val metaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||
public val readable: Boolean = true,
|
||||
public val mutable: Boolean = false,
|
||||
)
|
||||
|
||||
public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.() -> Unit) {
|
||||
metaDescriptor = MetaDescriptor {
|
||||
from(metaDescriptor)
|
||||
block()
|
||||
/**
|
||||
* A builder for PropertyDescriptor
|
||||
*/
|
||||
public class PropertyDescriptorBuilder(public val name: String) {
|
||||
public var description: String? = null
|
||||
public var metaDescriptor: MetaDescriptor = MetaDescriptor()
|
||||
public var readable: Boolean = true
|
||||
public var mutable: Boolean = false
|
||||
|
||||
/**
|
||||
* Configure the metaDescriptor using a block
|
||||
*/
|
||||
public fun metaDescriptor(block: MetaDescriptorBuilder.() -> Unit) {
|
||||
metaDescriptor = MetaDescriptor {
|
||||
from(metaDescriptor)
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the PropertyDescriptor
|
||||
*/
|
||||
public fun build(): PropertyDescriptor = PropertyDescriptor(
|
||||
name = name,
|
||||
description = description,
|
||||
metaDescriptor = metaDescriptor,
|
||||
readable = readable,
|
||||
mutable = mutable
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A descriptor for property
|
||||
* Create a PropertyDescriptor using a builder
|
||||
*/
|
||||
public fun propertyDescriptor(name: String, builder: PropertyDescriptorBuilder.() -> Unit = {}): PropertyDescriptor =
|
||||
PropertyDescriptorBuilder(name).apply(builder).build()
|
||||
|
||||
/**
|
||||
* A descriptor for an action
|
||||
*/
|
||||
@Serializable
|
||||
public class ActionDescriptor(
|
||||
public val name: String,
|
||||
public var description: String? = null,
|
||||
public var inputMetaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||
public var outputMetaDescriptor: MetaDescriptor = MetaDescriptor()
|
||||
public val description: String? = null,
|
||||
public val inputMetaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||
public val outputMetaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||
)
|
||||
|
||||
/**
|
||||
* A builder for ActionDescriptor
|
||||
*/
|
||||
public class ActionDescriptorBuilder(public val name: String) {
|
||||
public var description: String? = null
|
||||
public var inputMetaDescriptor: MetaDescriptor = MetaDescriptor()
|
||||
public var outputMetaDescriptor: MetaDescriptor = MetaDescriptor()
|
||||
|
||||
/**
|
||||
* Configure the inputMetaDescriptor using a block
|
||||
*/
|
||||
public fun inputMeta(block: MetaDescriptorBuilder.() -> Unit) {
|
||||
inputMetaDescriptor = MetaDescriptor {
|
||||
from(inputMetaDescriptor)
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the outputMetaDescriptor using a block
|
||||
*/
|
||||
public fun outputMeta(block: MetaDescriptorBuilder.() -> Unit) {
|
||||
outputMetaDescriptor = MetaDescriptor {
|
||||
from(outputMetaDescriptor)
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the ActionDescriptor
|
||||
*/
|
||||
public fun build(): ActionDescriptor = ActionDescriptor(
|
||||
name = name,
|
||||
description = description,
|
||||
inputMetaDescriptor = inputMetaDescriptor,
|
||||
outputMetaDescriptor = outputMetaDescriptor
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ActionDescriptor using a builder
|
||||
*/
|
||||
public fun actionDescriptor(name: String, builder: ActionDescriptorBuilder.() -> Unit = {}): ActionDescriptor =
|
||||
ActionDescriptorBuilder(name).apply(builder).build()
|
||||
|
@ -0,0 +1,809 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import space.kscience.controls.api.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.error
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.names.plus
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.time.Duration
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.ContextBuilder
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
|
||||
/**
|
||||
* Defines how child device lifecycle should be managed.
|
||||
*/
|
||||
public enum class LifecycleMode {
|
||||
/**
|
||||
* Device starts and stops with parent
|
||||
*/
|
||||
LINKED,
|
||||
|
||||
/**
|
||||
* Device is started and stopped independently
|
||||
*/
|
||||
INDEPENDENT,
|
||||
|
||||
/**
|
||||
* Device is created but starts only when explicitly requested
|
||||
*/
|
||||
LAZY
|
||||
}
|
||||
|
||||
public sealed class DeviceChangeEvent {
|
||||
public abstract val deviceName: Name
|
||||
|
||||
public data class Added(override val deviceName: Name, val device: Device) : DeviceChangeEvent()
|
||||
public data class Removed(override val deviceName: Name) : DeviceChangeEvent()
|
||||
}
|
||||
|
||||
public interface ComponentRegistry : ContextAware {
|
||||
public fun <D: ConfigurableCompositeControlComponent<*>> getSpec(name: Name): CompositeControlComponentSpec<D>?
|
||||
}
|
||||
|
||||
public class ComponentRegistryManager : AbstractPlugin(), ComponentRegistry {
|
||||
private val specs = mutableMapOf<Name, CompositeControlComponentSpec<*>>()
|
||||
|
||||
override val tag: PluginTag = Companion.tag
|
||||
|
||||
override fun <D : ConfigurableCompositeControlComponent<*>> getSpec(name: Name): CompositeControlComponentSpec<D>? {
|
||||
try {
|
||||
return specs[name] as? CompositeControlComponentSpec<D>
|
||||
} catch (e: ClassCastException) {
|
||||
logger.error(e) { "Failed to get spec $name" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public fun registerSpec(spec: CompositeControlComponentSpec<*>) {
|
||||
specs[(spec as DevicePropertySpec<*, *>).name.asName()] = spec
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<ComponentRegistryManager> {
|
||||
override val tag: PluginTag = PluginTag("controls.spechub", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override fun build(context: Context, meta: Meta): ComponentRegistryManager = ComponentRegistryManager()
|
||||
|
||||
}
|
||||
}
|
||||
public val Context.componentRegistry: ComponentRegistry? get() = plugins[ComponentRegistryManager]
|
||||
|
||||
public fun ContextBuilder.withSpecHub() {
|
||||
plugin(ComponentRegistryManager)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for [DeviceLifecycleConfig].
|
||||
*/
|
||||
public class DeviceLifecycleConfigBuilder {
|
||||
public var lifecycleMode: LifecycleMode = LifecycleMode.LINKED
|
||||
public var messageBuffer: Int = 1000
|
||||
public var startDelay: Duration = Duration.ZERO
|
||||
public var startTimeout: Duration? = null
|
||||
public var stopTimeout: Duration? = null
|
||||
public var coroutineScope: CoroutineScope? = null
|
||||
public var dispatcher: CoroutineDispatcher? = null
|
||||
public var onError: ChildDeviceErrorHandler = ChildDeviceErrorHandler.RESTART
|
||||
|
||||
public fun build(): DeviceLifecycleConfig = DeviceLifecycleConfig(
|
||||
lifecycleMode, messageBuffer, startDelay, startTimeout, stopTimeout, coroutineScope, dispatcher, onError
|
||||
)
|
||||
}
|
||||
|
||||
public fun DeviceLifecycleConfigBuilder.linked() {
|
||||
lifecycleMode = LifecycleMode.LINKED
|
||||
}
|
||||
|
||||
public fun DeviceLifecycleConfigBuilder.independent() {
|
||||
lifecycleMode = LifecycleMode.INDEPENDENT
|
||||
}
|
||||
|
||||
public fun DeviceLifecycleConfigBuilder.lazy() {
|
||||
lifecycleMode = LifecycleMode.LAZY
|
||||
}
|
||||
|
||||
public fun DeviceLifecycleConfigBuilder.restartOnError() {
|
||||
onError = ChildDeviceErrorHandler.RESTART
|
||||
}
|
||||
|
||||
public fun DeviceLifecycleConfigBuilder.propagateError() {
|
||||
onError = ChildDeviceErrorHandler.PROPAGATE
|
||||
}
|
||||
|
||||
public fun DeviceLifecycleConfigBuilder.withCustomTimeout(timeout: Duration) {
|
||||
startTimeout = timeout
|
||||
stopTimeout = timeout
|
||||
}
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class CompositeControlComponentSpec<D : Device>() : CompositeDeviceSpec<D> {
|
||||
|
||||
private val _properties = hashMapOf<String, DevicePropertySpec<D, *>>(
|
||||
DeviceMetaPropertySpec.name to DeviceMetaPropertySpec
|
||||
)
|
||||
|
||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
||||
|
||||
private val _actions = hashMapOf<String, DeviceActionSpec<D, *, *>>()
|
||||
|
||||
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
|
||||
|
||||
private val _childSpecs = mutableMapOf<String, ChildComponentConfig<*>>()
|
||||
|
||||
override val childSpecs: Map<String, ChildComponentConfig<*>> get() = _childSpecs
|
||||
|
||||
public fun <CDS: CompositeControlComponentSpec<CD>, CD: ConfigurableCompositeControlComponent<CD>> childSpec(
|
||||
deviceName: String? = null,
|
||||
specName: Name? = null,
|
||||
metaBuilder: (MutableMeta.() -> Unit)? = null,
|
||||
configBuilder: DeviceLifecycleConfigBuilder.() -> Unit = {},
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, CompositeControlComponentSpec<CD>>> =
|
||||
PropertyDelegateProvider { thisRef, property ->
|
||||
ReadOnlyProperty { _, _ ->
|
||||
val childSpecName = specName ?: property.name.asName()
|
||||
val nameForDevice = deviceName?.asName() ?: property.name.asName()
|
||||
val config = DeviceLifecycleConfigBuilder().apply(configBuilder).build()
|
||||
val meta = metaBuilder?.let { Meta(it) }
|
||||
val spec = (thisRef as ConfigurableCompositeControlComponent<*>).context.componentRegistry?.getSpec<CD>(childSpecName) ?: error("Spec with name '$specName' is not found")
|
||||
val childComponentConfig = object : ChildComponentConfig<CD>{
|
||||
override val spec: CompositeControlComponentSpec<CD> = spec
|
||||
override val config: DeviceLifecycleConfig = config
|
||||
override val meta: Meta? = meta
|
||||
override val name: Name = nameForDevice
|
||||
}
|
||||
_childSpecs[property.name] = childComponentConfig
|
||||
childComponentConfig.spec
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(device: D) {
|
||||
properties.map { it.value.descriptor }.forEach { specProperty ->
|
||||
check(specProperty in device.propertyDescriptors) { "Property ${specProperty.name} not registered in ${device.id}" }
|
||||
}
|
||||
actions.map { it.value.descriptor }.forEach { specAction ->
|
||||
check(specAction in device.actionDescriptors) { "Action ${specAction.name} not registered in ${device.id}" }
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
|
||||
_properties[deviceProperty.name] = deviceProperty
|
||||
return deviceProperty
|
||||
}
|
||||
|
||||
override fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
||||
_actions[deviceAction.name] = deviceAction
|
||||
return deviceAction
|
||||
}
|
||||
|
||||
override fun createPropertyDescriptorInternal(
|
||||
propertyName: String,
|
||||
converter: MetaConverter<*>,
|
||||
mutable: Boolean,
|
||||
property: KProperty<*>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit,
|
||||
): PropertyDescriptor {
|
||||
return propertyDescriptor(propertyName) {
|
||||
this.mutable = mutable
|
||||
converter.descriptor?.let { converterDescriptor ->
|
||||
metaDescriptor {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
}
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createActionDescriptor(
|
||||
actionName: String,
|
||||
inputConverter: MetaConverter<*>,
|
||||
outputConverter: MetaConverter<*>,
|
||||
property: KProperty<*>,
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit,
|
||||
): ActionDescriptor {
|
||||
return actionDescriptor(actionName) {
|
||||
inputConverter.descriptor?.let { converterDescriptor ->
|
||||
inputMeta {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
}
|
||||
outputConverter.descriptor?.let { converterDescriptor ->
|
||||
outputMeta {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
}
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit,
|
||||
name: String?,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _: CompositeControlComponentSpec<D>, property ->
|
||||
val propertyName = name ?: property.name
|
||||
val descriptor = createPropertyDescriptorInternal(
|
||||
propertyName = propertyName,
|
||||
converter = converter,
|
||||
mutable = false,
|
||||
property = property,
|
||||
descriptorBuilder = descriptorBuilder
|
||||
)
|
||||
val deviceProperty = registerProperty(object : DevicePropertySpec<D, T> {
|
||||
override val descriptor = descriptor
|
||||
override val converter = converter
|
||||
|
||||
override suspend fun read(device: D): T? =
|
||||
withContext(device.coroutineContext) { device.read(propertyName) }
|
||||
})
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceProperty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun <T> mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit,
|
||||
name: String?,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
write: suspend D.(propertyName: String, value: T) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _: CompositeControlComponentSpec<D>, property ->
|
||||
val propertyName = name ?: property.name
|
||||
val descriptor = createPropertyDescriptorInternal(
|
||||
propertyName = propertyName,
|
||||
converter = converter,
|
||||
mutable = true,
|
||||
property = property,
|
||||
descriptorBuilder = descriptorBuilder
|
||||
)
|
||||
val deviceProperty = registerProperty(object : MutableDevicePropertySpec<D, T> {
|
||||
override val descriptor = descriptor
|
||||
override val converter = converter
|
||||
override suspend fun read(device: D): T? =
|
||||
withContext(device.coroutineContext) { device.read(propertyName) }
|
||||
|
||||
override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) {
|
||||
device.write(propertyName, value)
|
||||
}
|
||||
})
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceProperty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun <I, O> action(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit,
|
||||
name: String?,
|
||||
execute: suspend D.(I) -> O,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||
PropertyDelegateProvider { _: CompositeControlComponentSpec<D>, property ->
|
||||
val actionName = name ?: property.name
|
||||
val descriptor = createActionDescriptor(
|
||||
actionName = actionName,
|
||||
inputConverter = inputConverter,
|
||||
outputConverter = outputConverter,
|
||||
property = property,
|
||||
descriptorBuilder = descriptorBuilder
|
||||
)
|
||||
val deviceAction = registerAction(object : DeviceActionSpec<D, I, O> {
|
||||
override val descriptor = descriptor
|
||||
override val inputConverter = inputConverter
|
||||
override val outputConverter = outputConverter
|
||||
override suspend fun execute(device: D, input: I): O = withContext(device.coroutineContext) {
|
||||
device.execute(input)
|
||||
}
|
||||
})
|
||||
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceAction
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun D.onOpen() {}
|
||||
override suspend fun D.onClose() {}
|
||||
|
||||
}
|
||||
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.unitAction(
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.() -> Unit,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Unit, Unit>>> =
|
||||
action(
|
||||
MetaConverter.unit,
|
||||
MetaConverter.unit,
|
||||
descriptorBuilder,
|
||||
name
|
||||
) {
|
||||
execute()
|
||||
}
|
||||
|
||||
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.metaAction(
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(Meta) -> Meta,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||
action(
|
||||
MetaConverter.meta,
|
||||
MetaConverter.meta,
|
||||
descriptorBuilder,
|
||||
name
|
||||
) {
|
||||
execute(it)
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic interface for device description
|
||||
*/
|
||||
public interface CompositeDeviceSpec<D : Device> {
|
||||
public val properties: Map<String, DevicePropertySpec<D, *>>
|
||||
|
||||
public val actions: Map<String, DeviceActionSpec<D, *, *>>
|
||||
|
||||
public val childSpecs: Map<String, ChildComponentConfig<*>>
|
||||
|
||||
/**
|
||||
* Called on `start()`
|
||||
*/
|
||||
public suspend fun D.onOpen()
|
||||
|
||||
/**
|
||||
* Called on `stop()`
|
||||
*/
|
||||
public suspend fun D.onClose()
|
||||
|
||||
/**
|
||||
* Registers a property in the spec.
|
||||
*/
|
||||
public fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P
|
||||
|
||||
/**
|
||||
* Registers an action in the spec.
|
||||
*/
|
||||
public fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O>
|
||||
|
||||
public fun validate(device: D)
|
||||
|
||||
public fun createPropertyDescriptorInternal(
|
||||
propertyName: String,
|
||||
converter: MetaConverter<*>,
|
||||
mutable: Boolean,
|
||||
property: KProperty<*>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit
|
||||
): PropertyDescriptor
|
||||
|
||||
public fun createActionDescriptor(
|
||||
actionName: String,
|
||||
inputConverter: MetaConverter<*>,
|
||||
outputConverter: MetaConverter<*>,
|
||||
property: KProperty<*>,
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit
|
||||
): ActionDescriptor
|
||||
|
||||
public fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, T>>>
|
||||
|
||||
public fun <T> mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
write: suspend D.(propertyName: String, value: T) -> Unit,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, T>>>
|
||||
|
||||
public fun <I, O> action(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(I) -> O,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>>
|
||||
}
|
||||
|
||||
public data class DeviceLifecycleConfig(
|
||||
val lifecycleMode: LifecycleMode = LifecycleMode.LINKED,
|
||||
val messageBuffer: Int = 1000,
|
||||
val startDelay: Duration = Duration.ZERO,
|
||||
val startTimeout: Duration? = null,
|
||||
val stopTimeout: Duration? = null,
|
||||
val coroutineScope: CoroutineScope? = null,
|
||||
val dispatcher: CoroutineDispatcher? = null,
|
||||
val onError: ChildDeviceErrorHandler = ChildDeviceErrorHandler.RESTART
|
||||
) {
|
||||
init {
|
||||
require(messageBuffer > 0) { "Message buffer size must be positive." }
|
||||
startTimeout?.let { require(it.isPositive()) { "Start timeout must be positive." } }
|
||||
stopTimeout?.let { require(it.isPositive()) { "Stop timeout must be positive." } }
|
||||
}
|
||||
}
|
||||
|
||||
public enum class ChildDeviceErrorHandler {
|
||||
IGNORE,
|
||||
RESTART,
|
||||
STOP_PARENT,
|
||||
PROPAGATE
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for managing child devices. Manages lifecycle and message flow.
|
||||
*/
|
||||
public abstract class AbstractDeviceHubManager(
|
||||
public val context: Context,
|
||||
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
) {
|
||||
internal val childrenJobs: MutableMap<Name, ChildJob> = mutableMapOf()
|
||||
public val devices: Map<Name, Device> get() = childrenJobs.mapValues { it.value.device }
|
||||
|
||||
internal data class ChildJob(
|
||||
val device: Device,
|
||||
val job: Job,
|
||||
val lifecycleMode: LifecycleMode,
|
||||
val messageBus: MutableSharedFlow<DeviceMessage>,
|
||||
val meta: Meta? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* A centralized bus for messages
|
||||
*/
|
||||
public abstract val messageBus: MutableSharedFlow<DeviceMessage>
|
||||
|
||||
/**
|
||||
* A centralized bus for device change events
|
||||
*/
|
||||
public abstract val deviceChanges: MutableSharedFlow<DeviceChangeEvent>
|
||||
|
||||
|
||||
/**
|
||||
* Launches a child device with a specific lifecycle mode and error handling.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal fun launchChild(name: Name, device: Device, config: DeviceLifecycleConfig, meta: Meta? = null): ChildJob {
|
||||
val childMessageBus = MutableSharedFlow<DeviceMessage>(
|
||||
replay = config.messageBuffer,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
val childScope = config.coroutineScope ?: context
|
||||
val job = childScope.launch(dispatcher + CoroutineName("Child device $name")) {
|
||||
try {
|
||||
if (config.lifecycleMode != LifecycleMode.INDEPENDENT) {
|
||||
if (config.lifecycleMode == LifecycleMode.LINKED || device.lifecycleState == LifecycleState.STARTING){
|
||||
delay(config.startDelay)
|
||||
withTimeoutOrNull(config.startTimeout ?: Duration.INFINITE) {
|
||||
device.start()
|
||||
} ?: error("Timeout on start for $name")
|
||||
}
|
||||
}
|
||||
|
||||
device.messageFlow.collect { message ->
|
||||
childMessageBus.emit(message.changeSource { name.plus(it) })
|
||||
messageBus.emit(message.changeSource { name.plus(it) })
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
val errorMessage = DeviceMessage.error(ex, name)
|
||||
messageBus.emit(errorMessage)
|
||||
|
||||
when (config.onError) {
|
||||
ChildDeviceErrorHandler.IGNORE -> context.logger.error(ex) { "Error in child device $name ignored" }
|
||||
ChildDeviceErrorHandler.RESTART -> {
|
||||
context.logger.error(ex) { "Error in child device $name, restarting" }
|
||||
removeDevice(name)
|
||||
childrenJobs[name] = launchChild(name, device, config, meta)
|
||||
}
|
||||
ChildDeviceErrorHandler.STOP_PARENT -> {
|
||||
context.logger.error(ex) { "Error in child device $name, stopping parent" }
|
||||
coroutineContext[Job]?.cancelAndJoin()
|
||||
}
|
||||
ChildDeviceErrorHandler.PROPAGATE -> {
|
||||
context.logger.error(ex) { "Error in child device $name propagated to parent" }
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
childrenJobs.remove(name)
|
||||
clearReplayCache(childMessageBus)
|
||||
deviceChanges.emit(DeviceChangeEvent.Removed(name))
|
||||
messageBus.emit(DeviceLogMessage("Device $name stopped", sourceDevice = name))
|
||||
if (device is ConfigurableCompositeControlComponent<*>) {
|
||||
device.onChildStop()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ChildJob(device, job, config.lifecycleMode, childMessageBus, meta)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun <T> clearReplayCache(mutableSharedFlow: MutableSharedFlow<T>){
|
||||
val cached = mutableSharedFlow.replayCache
|
||||
mutableSharedFlow.resetReplayCache()
|
||||
cached.forEach { mutableSharedFlow.tryEmit(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a device to the hub and manage its lifecycle according to its spec
|
||||
*/
|
||||
public fun addDevice(name: Name, device: Device, config: DeviceLifecycleConfig, meta: Meta? = null) {
|
||||
val existingDevice = devices[name]
|
||||
if (existingDevice != null) {
|
||||
if(existingDevice == device) {
|
||||
error("Device with name $name is already installed")
|
||||
}
|
||||
context.launch(device.coroutineContext) { existingDevice.stopWithTimeout(config.stopTimeout ?: Duration.INFINITE) }
|
||||
}
|
||||
childrenJobs[name] = launchChild(name, device, config, meta)
|
||||
|
||||
context.launch {
|
||||
deviceChanges.emit(DeviceChangeEvent.Added(name, device))
|
||||
messageBus.emit(DeviceLogMessage("Device $name added", sourceDevice = name))
|
||||
}
|
||||
}
|
||||
|
||||
public fun removeDevice(name: Name) {
|
||||
childrenJobs[name]?.let { childJob ->
|
||||
context.launch(childJob.device.coroutineContext) {
|
||||
val timeout = when (childJob.lifecycleMode) {
|
||||
LifecycleMode.INDEPENDENT -> childJob.device.meta["stopTimeout".asName()]?.value?.let {
|
||||
Duration.parse(it.toString())
|
||||
} ?: Duration.INFINITE
|
||||
|
||||
else -> Duration.INFINITE
|
||||
}
|
||||
withTimeoutOrNull(timeout) {
|
||||
childJob.job.cancelAndJoin()
|
||||
childJob.device.stop()
|
||||
} ?: error("Timeout on stop for $name")
|
||||
}
|
||||
childrenJobs.remove(name)
|
||||
context.launch {
|
||||
messageBus.emit(DeviceLogMessage("Device $name removed", sourceDevice = name))
|
||||
deviceChanges.emit(DeviceChangeEvent.Removed(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change lifecycle mode of a child device
|
||||
*/
|
||||
public fun changeLifecycleMode(name: Name, mode: LifecycleMode) {
|
||||
val job = childrenJobs[name] ?: error("Device with name '$name' is not found")
|
||||
val config = DeviceLifecycleConfig(lifecycleMode = mode, messageBuffer = job.messageBus.replayCache.size)
|
||||
context.launch {
|
||||
job.job.cancelAndJoin()
|
||||
childrenJobs[name] = launchChild(name, job.device, config, job.meta)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local message bus for a child device
|
||||
*/
|
||||
public fun getChildMessageBus(name: Name) : MutableSharedFlow<DeviceMessage>? = childrenJobs[name]?.messageBus
|
||||
|
||||
/**
|
||||
* Method for explicit call when child device is stopped.
|
||||
*/
|
||||
internal open fun onChildStop(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class DeviceHubManagerImpl(context: Context, dispatcher: CoroutineDispatcher = Dispatchers.Default) : AbstractDeviceHubManager(context, dispatcher){
|
||||
override val messageBus: MutableSharedFlow<DeviceMessage> = MutableSharedFlow(
|
||||
replay = 1000,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
override val deviceChanges: MutableSharedFlow<DeviceChangeEvent> = MutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for a device that contains other devices as children.
|
||||
*/
|
||||
public interface CompositeControlComponent : Device {
|
||||
/**
|
||||
* A centralized flow of all device messages from this node and all its children
|
||||
*/
|
||||
public val messageBus: SharedFlow<DeviceMessage>
|
||||
|
||||
/**
|
||||
* A map of child devices
|
||||
*/
|
||||
public val devices: Map<Name, Device>
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for devices created from specification, using AbstractDeviceHubManager for children management
|
||||
*/
|
||||
public open class ConfigurableCompositeControlComponent<D : Device>(
|
||||
public open val spec: CompositeControlComponentSpec<D>,
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
config: DeviceLifecycleConfig = DeviceLifecycleConfig(),
|
||||
private val hubManager: AbstractDeviceHubManager = DeviceHubManagerImpl(context, config.dispatcher ?: Dispatchers.Default),
|
||||
) : DeviceBase<D>(context, meta), CompositeControlComponent {
|
||||
|
||||
override val properties: Map<String, DevicePropertySpec<D, *>>
|
||||
get() = spec.properties
|
||||
|
||||
override val actions: Map<String, DeviceActionSpec<D, *, *>>
|
||||
get() = spec.actions
|
||||
|
||||
final override val messageBus: MutableSharedFlow<DeviceMessage>
|
||||
get() = hubManager.messageBus
|
||||
|
||||
public val deviceChanges: MutableSharedFlow<DeviceChangeEvent>
|
||||
get() = hubManager.deviceChanges
|
||||
|
||||
public val aggregatedMessageFlow: SharedFlow<DeviceMessage>
|
||||
get() = hubManager.messageBus
|
||||
|
||||
init {
|
||||
spec.childSpecs.forEach{ (name, childSpec) ->
|
||||
val childDevice = ConfigurableCompositeControlComponent(childSpec.spec, context, childSpec.meta ?: Meta.EMPTY, childSpec.config)
|
||||
addDevice(childSpec.name, childDevice, childSpec.config, childSpec.meta)
|
||||
}
|
||||
|
||||
spec.actions.values.forEach { actionSpec ->
|
||||
launch {
|
||||
val actionName = actionSpec.name
|
||||
messageFlow.filterIsInstance<ActionExecuteMessage>().filter { it.action == actionName }.onEach {
|
||||
val result = execute(actionName, it.argument)
|
||||
messageBus.emit(
|
||||
ActionResultMessage(
|
||||
action = actionName,
|
||||
result = result,
|
||||
requestId = it.requestId,
|
||||
sourceDevice = id.asName()
|
||||
)
|
||||
)
|
||||
}.launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onStart() {
|
||||
with(spec) {
|
||||
self.onOpen()
|
||||
validate(self)
|
||||
}
|
||||
hubManager.devices.values.filter {
|
||||
it.lifecycleState != LifecycleState.STARTED && it.lifecycleState != LifecycleState.STARTING
|
||||
}.forEach {
|
||||
if (hubManager.childrenJobs[it.id.parseAsName()]?.lifecycleMode != LifecycleMode.LAZY) {
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getTimeout(device: Device): Duration {
|
||||
return (hubManager.childrenJobs[device.id.parseAsName()]?.lifecycleMode ?: LifecycleMode.LINKED).let{
|
||||
if(it == LifecycleMode.INDEPENDENT)
|
||||
it.name.let{ meta["stopTimeout".parseAsName()]?.let { durationMeta ->
|
||||
Duration.parse(durationMeta.value.toString())
|
||||
} ?: Duration.INFINITE }
|
||||
else Duration.INFINITE
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onStop() {
|
||||
hubManager.devices.values.forEach {
|
||||
launch(it.coroutineContext){
|
||||
withTimeoutOrNull(getTimeout(it)){
|
||||
it.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
with(spec) {
|
||||
self.onClose()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "Device(spec=$spec)"
|
||||
|
||||
internal open fun onChildStop() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add existing device to this hub
|
||||
*/
|
||||
private fun addDevice(name: Name = device.id.asName(), device: Device, config: DeviceLifecycleConfig = DeviceLifecycleConfig(), meta: Meta? = null) {
|
||||
hubManager.addDevice(name, device, config, meta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child device from the hub by name.
|
||||
*/
|
||||
private fun removeDevice(name: Name) {
|
||||
hubManager.removeDevice(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all children devices
|
||||
*/
|
||||
public override val devices: Map<Name, Device>
|
||||
get() = hubManager.devices
|
||||
|
||||
/**
|
||||
* Get child device from this hub by name
|
||||
*/
|
||||
public fun <CD : ConfigurableCompositeControlComponent<CD>> getChildDevice(name: Name): ConfigurableCompositeControlComponent<CD> =
|
||||
hubManager.devices[name] as? ConfigurableCompositeControlComponent<CD>? ?: error("Device $name not found")
|
||||
|
||||
public fun <CD: ConfigurableCompositeControlComponent<CD>> childDevice(name: Name? = null):
|
||||
PropertyDelegateProvider<ConfigurableCompositeControlComponent<D>, ReadOnlyProperty<ConfigurableCompositeControlComponent<D>, CD>>
|
||||
= PropertyDelegateProvider{ thisRef, property ->
|
||||
ReadOnlyProperty{ _, _ ->
|
||||
val deviceName = name ?: property.name.asName()
|
||||
thisRef.devices[deviceName] as? CD ?: error("Device $deviceName not found")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get child device message bus by name
|
||||
*/
|
||||
public fun getChildMessageBus(name: Name): SharedFlow<DeviceMessage>? = hubManager.getChildMessageBus(name)
|
||||
|
||||
/**
|
||||
* Get device, using delegate method
|
||||
*/
|
||||
public inline operator fun <reified D : Device> get(name: Name): D? = devices[name] as? D
|
||||
|
||||
/**
|
||||
* Get device, using delegate method
|
||||
*/
|
||||
public inline operator fun <reified D : Device> get(name: String): D? = devices[name.asName()] as? D
|
||||
|
||||
}
|
||||
|
||||
public suspend fun WithLifeCycle.stopWithTimeout(timeout: Duration = Duration.INFINITE) {
|
||||
withTimeoutOrNull(timeout) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
public interface ChildComponentConfig<CD: Device>{
|
||||
public val spec: CompositeControlComponentSpec<CD>
|
||||
public val config: DeviceLifecycleConfig
|
||||
public val meta: Meta?
|
||||
public val name: Name
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import space.kscience.controls.api.ActionDescriptorBuilder
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import space.kscience.controls.api.id
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.ValueType
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.string
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
/**
|
||||
* Create a [MetaConverter] for enum values
|
||||
*/
|
||||
public fun <E : Enum<E>> createEnumConverter(enumValues: Array<E>): MetaConverter<E> = object : MetaConverter<E> {
|
||||
override val descriptor: MetaDescriptor = MetaDescriptor {
|
||||
valueType(ValueType.STRING)
|
||||
allowedValues(enumValues.map { it.name })
|
||||
}
|
||||
|
||||
override fun readOrNull(source: Meta): E? {
|
||||
val value = source.value ?: return null
|
||||
return enumValues.firstOrNull { it.name == value.string }
|
||||
}
|
||||
|
||||
override fun convert(obj: E): Meta = Meta(obj.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* A read-only device property that delegates reading to a device [KProperty1]
|
||||
*/
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, T>>> {
|
||||
return property(converter, descriptorBuilder, name, read)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable property that delegates reading and writing to a device [KMutableProperty1]
|
||||
*/
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
write: suspend D.(propertyName: String, value: T) -> Unit,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, T>>> {
|
||||
return mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mutable logical property (without a corresponding physical state) for a device
|
||||
*/
|
||||
public fun <T, D : DeviceBase<D>> CompositeControlComponentSpec<D>.logical(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
||||
mutableProperty(
|
||||
converter,
|
||||
descriptorBuilder,
|
||||
name,
|
||||
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
|
||||
write = { propertyName, value -> writeProperty(propertyName, converter.convert(value)) }
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a boolean property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.boolean(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Boolean?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Boolean>>> =
|
||||
property(MetaConverter.boolean, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable boolean property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.booleanMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Boolean?,
|
||||
write: suspend D.(propertyName: String, value: Boolean) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Boolean>>> =
|
||||
mutableProperty(MetaConverter.boolean, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only number property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.number(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Number?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Number>>> =
|
||||
property(MetaConverter.number, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable number property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.numberMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Number?,
|
||||
write: suspend D.(propertyName: String, value: Number) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Number>>> =
|
||||
mutableProperty(MetaConverter.number, descriptorBuilder, name, read, write)
|
||||
|
||||
|
||||
/**
|
||||
* Creates a read-only double property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.double(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Double?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Double>>> =
|
||||
property(MetaConverter.double, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable double property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.doubleMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Double?,
|
||||
write: suspend D.(propertyName: String, value: Double) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Double>>> =
|
||||
mutableProperty(MetaConverter.double, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only string property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.string(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> String?
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, String>>> =
|
||||
property(MetaConverter.string, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable string property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.stringMutableProperty(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> String?,
|
||||
write: suspend D.(propertyName: String, value: String) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, String>>> =
|
||||
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only meta property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.meta(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Meta?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Meta>>> =
|
||||
property(MetaConverter.meta, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable meta property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.metaMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Meta?,
|
||||
write: suspend D.(propertyName: String, value: Meta) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
|
||||
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only enum property for a device.
|
||||
*/
|
||||
public fun <E : Enum<E>, D : Device> CompositeControlComponentSpec<D>.enum(
|
||||
enumValues: Array<E>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> E?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, E>>> {
|
||||
val converter = createEnumConverter(enumValues)
|
||||
return property(converter, descriptorBuilder, name, read)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mutable enum property for a device.
|
||||
*/
|
||||
public fun <E : Enum<E>, D : Device> CompositeControlComponentSpec<D>.enumMutable(
|
||||
enumValues: Array<E>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> E?,
|
||||
write: suspend D.(propertyName: String, value: E) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, E>>> {
|
||||
val converter = createEnumConverter(enumValues)
|
||||
return mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a read-only float property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.float(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Float?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Float>>> =
|
||||
property(MetaConverter.float, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable float property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.floatMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Float?,
|
||||
write: suspend D.(propertyName: String, value: Float) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Float>>> =
|
||||
mutableProperty(MetaConverter.float, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only long property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.long(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Long?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Long>>> =
|
||||
property(MetaConverter.long, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable long property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.longMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Long?,
|
||||
write: suspend D.(propertyName: String, value: Long) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Long>>> =
|
||||
mutableProperty(MetaConverter.long, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only int property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.int(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Int?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Int>>> =
|
||||
property(MetaConverter.int, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable int property for a device.
|
||||
*/
|
||||
public fun <D : Device> CompositeControlComponentSpec<D>.intMutable(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Int?,
|
||||
write: suspend D.(propertyName: String, value: Int) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Int>>> =
|
||||
mutableProperty(MetaConverter.int, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* Creates a read-only list property for a device.
|
||||
*/
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.list(
|
||||
converter: MetaConverter<List<T>>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> List<T>?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, List<T>>>> =
|
||||
property(converter, descriptorBuilder, name, read)
|
||||
|
||||
/**
|
||||
* Creates a mutable list property for a device.
|
||||
*/
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.listMutable(
|
||||
converter: MetaConverter<List<T>>,
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> List<T>?,
|
||||
write: suspend D.(propertyName: String, value: List<T>) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, List<T>>>> =
|
||||
mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
|
||||
|
||||
public fun <I, O, D : Device> CompositeControlComponentSpec<D>.asyncActionProperty(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(I) -> Deferred<O>,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||
action(inputConverter, outputConverter, descriptorBuilder, name) { input ->
|
||||
execute(input).await()
|
||||
}
|
||||
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.metaProperty(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Meta?,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DevicePropertySpec<D, Meta>>> =
|
||||
property(MetaConverter.meta, descriptorBuilder, name, read)
|
||||
|
||||
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.mutableMetaProperty(
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Meta?,
|
||||
write: suspend D.(propertyName: String, value: Meta) -> Unit
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
|
||||
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
|
||||
|
||||
/**
|
||||
* An action that takes no parameters and returns no values
|
||||
*/
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.unitAction(
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.() -> Unit,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Unit, Unit>>> =
|
||||
action(
|
||||
MetaConverter.unit,
|
||||
MetaConverter.unit,
|
||||
descriptorBuilder,
|
||||
name
|
||||
) {
|
||||
execute()
|
||||
}
|
||||
|
||||
public fun <I, O, D : Device> CompositeControlComponentSpec<D>.asyncAction(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(I) -> Deferred<O>,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||
action(
|
||||
inputConverter,
|
||||
outputConverter,
|
||||
descriptorBuilder,
|
||||
name
|
||||
) {
|
||||
execute(it).await()
|
||||
}
|
||||
|
||||
/**
|
||||
* An action that takes [Meta] and returns [Meta]. No conversions are done
|
||||
*/
|
||||
public fun <T, D : Device> CompositeControlComponentSpec<D>.metaAction(
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(Meta) -> Meta,
|
||||
): PropertyDelegateProvider<CompositeControlComponentSpec<D>, ReadOnlyProperty<CompositeControlComponentSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||
action(
|
||||
MetaConverter.meta,
|
||||
MetaConverter.meta,
|
||||
descriptorBuilder,
|
||||
name
|
||||
) {
|
||||
execute(it)
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception if device does not have all properties and actions defined by this specification
|
||||
*/
|
||||
public fun CompositeControlComponentSpec<*>.validate(device: Device) {
|
||||
properties.map { it.value.descriptor }.forEach { specProperty ->
|
||||
check(specProperty in device.propertyDescriptors) { "Property ${specProperty.name} not registered in ${device.id}" }
|
||||
}
|
||||
|
||||
actions.map { it.value.descriptor }.forEach { specAction ->
|
||||
check(specAction in device.actionDescriptors) { "Action ${specAction.name} not registered in ${device.id}" }
|
||||
}
|
||||
}
|
@ -44,24 +44,24 @@ public abstract class DeviceSpec<D : Device> {
|
||||
|
||||
public fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> 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 descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
|
||||
converter.descriptor?.let { converterDescriptor ->
|
||||
metaDescriptor {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
val descriptor = propertyDescriptor(propertyName) {
|
||||
mutable = false
|
||||
converter.descriptor?.let { converterDescriptor ->
|
||||
metaDescriptor {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||
override val descriptor = descriptor
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T? =
|
||||
@ -75,26 +75,25 @@ public abstract class DeviceSpec<D : Device> {
|
||||
|
||||
public fun <T> mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> T?,
|
||||
write: suspend D.(propertyName: String, value: T) -> Unit,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||
val propertyName = name ?: property.name
|
||||
val deviceProperty = object : MutableDevicePropertySpec<D, T> {
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(
|
||||
propertyName,
|
||||
mutable = true
|
||||
).apply {
|
||||
converter.descriptor?.let { converterDescriptor ->
|
||||
metaDescriptor {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
val descriptor = propertyDescriptor(propertyName) {
|
||||
this.mutable = true
|
||||
converter.descriptor?.let { converterDescriptor ->
|
||||
metaDescriptor {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
val deviceProperty = object : MutableDevicePropertySpec<D, T> {
|
||||
override val descriptor = descriptor
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T? =
|
||||
@ -119,30 +118,28 @@ public abstract class DeviceSpec<D : Device> {
|
||||
public fun <I, O> action(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(I) -> O,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||
val actionName = name ?: property.name
|
||||
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
||||
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply {
|
||||
inputConverter.descriptor?.let { converterDescriptor ->
|
||||
inputMetaDescriptor = MetaDescriptor {
|
||||
from(converterDescriptor)
|
||||
from(inputMetaDescriptor)
|
||||
}
|
||||
val descriptor = actionDescriptor(actionName) {
|
||||
inputConverter.descriptor?.let { converterDescriptor ->
|
||||
inputMeta {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
outputConverter.descriptor?.let { converterDescriptor ->
|
||||
outputMetaDescriptor = MetaDescriptor {
|
||||
from(converterDescriptor)
|
||||
from(outputMetaDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
outputConverter.descriptor?.let { converterDescriptor ->
|
||||
outputMeta {
|
||||
from(converterDescriptor)
|
||||
}
|
||||
}
|
||||
fromSpec(property)
|
||||
descriptorBuilder()
|
||||
}
|
||||
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
||||
override val descriptor: ActionDescriptor = descriptor
|
||||
|
||||
override val inputConverter: MetaConverter<I> = inputConverter
|
||||
override val outputConverter: MetaConverter<O> = outputConverter
|
||||
@ -162,7 +159,7 @@ public abstract class DeviceSpec<D : Device> {
|
||||
* An action that takes no parameters and returns no values
|
||||
*/
|
||||
public fun <D : Device> DeviceSpec<D>.unitAction(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.() -> Unit,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Unit, Unit>>> =
|
||||
@ -179,7 +176,7 @@ public fun <D : Device> DeviceSpec<D>.unitAction(
|
||||
* An action that takes [Meta] and returns [Meta]. No conversions are done
|
||||
*/
|
||||
public fun <D : Device> DeviceSpec<D>.metaAction(
|
||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: ActionDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
execute: suspend D.(Meta) -> Meta,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
||||
|
@ -1,10 +1,10 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.ActionDescriptor
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.ActionDescriptorBuilder
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
internal expect fun PropertyDescriptor.fromSpec(property: KProperty<*>)
|
||||
internal expect fun PropertyDescriptorBuilder.fromSpec(property: KProperty<*>)
|
||||
|
||||
internal expect fun ActionDescriptor.fromSpec(property: KProperty<*>)
|
||||
internal expect fun ActionDescriptorBuilder.fromSpec(property: KProperty<*>)
|
@ -1,8 +1,7 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.metaDescriptor
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.ValueType
|
||||
@ -17,7 +16,7 @@ import kotlin.reflect.KProperty1
|
||||
public fun <T, D : Device> DeviceSpec<D>.property(
|
||||
converter: MetaConverter<T>,
|
||||
readOnlyProperty: KProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> = property(
|
||||
converter,
|
||||
descriptorBuilder,
|
||||
@ -31,7 +30,7 @@ public fun <T, D : Device> DeviceSpec<D>.property(
|
||||
public fun <T, D : Device> DeviceSpec<D>.mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
readWriteProperty: KMutableProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
||||
mutableProperty(
|
||||
converter,
|
||||
@ -49,7 +48,7 @@ public fun <T, D : Device> DeviceSpec<D>.mutableProperty(
|
||||
*/
|
||||
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
||||
property(
|
||||
@ -60,7 +59,7 @@ public fun <T, D : DeviceBase<D>> DeviceSpec<D>.property(
|
||||
)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Boolean?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
||||
@ -76,8 +75,8 @@ public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
)
|
||||
|
||||
private inline fun numberDescriptor(
|
||||
crossinline descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): PropertyDescriptor.() -> Unit = {
|
||||
crossinline descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {}
|
||||
): PropertyDescriptorBuilder.() -> Unit = {
|
||||
metaDescriptor {
|
||||
valueType(ValueType.NUMBER)
|
||||
}
|
||||
@ -85,7 +84,7 @@ private inline fun numberDescriptor(
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.numberProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Number?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
||||
@ -96,7 +95,7 @@ public fun <D : Device> DeviceSpec<D>.numberProperty(
|
||||
)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Double?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
||||
@ -107,7 +106,7 @@ public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
||||
)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> String?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
||||
@ -123,7 +122,7 @@ public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||
)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Meta?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
||||
@ -147,7 +146,7 @@ public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||
*/
|
||||
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
||||
mutableProperty(
|
||||
@ -159,7 +158,7 @@ public fun <T, D : DeviceBase<D>> DeviceSpec<D>.mutableProperty(
|
||||
)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.mutableBooleanProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Boolean?,
|
||||
write: suspend D.(propertyName: String, value: Boolean) -> Unit
|
||||
@ -179,7 +178,7 @@ public fun <D : Device> DeviceSpec<D>.mutableBooleanProperty(
|
||||
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.mutableNumberProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Number,
|
||||
write: suspend D.(propertyName: String, value: Number) -> Unit
|
||||
@ -187,7 +186,7 @@ public fun <D : Device> DeviceSpec<D>.mutableNumberProperty(
|
||||
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.mutableDoubleProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Double,
|
||||
write: suspend D.(propertyName: String, value: Double) -> Unit
|
||||
@ -195,7 +194,7 @@ public fun <D : Device> DeviceSpec<D>.mutableDoubleProperty(
|
||||
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.mutableStringProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> String,
|
||||
write: suspend D.(propertyName: String, value: String) -> Unit
|
||||
@ -203,7 +202,7 @@ public fun <D : Device> DeviceSpec<D>.mutableStringProperty(
|
||||
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.mutableMetaProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
descriptorBuilder: PropertyDescriptorBuilder.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Meta,
|
||||
write: suspend D.(propertyName: String, value: Meta) -> Unit
|
||||
|
@ -1,9 +1,9 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.ActionDescriptor
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.ActionDescriptorBuilder
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>){}
|
||||
internal actual fun PropertyDescriptorBuilder.fromSpec(property: KProperty<*>){}
|
||||
|
||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}
|
||||
internal actual fun ActionDescriptorBuilder.fromSpec(property: KProperty<*>){}
|
@ -1,18 +1,18 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.ActionDescriptor
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.ActionDescriptorBuilder
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import space.kscience.dataforge.descriptors.Description
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
|
||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>) {
|
||||
internal actual fun PropertyDescriptorBuilder.fromSpec(property: KProperty<*>) {
|
||||
property.findAnnotation<Description>()?.let {
|
||||
description = it.value
|
||||
}
|
||||
}
|
||||
|
||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){
|
||||
internal actual fun ActionDescriptorBuilder.fromSpec(property: KProperty<*>){
|
||||
property.findAnnotation<Description>()?.let {
|
||||
description = it.value
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.ActionDescriptor
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.ActionDescriptorBuilder
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>) {}
|
||||
internal actual fun PropertyDescriptorBuilder.fromSpec(property: KProperty<*>) {}
|
||||
|
||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}
|
||||
internal actual fun ActionDescriptorBuilder.fromSpec(property: KProperty<*>){}
|
@ -1,9 +1,9 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.ActionDescriptor
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.ActionDescriptorBuilder
|
||||
import space.kscience.controls.api.PropertyDescriptorBuilder
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>){}
|
||||
internal actual fun PropertyDescriptorBuilder.fromSpec(property: KProperty<*>){}
|
||||
|
||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}
|
||||
internal actual fun ActionDescriptorBuilder.fromSpec(property: KProperty<*>){}
|
@ -2,7 +2,6 @@ package space.kscience.controls.demo
|
||||
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.metaDescriptor
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Factory
|
||||
|
Loading…
Reference in New Issue
Block a user