Update constructor api

This commit is contained in:
Alexander Nozik 2024-03-18 09:30:41 +03:00
parent 29af4dfb2c
commit f28e9dc226
11 changed files with 98 additions and 63 deletions

View File

@ -6,14 +6,14 @@ plugins {
} }
val dataforgeVersion: String by extra("0.8.0") val dataforgeVersion: String by extra("0.8.0")
val visionforgeVersion by extra("0.4.0") val visionforgeVersion by extra("0.4.1")
val ktorVersion: String by extra(space.kscience.gradle.KScienceVersions.ktorVersion) val ktorVersion: String by extra(space.kscience.gradle.KScienceVersions.ktorVersion)
val rsocketVersion by extra("0.15.4") val rsocketVersion by extra("0.15.4")
val xodusVersion by extra("2.0.1") val xodusVersion by extra("2.0.1")
allprojects { allprojects {
group = "space.kscience" group = "space.kscience"
version = "0.3.0" version = "0.3.1-dev-1"
repositories{ repositories{
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
} }

View File

@ -10,7 +10,6 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import kotlin.properties.PropertyDelegateProvider import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.time.Duration import kotlin.time.Duration
@ -55,17 +54,17 @@ public abstract class DeviceConstructor(
/** /**
* Register a property and provide a direct reader for it * Register a property and provide a direct reader for it
*/ */
public fun <T : Any> property( public fun <T : Any, S: DeviceState<T>> property(
state: DeviceState<T>, state: S,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null, nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = ): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, S>> =
PropertyDelegateProvider { _: DeviceConstructor, property -> PropertyDelegateProvider { _: DeviceConstructor, property ->
val name = nameOverride ?: property.name val name = nameOverride ?: property.name
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder) val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
registerProperty(descriptor, state) registerProperty(descriptor, state)
ReadOnlyProperty { _: DeviceConstructor, _ -> ReadOnlyProperty { _: DeviceConstructor, _ ->
state.value state
} }
} }
@ -79,37 +78,14 @@ public abstract class DeviceConstructor(
initialState: T, initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null, nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = property( ): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, DeviceState<T>>> = property(
DeviceState.external(this, metaConverter, readInterval, initialState, reader), DeviceState.external(this, metaConverter, readInterval, initialState, reader),
descriptorBuilder, descriptorBuilder,
nameOverride, nameOverride,
) )
/** /**
* Register a mutable property and provide a direct reader for it * Register a mutable external state as a property
*/
public fun <T : Any> mutableProperty(
state: MutableDeviceState<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> =
PropertyDelegateProvider { _: DeviceConstructor, property ->
val name = nameOverride ?: property.name
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
registerProperty(descriptor, state)
object : ReadWriteProperty<DeviceConstructor, T> {
override fun getValue(thisRef: DeviceConstructor, property: KProperty<*>): T = state.value
override fun setValue(thisRef: DeviceConstructor, property: KProperty<*>, value: T) {
state.value = value
}
}
}
/**
* Register external state as a property
*/ */
public fun <T : Any> mutableProperty( public fun <T : Any> mutableProperty(
metaConverter: MetaConverter<T>, metaConverter: MetaConverter<T>,
@ -119,22 +95,22 @@ public abstract class DeviceConstructor(
initialState: T, initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null, nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> = mutableProperty( ): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer), DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer),
descriptorBuilder, descriptorBuilder,
nameOverride, nameOverride,
) )
/** /**
* Create and register a virtual property with optional [callback] * Create and register a virtual mutable property with optional [callback]
*/ */
public fun <T : Any> state( public fun <T : Any> virtualProperty(
metaConverter: MetaConverter<T>, metaConverter: MetaConverter<T>,
initialState: T, initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null, nameOverride: String? = null,
callback: (T) -> Unit = {}, callback: (T) -> Unit = {},
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> = mutableProperty( ): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
DeviceState.virtual(metaConverter, initialState, callback), DeviceState.virtual(metaConverter, initialState, callback),
descriptorBuilder, descriptorBuilder,
nameOverride, nameOverride,

View File

@ -1,6 +1,7 @@
package space.kscience.controls.constructor package space.kscience.controls.constructor
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -11,6 +12,7 @@ import space.kscience.controls.spec.MutableDevicePropertySpec
import space.kscience.controls.spec.name import space.kscience.controls.spec.name
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter import space.kscience.dataforge.meta.MetaConverter
import kotlin.reflect.KProperty
import kotlin.time.Duration import kotlin.time.Duration
/** /**
@ -29,6 +31,13 @@ public val <T> DeviceState<T>.metaFlow: Flow<Meta> get() = valueFlow.map(convert
public val <T> DeviceState<T>.valueAsMeta: Meta get() = converter.convert(value) public val <T> DeviceState<T>.valueAsMeta: Meta get() = converter.convert(value)
public operator fun <T> DeviceState<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
/**
* Collect values in a given [scope]
*/
public fun <T> DeviceState<T>.collectValuesIn(scope: CoroutineScope, block: suspend (T)->Unit): Job =
valueFlow.onEach(block).launchIn(scope)
/** /**
* A mutable state of a device * A mutable state of a device
@ -37,6 +46,10 @@ public interface MutableDeviceState<T> : DeviceState<T> {
override var value: T override var value: T
} }
public operator fun <T> MutableDeviceState<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
public var <T : Any> MutableDeviceState<T>.valueAsMeta: Meta public var <T : Any> MutableDeviceState<T>.valueAsMeta: Meta
get() = converter.convert(value) get() = converter.convert(value)
set(arg) { set(arg) {
@ -216,6 +229,9 @@ private class MutableExternalState<T>(
} }
} }
/**
* Create a [DeviceState] that regularly reads and caches an external value
*/
public fun <T> DeviceState.Companion.external( public fun <T> DeviceState.Companion.external(
scope: CoroutineScope, scope: CoroutineScope,
converter: MetaConverter<T>, converter: MetaConverter<T>,

View File

@ -75,7 +75,7 @@ public class DeviceNameSpace(
browseName = newQualifiedName(propertyName) browseName = newQualifiedName(propertyName)
displayName = LocalizedText.english(propertyName) displayName = LocalizedText.english(propertyName)
dataType = if (descriptor.metaDescriptor.children.isNotEmpty()) { dataType = if (descriptor.metaDescriptor.nodes.isNotEmpty()) {
Identifiers.String Identifiers.String
} else when (descriptor.metaDescriptor.valueTypes?.first()) { } else when (descriptor.metaDescriptor.valueTypes?.first()) {
null, ValueType.STRING, ValueType.NULL -> Identifiers.String null, ValueType.STRING, ValueType.NULL -> Identifiers.String

View File

@ -2,13 +2,8 @@
package space.kscience.controls.vision package space.kscience.controls.vision
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
@ -22,6 +17,7 @@ import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.name import space.kscience.controls.spec.name
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.plotly.Plot import space.kscience.plotly.Plot
import space.kscience.plotly.bar import space.kscience.plotly.bar
import space.kscience.plotly.models.Bar import space.kscience.plotly.models.Bar
@ -183,4 +179,61 @@ public fun Plot.plotBooleanState(
configuration: Bar.() -> Unit = {}, configuration: Bar.() -> Unit = {},
): Job = bar(configuration).run { ): Job = bar(configuration).run {
updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints, sampling) updateFromState(context, state, { asValue() }, maxAge, maxPoints, minPoints, sampling)
} }
private fun <T> Flow<T>.chunkedByPeriod(duration: Duration): Flow<List<T>> {
val collector: ArrayDeque<T> = ArrayDeque<T>()
return channelFlow {
coroutineScope {
launch {
while (isActive) {
delay(duration)
send(ArrayList(collector))
collector.clear()
}
}
this@chunkedByPeriod.collect {
collector.add(it)
}
}
}
}
private fun List<Instant>.averageTime(): Instant {
val min = min()
val max = max()
val duration = max - min
return min + duration / 2
}
/**
* Average property value by [averagingInterval]. Return [missingValue] on each sample interval if no events arrived.
*/
@DFExperimental
public fun Plot.plotAveragedDeviceProperty(
device: Device,
propertyName: String,
missingValue: Double = 0.0,
extractValue: Meta.() -> Double = { value?.double ?: missingValue },
maxAge: Duration = defaultMaxAge,
maxPoints: Int = defaultMaxPoints,
minPoints: Int = defaultMinPoints,
averagingInterval: Duration = defaultSampling,
coroutineScope: CoroutineScope = device.context,
configuration: Scatter.() -> Unit = {},
): Job = scatter(configuration).run {
val data = TimeData()
device.propertyMessageFlow(propertyName).chunkedByPeriod(averagingInterval).transform { eventList ->
if(eventList.isEmpty()){
data.append(Clock.System.now(), missingValue.asValue())
} else {
val time = eventList.map { it.time }.averageTime()
val value = eventList.map { extractValue(it.value) }.average()
data.append(time, value.asValue())
}
data.trim(maxAge, maxPoints, minPoints)
emit(data)
}.onEach {
it.fillPlot(x, y)
}.launchIn(coroutineScope)
}

View File

@ -1,29 +1,17 @@
package space.kscience.controls.vision package space.kscience.controls.vision
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import org.w3c.dom.Element
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionPlugin import space.kscience.visionforge.VisionPlugin
import space.kscience.visionforge.html.ElementVisionRenderer
public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer { public actual class ControlVisionPlugin : VisionPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule override val visionSerializersModule: SerializersModule get() = controlsVisionSerializersModule
override fun rateVision(vision: Vision): Int {
TODO("Not yet implemented")
}
override fun render(element: Element, name: Name, vision: Vision, meta: Meta) {
TODO("Not yet implemented")
}
public actual companion object : PluginFactory<ControlVisionPlugin> { public actual companion object : PluginFactory<ControlVisionPlugin> {
override val tag: PluginTag = PluginTag("controls.vision") override val tag: PluginTag = PluginTag("controls.vision")

View File

@ -14,7 +14,7 @@ public final class space/kscience/controls/demo/constructor/LinearDrive : space/
public final fun getDrive ()Lspace/kscience/controls/constructor/Drive; public final fun getDrive ()Lspace/kscience/controls/constructor/Drive;
public final fun getEnd ()Lspace/kscience/controls/constructor/LimitSwitch; public final fun getEnd ()Lspace/kscience/controls/constructor/LimitSwitch;
public final fun getPid ()Lspace/kscience/controls/constructor/PidRegulator; public final fun getPid ()Lspace/kscience/controls/constructor/PidRegulator;
public final fun getPosition ()D public final fun getPositionState ()Lspace/kscience/controls/constructor/DoubleRangeState;
public final fun getStart ()Lspace/kscience/controls/constructor/LimitSwitch; public final fun getStart ()Lspace/kscience/controls/constructor/LimitSwitch;
public final fun getTarget ()D public final fun getTarget ()D
public final fun setTarget (D)V public final fun setTarget (D)V

View File

@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
plugins { plugins {
id("space.kscience.gradle.mpp") id("space.kscience.gradle.mpp")
id("org.jetbrains.compose") version "1.5.11" alias(spclibs.plugins.compose)
} }
kscience { kscience {

View File

@ -52,8 +52,9 @@ class LinearDrive(
val end by device(LimitSwitch.factory(state.atEndState)) val end by device(LimitSwitch.factory(state.atEndState))
val position by property(state) val positionState: DoubleRangeState by property(state)
var target by mutableProperty(pid.mutablePropertyAsState(Regulator.target, 0.0)) private val targetState: MutableDeviceState<Double> by property(pid.mutablePropertyAsState(Regulator.target, 0.0))
var target by targetState
} }

View File

@ -7,4 +7,4 @@ org.gradle.parallel=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.15.2-kotlin-1.9.21 toolsVersion=0.15.2-kotlin-1.9.22

View File

@ -15,6 +15,7 @@ kscience {
jvm() jvm()
js() js()
native() native()
useSerialization()
commonMain { commonMain {
api(projects.magix.magixApi) api(projects.magix.magixApi)
api("space.kscience:dataforge-meta:$dataforgeVersion") api("space.kscience:dataforge-meta:$dataforgeVersion")