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 visionforgeVersion by extra("0.4.0")
val visionforgeVersion by extra("0.4.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"
version = "0.3.1-dev-1"
repositories{
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 kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.time.Duration
@ -55,17 +54,17 @@ public abstract class DeviceConstructor(
/**
* Register a property and provide a direct reader for it
*/
public fun <T : Any> property(
state: DeviceState<T>,
public fun <T : Any, S: DeviceState<T>> property(
state: S,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> =
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, S>> =
PropertyDelegateProvider { _: DeviceConstructor, property ->
val name = nameOverride ?: property.name
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
registerProperty(descriptor, state)
ReadOnlyProperty { _: DeviceConstructor, _ ->
state.value
state
}
}
@ -79,37 +78,14 @@ public abstract class DeviceConstructor(
initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, T>> = property(
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, DeviceState<T>>> = property(
DeviceState.external(this, metaConverter, readInterval, initialState, reader),
descriptorBuilder,
nameOverride,
)
/**
* Register a mutable property and provide a direct reader for it
*/
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
* Register a mutable external state as a property
*/
public fun <T : Any> mutableProperty(
metaConverter: MetaConverter<T>,
@ -119,22 +95,22 @@ public abstract class DeviceConstructor(
initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> = mutableProperty(
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer),
descriptorBuilder,
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>,
initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
callback: (T) -> Unit = {},
): PropertyDelegateProvider<DeviceConstructor, ReadWriteProperty<DeviceConstructor, T>> = mutableProperty(
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
DeviceState.virtual(metaConverter, initialState, callback),
descriptorBuilder,
nameOverride,

View File

@ -1,6 +1,7 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -11,6 +12,7 @@ import space.kscience.controls.spec.MutableDevicePropertySpec
import space.kscience.controls.spec.name
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter
import kotlin.reflect.KProperty
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 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
@ -37,6 +46,10 @@ public interface MutableDeviceState<T> : DeviceState<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
get() = converter.convert(value)
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(
scope: CoroutineScope,
converter: MetaConverter<T>,

View File

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

View File

@ -2,13 +2,8 @@
package space.kscience.controls.vision
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
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.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock
@ -22,6 +17,7 @@ import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.name
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.plotly.Plot
import space.kscience.plotly.bar
import space.kscience.plotly.models.Bar
@ -183,4 +179,61 @@ public fun Plot.plotBooleanState(
configuration: Bar.() -> Unit = {},
): Job = bar(configuration).run {
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
import kotlinx.serialization.modules.SerializersModule
import org.w3c.dom.Element
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag
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.html.ElementVisionRenderer
public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer {
public actual class ControlVisionPlugin : VisionPlugin() {
override val tag: PluginTag get() = Companion.tag
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> {
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 getEnd ()Lspace/kscience/controls/constructor/LimitSwitch;
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 getTarget ()D
public final fun setTarget (D)V

View File

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

View File

@ -52,8 +52,9 @@ class LinearDrive(
val end by device(LimitSwitch.factory(state.atEndState))
val position by property(state)
var target by mutableProperty(pid.mutablePropertyAsState(Regulator.target, 0.0))
val positionState: DoubleRangeState by property(state)
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.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()
js()
native()
useSerialization()
commonMain {
api(projects.magix.magixApi)
api("space.kscience:dataforge-meta:$dataforgeVersion")