diff --git a/build.gradle.kts b/build.gradle.kts index 6c169fe..aebfc4f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,14 +6,14 @@ plugins { } val dataforgeVersion: String by extra("0.7.1") -val visionforgeVersion by extra("0.3.0-RC") +val visionforgeVersion by extra("0.3.1") val ktorVersion: String by extra(space.kscience.gradle.KScienceVersions.ktorVersion) val rsocketVersion by extra("0.15.4") val xodusVersion by extra("2.0.1") allprojects { group = "space.kscience" - version = "0.3.0-dev-5" + version = "0.3.0-dev-6" repositories{ maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") } diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt index 1eb3b91..5dbbe13 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt @@ -57,8 +57,8 @@ public abstract class DeviceConstructor( */ public fun property( state: DeviceState, - nameOverride: String? = null, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + nameOverride: String? = null, ): PropertyDelegateProvider> = PropertyDelegateProvider { _: DeviceConstructor, property -> val name = nameOverride ?: property.name @@ -77,11 +77,12 @@ public abstract class DeviceConstructor( reader: suspend () -> T, readInterval: Duration, initialState: T, - nameOverride: String? = null, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + nameOverride: String? = null, ): PropertyDelegateProvider> = property( DeviceState.external(this, metaConverter, readInterval, initialState, reader), - nameOverride, descriptorBuilder + descriptorBuilder, + nameOverride, ) @@ -90,8 +91,8 @@ public abstract class DeviceConstructor( */ public fun mutableProperty( state: MutableDeviceState, - nameOverride: String? = null, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + nameOverride: String? = null, ): PropertyDelegateProvider> = PropertyDelegateProvider { _: DeviceConstructor, property -> val name = nameOverride ?: property.name @@ -116,11 +117,26 @@ public abstract class DeviceConstructor( writer: suspend (T) -> Unit, readInterval: Duration, initialState: T, - nameOverride: String? = null, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + nameOverride: String? = null, ): PropertyDelegateProvider> = mutableProperty( DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer), + descriptorBuilder, + nameOverride, + ) + + /** + * Create and register a virtual property with optional [callback] + */ + public fun state( + metaConverter: MetaConverter, + initialState: T, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + nameOverride: String? = null, + callback: (T) -> Unit = {}, + ): PropertyDelegateProvider> = mutableProperty( + DeviceState.virtual(metaConverter, initialState, callback), + descriptorBuilder, nameOverride, - descriptorBuilder ) } \ No newline at end of file diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt index cea9d71..26b5896 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceGroup.kt @@ -47,9 +47,10 @@ public open class DeviceGroup( override val messageFlow: Flow get() = sharedMessageFlow + @OptIn(ExperimentalCoroutinesApi::class) override val coroutineContext: CoroutineContext = context.newCoroutineContext( SupervisorJob(context.coroutineContext[Job]) + - CoroutineName("Device $this") + + CoroutineName("Device $id") + CoroutineExceptionHandler { _, throwable -> context.launch { sharedMessageFlow.emit( @@ -75,7 +76,7 @@ public open class DeviceGroup( public fun install(token: NameToken, device: D): D { require(_devices[token] == null) { "A child device with name $token already exists" } //start the child device if needed - if(lifecycleState == STARTED || lifecycleState == STARTING) launch { device.start() } + if (lifecycleState == STARTED || lifecycleState == STARTING) launch { device.start() } _devices[token] = device return device } @@ -216,6 +217,9 @@ public fun DeviceGroup.install(name: Name, device: D): D { public fun DeviceGroup.install(name: String, device: D): D = install(name.parseAsName(), device) +public fun DeviceGroup.install(device: D): D = + install(device.id, device) + public fun Context.install(name: String, device: D): D = request(DeviceManager).install(name, device) /** @@ -281,15 +285,6 @@ public fun DeviceGroup.registerMutableProperty( } -/** - * Create a virtual [MutableDeviceState], but do not register it to a device - */ -@Suppress("UnusedReceiverParameter") -public fun DeviceGroup.state( - converter: MetaConverter, - initialValue: T, -): MutableDeviceState = VirtualDeviceState(converter, initialValue) - /** * Create a new virtual mutable state and a property based on it. * @return the mutable state used in property @@ -299,8 +294,9 @@ public fun DeviceGroup.registerVirtualProperty( initialValue: T, converter: MetaConverter, descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + callback: (T) -> Unit = {}, ): MutableDeviceState { - val state = state(converter, initialValue) + val state = DeviceState.virtual(converter, initialValue, callback) registerMutableProperty(name, state, descriptorBuilder) return state } diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt index a9cff51..672b0a6 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceState.kt @@ -45,17 +45,49 @@ public var MutableDeviceState.valueAsMeta: Meta /** * A [MutableDeviceState] that does not correspond to a physical state + * + * @param callback a synchronous callback that could be used without a scope */ -public class VirtualDeviceState( +private class VirtualDeviceState( override val converter: MetaConverter, initialValue: T, + private val callback: (T) -> Unit = {}, ) : MutableDeviceState { private val flow = MutableStateFlow(initialValue) override val valueFlow: Flow get() = flow - override var value: T by flow::value + override var value: T + get() = flow.value + set(value) { + flow.value = value + callback(value) + } } + +/** + * A [MutableDeviceState] that does not correspond to a physical state + * + * @param callback a synchronous callback that could be used without a scope + */ +public fun DeviceState.Companion.virtual( + converter: MetaConverter, + initialValue: T, + callback: (T) -> Unit = {}, +): MutableDeviceState = VirtualDeviceState(converter, initialValue, callback) + +private class StateFlowAsState( + override val converter: MetaConverter, + val flow: MutableStateFlow, +) : MutableDeviceState { + override var value: T by flow::value + override val valueFlow: Flow get() = flow +} + +public fun MutableStateFlow.asDeviceState(converter: MetaConverter): DeviceState = + StateFlowAsState(converter, this) + + private open class BoundDeviceState( override val converter: MetaConverter, val device: Device, diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt index 4b78f24..89040c0 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt @@ -10,6 +10,8 @@ import space.kscience.dataforge.context.ContextAware import space.kscience.dataforge.context.info import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.string import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DfType import space.kscience.dataforge.names.parseAsName @@ -111,6 +113,11 @@ public interface Device : ContextAware, CoroutineScope { } } +/** + * Inner id of a device. Not necessary corresponds to the name in the parent container + */ +public val Device.id: String get() = meta["id"].string?: "device[${hashCode().toString(16)}]" + /** * Device that caches properties values */ diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt index be59b4c..c8c7ffc 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.launch import space.kscience.controls.api.Device import space.kscience.controls.api.DeviceHub import space.kscience.controls.api.getOrNull +import space.kscience.controls.api.id import space.kscience.dataforge.context.* import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MutableMeta @@ -45,6 +46,8 @@ public fun DeviceManager.install(name: String, device: D): D { return device } +public fun DeviceManager.install(device: D): D = install(device.id, device) + /** * Register and start a device built by [factory] with current [Context] and [meta].