diff --git a/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt index 5471bb2e..f4558c23 100644 --- a/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt +++ b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/simpleCube.kt @@ -1,12 +1,13 @@ -package ru.mipt.npm.sat +package hep.dataforge.vision.solid +import hep.dataforge.meta.DFExperimental import hep.dataforge.vision.ResourceLocation import hep.dataforge.vision.VisionManager import hep.dataforge.vision.html.fragment -import hep.dataforge.vision.solid.box import hep.dataforge.vision.three.server.makeFile import hep.dataforge.vision.three.server.solid +@OptIn(DFExperimental::class) fun main() { val fragment = VisionManager.fragment { vision("canvas") { diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt index eb0bb8b9..5afcedac 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt @@ -117,5 +117,4 @@ public object TreeStyles : StyleSheet("treeStyles", true) { width = 100.pct } } - } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Colors.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Colors.kt index 7dc41632..9c76fd81 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Colors.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Colors.kt @@ -5,7 +5,6 @@ import hep.dataforge.meta.get import hep.dataforge.meta.number import hep.dataforge.values.ValueType import hep.dataforge.values.int -import hep.dataforge.values.string import kotlin.math.max /** diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt index c800024f..ca7396a7 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt @@ -6,8 +6,6 @@ import hep.dataforge.names.plus import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.serialization.* import kotlin.time.Duration @@ -15,13 +13,16 @@ import kotlin.time.Duration * An update for a [Vision] or a [VisionGroup] */ public class VisionChangeBuilder : VisionContainerBuilder { + private val propertyChange = HashMap() private val childrenChange = HashMap() public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty() public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) { - propertyChange.getOrPut(visionName) { Config() }.setItem(propertyName, item) + propertyChange + .getOrPut(visionName) { Config() } + .setItem(propertyName, item) } override fun set(name: Name, child: Vision?) { @@ -32,7 +33,7 @@ public class VisionChangeBuilder : VisionContainerBuilder { * Isolate collected changes by creating detached copies of given visions */ public fun isolate(manager: VisionManager): VisionChange = VisionChange( - propertyChange, + propertyChange.mapValues { it.value.seal() }, childrenChange.mapValues { it.value?.isolate(manager) } ) //TODO optimize isolation for visions without parents? @@ -64,42 +65,40 @@ public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilde private fun CoroutineScope.collectChange( name: Name, source: Vision, - mutex: Mutex, collector: () -> VisionChangeBuilder, ) { + //Collect properties change - source.config.onChange(mutex) { propertyName, oldItem, newItem -> + source.config.onChange(this) { propertyName, oldItem, newItem -> if (oldItem != newItem) { launch { - mutex.withLock { - collector().propertyChanged(name, propertyName, newItem) - } + collector().propertyChanged(name, propertyName, newItem) } } } coroutineContext[Job]?.invokeOnCompletion { - source.config.removeListener(mutex) + source.config.removeListener(this) } if (source is VisionGroup) { //Subscribe for children changes source.children.forEach { (token, child) -> - collectChange(name + token, child, mutex, collector) + collectChange(name + token, child, collector) } //Subscribe for structure change if (source is MutableVisionGroup) { - source.onStructureChange(mutex) { token, before, after -> - before?.removeChangeListener(mutex) - (before as? MutableVisionGroup)?.removeStructureChangeListener(mutex) + source.onStructureChange(this) { token, before, after -> + before?.removeChangeListener(this) + (before as? MutableVisionGroup)?.removeStructureChangeListener(this) if (after != null) { - collectChange(name + token, after, mutex, collector) + collectChange(name + token, after, collector) } collector()[name + token] = after } coroutineContext[Job]?.invokeOnCompletion { - source.removeStructureChangeListener(mutex) + source.removeStructureChangeListener(this) } } } @@ -110,10 +109,9 @@ public fun Vision.flowChanges( manager: VisionManager, collectionDuration: Duration, ): Flow = flow { - val mutex = Mutex() var collector = VisionChangeBuilder() - manager.context.collectChange(Name.EMPTY, this@flowChanges, mutex) { collector } + manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector } while (currentCoroutineContext().isActive) { //Wait for changes to accumulate @@ -121,11 +119,9 @@ public fun Vision.flowChanges( //Propagate updates only if something is changed if (!collector.isEmpty()) { //emit changes - mutex.withLock { - emit(collector.isolate(manager)) - //Reset the collector - collector = VisionChangeBuilder() - } + emit(collector.isolate(manager)) + //Reset the collector + collector = VisionChangeBuilder() } } } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt index 594011af..101bef5b 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/VisionTagConsumer.kt @@ -97,8 +97,8 @@ public abstract class VisionTagConsumer( public const val OUTPUT_META_CLASS: String = "visionforge-output-meta" public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data" - public const val OUTPUT_FETCH_VISION_ATTRIBUTE: String = "data-output-fetch-vision" - public const val OUTPUT_FETCH_UPDATE_ATTRIBUTE: String = "data-output-fetch-update" + public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch" + public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect" public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name" public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint" diff --git a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt index 72cf61f7..8dda1ebf 100644 --- a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt @@ -7,8 +7,9 @@ import hep.dataforge.vision.Vision import hep.dataforge.vision.VisionChange import hep.dataforge.vision.VisionManager import hep.dataforge.vision.html.VisionTagConsumer +import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE -import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_FETCH_UPDATE_ATTRIBUTE +import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE import kotlinx.browser.document import kotlinx.browser.window @@ -23,6 +24,8 @@ public class VisionClient : AbstractPlugin() { override val tag: PluginTag get() = Companion.tag private val visionManager: VisionManager by require(VisionManager) + private val visionMap = HashMap() + /** * Up-going tree traversal in search for endpoint attribute */ @@ -39,19 +42,27 @@ public class VisionClient : AbstractPlugin() { private fun getRenderers() = context.gather(ElementVisionRenderer.TYPE).values - public fun findRendererFor(vision: Vision): ElementVisionRenderer? = + private fun findRendererFor(vision: Vision): ElementVisionRenderer? = getRenderers().maxByOrNull { it.rateVision(vision) } private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML - private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value == "true" + private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null + + private fun renderVision(element: Element, vision: Vision?, outputMeta: Meta) { + if (vision != null) { + visionMap[element] = vision + val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") + renderer.render(element, vision, outputMeta) + } + } /** * Fetch from server and render a vision, described in a given with [VisionTagConsumer.OUTPUT_CLASS] class. */ public fun renderVisionAt(element: Element) { val name = resolveName(element) ?: error("The element is not a vision output") - console.info("Found DF output with name $name") + logger.info { "Found DF output with name $name" } if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element") @@ -63,66 +74,72 @@ public class VisionClient : AbstractPlugin() { val embeddedVision = element.getEmbeddedData(VisionTagConsumer.OUTPUT_DATA_CLASS)?.let { visionManager.decodeFromString(it) } + if (embeddedVision != null) { - val renderer = findRendererFor(embeddedVision) ?: error("Could nof find renderer for $embeddedVision") - renderer.render(element, embeddedVision, outputMeta) + logger.info { "Found embedded vision for output with name $name" } + renderVision(element, embeddedVision, outputMeta) } - if(element.getFlag(VisionTagConsumer.OUTPUT_FETCH_VISION_ATTRIBUTE)) { + val endpoint = resolveEndpoint(element) + logger.info { "Vision server is resolved to $endpoint" } - val endpoint = resolveEndpoint(element) - console.info("Vision server is resolved to $endpoint") + element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { val fetchUrl = URL(endpoint).apply { searchParams.append("name", name) pathname += "/vision" } - console.info("Fetching vision data from $fetchUrl") + logger.info { "Fetching vision data from $fetchUrl" } window.fetch(fetchUrl).then { response -> if (response.ok) { response.text().then { text -> val vision = visionManager.decodeFromString(text) - val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") - renderer.render(element, vision, outputMeta) - if (element.getFlag(OUTPUT_FETCH_UPDATE_ATTRIBUTE)) { - val wsUrl = URL(endpoint).apply { - pathname += "/ws" - protocol = "ws" - searchParams.append("name", name) - } - WebSocket(wsUrl.toString()).apply { - onmessage = { messageEvent -> - val stringData: String? = messageEvent.data as? String - if (stringData != null) { - val dif = visionManager.jsonFormat.decodeFromString( - VisionChange.serializer(), - stringData - ) - vision.update(dif) - } else { - console.error("WebSocket message data is not a string") - } - } - onopen = { - console.info("WebSocket update channel established for output '$name'") - } - onclose = { - console.info("WebSocket update channel closed for output '$name'") - } - onerror = { - console.error("WebSocket update channel error for output '$name'") - } - } - - } + renderVision(element, vision, outputMeta) } } else { - console.error("Failed to fetch initial vision state from $endpoint") + logger.error { "Failed to fetch initial vision state from $endpoint" } } } } + + element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { + + val wsUrl = URL(endpoint).apply { + pathname += "/ws" + protocol = "ws" + searchParams.append("name", name) + } + + logger.info { "Updating vision data from $wsUrl" } + + val ws = WebSocket(wsUrl.toString()).apply { + onmessage = { messageEvent -> + val stringData: String? = messageEvent.data as? String + if (stringData != null) { + val dif = visionManager.jsonFormat.decodeFromString( + VisionChange.serializer(), + stringData + ) + logger.debug { "Got update $dif for output with name $name" } + visionMap[element]?.update(dif) + ?: logger.info { "Target vision for element $element with name $name not found" } + } else { + console.error("WebSocket message data is not a string") + } + } + onopen = { + console.info("WebSocket update channel established for output '$name'") + } + onclose = { + console.info("WebSocket update channel closed for output '$name'") + } + onerror = { + console.error("WebSocket update channel error for output '$name'") + } + } + } } public companion object : PluginFactory { diff --git a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt index 72a80143..22e327a5 100644 --- a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt +++ b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt @@ -25,37 +25,4 @@ public interface ElementVisionRenderer { public const val ZERO_RATING: Int = 0 public const val DEFAULT_RATING: Int = 10 } -} -// -//@DFExperimental -//public fun Map.bind(rendererFactory: (Vision) -> ElementVisionRenderer) { -// forEach { (id, vision) -> -// val element = document.getElementById(id) ?: error("Could not find element with id $id") -// rendererFactory(vision).render(element, vision) -// } -//} -// -//@DFExperimental -//public fun Element.renderAllVisions( -// visionProvider: (Name) -> Vision, -// rendererFactory: (Vision) -> ElementVisionRenderer, -//) { -// val elements = getElementsByClassName(VisionTagConsumer.OUTPUT_CLASS) -// elements.asList().forEach { element -> -// val name = element.attributes[VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE]?.value -// if (name == null) { -// console.error("Attribute ${VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE} not defined in the output element") -// return@forEach -// } -// val vision = visionProvider(name.toName()) -// rendererFactory(vision).render(element, vision) -// } -//} -// -//@DFExperimental -//public fun Document.renderAllVisions( -// visionProvider: (Name) -> Vision, -// rendererFactory: (Vision) -> ElementVisionRenderer, -//): Unit { -// documentElement?.renderAllVisions(visionProvider, rendererFactory) -//} +} \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ColorValueChooser.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ColorValueChooser.kt index 3487f95c..5f4e2573 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ColorValueChooser.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ColorValueChooser.kt @@ -6,7 +6,6 @@ import hep.dataforge.names.asName import hep.dataforge.values.Null import hep.dataforge.values.Value import hep.dataforge.values.asValue -import hep.dataforge.values.string import javafx.scene.control.ColorPicker import javafx.scene.paint.Color import org.slf4j.LoggerFactory diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ComboBoxValueChooser.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ComboBoxValueChooser.kt index 5ce3d960..051469c7 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ComboBoxValueChooser.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/ComboBoxValueChooser.kt @@ -12,7 +12,6 @@ import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.values.Value import hep.dataforge.values.parseValue -import hep.dataforge.values.string import javafx.collections.FXCollections import javafx.scene.control.ComboBox import javafx.util.StringConverter diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/MetaViewer.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/MetaViewer.kt index 88079731..ca6edcc2 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/MetaViewer.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/MetaViewer.kt @@ -17,7 +17,6 @@ package hep.dataforge.vision.editor import hep.dataforge.meta.Meta -import hep.dataforge.values.string import hep.dataforge.vision.dfIconView import javafx.beans.property.SimpleStringProperty import javafx.scene.control.TreeItem diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXMaterials.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXMaterials.kt index b42cd315..a8a62999 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXMaterials.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXMaterials.kt @@ -6,7 +6,6 @@ import hep.dataforge.meta.get import hep.dataforge.meta.int import hep.dataforge.values.ValueType import hep.dataforge.values.int -import hep.dataforge.values.string import hep.dataforge.vision.Colors import javafx.scene.paint.Color import javafx.scene.paint.Material diff --git a/visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt b/visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt index 680f0a37..eaa38618 100644 --- a/visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt @@ -74,8 +74,8 @@ public class VisionServer internal constructor( visionMap[name] = vision // Toggle updates - attributes[OUTPUT_FETCH_VISION_ATTRIBUTE] = "true" - attributes[OUTPUT_FETCH_UPDATE_ATTRIBUTE] = "true" + attributes[OUTPUT_FETCH_ATTRIBUTE] = "true" + attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true" } } diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt index f7ec681d..16d24525 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt @@ -3,7 +3,6 @@ package hep.dataforge.vision.solid.three import hep.dataforge.meta.* import hep.dataforge.values.ValueType import hep.dataforge.values.int -import hep.dataforge.values.string import hep.dataforge.vision.Colors import hep.dataforge.vision.Vision import hep.dataforge.vision.solid.SolidMaterial