From cf6d73305bf372fe730e3dc66e91faf5110da5e9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 18 Oct 2023 13:52:42 +0300 Subject: [PATCH] Add vision client-side events --- ...VisionChangeBuilder.kt => VisionChange.kt} | 38 +++++++++++++++++++ .../space/kscience/visionforge/VisionEvent.kt | 37 +++--------------- .../kscience/visionforge/VisionClient.kt | 15 ++++---- .../visionforge/server/VisionServer.kt | 4 +- 4 files changed, 53 insertions(+), 41 deletions(-) rename visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/{VisionChangeBuilder.kt => VisionChange.kt} (83%) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChangeBuilder.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt similarity index 83% rename from visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChangeBuilder.kt rename to visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt index 5b143023..9915c035 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChangeBuilder.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt @@ -9,12 +9,34 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.* +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.plus import kotlin.time.Duration + + +/** + * A vision used only in change propagation and showing that the target should be removed + */ +@Serializable +@SerialName("null") +public object NullVision : Vision { + override var parent: Vision? + get() = null + set(_) { + error("Can't set parent for null vision") + } + + override val properties: MutableVisionProperties get() = error("Can't get properties of `NullVision`") + + override val descriptor: MetaDescriptor? = null +} + /** * Create a deep copy of given Vision without external connections. */ @@ -28,6 +50,22 @@ private fun Vision.deepCopy(manager: VisionManager): Vision { } +/** + * An event that contains changes made to a vision. + * + * @param vision a new value for vision content. If the Vision is to be removed should be [NullVision] + * @param properties updated properties + * @param children a map of children changed in ths [VisionChange]. + */ +@Serializable +@SerialName("change") +public data class VisionChange( + public val vision: Vision? = null, + public val properties: Meta? = null, + public val children: Map? = null, +) + + /** * An update for a [Vision] */ diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionEvent.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionEvent.kt index 3f86a2c0..6b85156a 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionEvent.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionEvent.kt @@ -3,50 +3,23 @@ package space.kscience.visionforge import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.names.Name /** * An event propagated from client to a server */ @Serializable -public sealed interface VisionEvent +public sealed interface VisionEvent{ + public val targetName: Name +} /** * An event that consists of custom meta */ @Serializable @SerialName("meta") -public class VisionMetaEvent(public val targetName: Name, public val meta: Meta) : VisionEvent +public class VisionMetaEvent(override val targetName: Name, public val meta: Meta) : VisionEvent - -/** - * A vision used only in change propagation and showing that the target should be removed - */ -@Serializable -@SerialName("null") -public object NullVision : Vision { - override var parent: Vision? - get() = null - set(_) { - error("Can't set parent for null vision") - } - - override val properties: MutableVisionProperties get() = error("Can't get properties of `NullVision`") - - override val descriptor: MetaDescriptor? = null -} - - -/** - * @param vision a new value for vision content. If the Vision is to be removed should be [NullVision] - * @param properties updated properties - * @param children a map of children changed in ths [VisionChange]. - */ @Serializable @SerialName("change") -public data class VisionChange( - public val vision: Vision? = null, - public val properties: Meta? = null, - public val children: Map? = null, -) : VisionEvent \ No newline at end of file +public class VisionChangeEvent(override val targetName: Name, public val change: VisionChange) : VisionEvent \ No newline at end of file diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt index 91e4f1bc..81493a6e 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -5,6 +5,9 @@ import kotlinx.browser.window import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex @@ -141,21 +144,19 @@ public class VisionClient : AbstractPlugin() { onopen = { feedbackJob = visionManager.context.launch { + eventCollector.filter { it.targetName == name }.onEach { + send(visionManager.jsonFormat.encodeToString(VisionEvent.serializer(), it)) + }.launchIn(this) + while (isActive) { delay(feedbackAggregationTime.milliseconds) val change = changeCollector[name] ?: continue if (!change.isEmpty()) { mutex.withLock { - send(change.toJsonString(visionManager)) + eventCollector.emit(VisionChangeEvent(name, change.deepCopy(visionManager))) change.reset() } } -// // take channel for given vision name -// eventCollector[name]?.let { channel -> -// for (e in channel) { -// send(visionManager.jsonFormat.encodeToString(VisionEvent.serializer(), e)) -// } -// } } } logger.info { "WebSocket feedback channel established for output '$name'" } diff --git a/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt b/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt index 873a52c9..cf2f068d 100644 --- a/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt @@ -75,8 +75,8 @@ public class VisionRoute( public fun Application.serveVisionData( configuration: VisionRoute, onEvent: suspend Vision.(VisionEvent) -> Unit = { event -> - if (event is VisionChange) { - update(event) + if (event is VisionChangeEvent) { + update(event.change) } }, resolveVision: (Name) -> Vision?,