From 0259d4eb152a7fdba2cd74bb1f998a0485cb230c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 20 Dec 2020 21:43:29 +0300 Subject: [PATCH] Another huge refactoring of property updates --- .../main/kotlin/ru/mipt/npm/sat/satServer.kt | 2 +- .../vision/solid/demo/VariableBox.kt | 7 +-- .../vision/bootstrap/visionPropertyEditor.kt | 3 +- .../dataforge/vision/react/PropertyEditor.kt | 6 +- .../kotlin/hep/dataforge/vision/StyleSheet.kt | 5 +- .../kotlin/hep/dataforge/vision/Vision.kt | 26 +++++++-- .../kotlin/hep/dataforge/vision/VisionBase.kt | 31 +++++++---- .../hep/dataforge/vision/VisionChange.kt | 7 ++- .../hep/dataforge/vision/VisionGroupBase.kt | 2 +- .../kotlin/hep/dataforge/vision/misc.kt | 12 ---- .../hep/dataforge/vision/valueWidget.kt | 27 --------- .../hep/dataforge/vision/visionDescriptor.kt | 55 +++++++++++++++++-- .../dataforge/vision/client/VisionClient.kt | 48 ++++++++++------ .../vision/solid/FXReferenceFactory.kt | 6 +- .../vision/solid/VisualObjectFXBinding.kt | 6 +- .../vision/three/server/VisionServer.kt | 40 ++++++++++++-- .../hep/dataforge/vision/solid/Solid.kt | 24 ++++---- .../hep/dataforge/vision/solid/SolidGroup.kt | 10 +++- .../dataforge/vision/solid/SolidMaterial.kt | 18 ++++-- .../dataforge/vision/solid/SolidReference.kt | 17 +++--- .../vision/solid/three/MeshThreeFactory.kt | 6 +- .../vision/solid/three/ThreeFactory.kt | 2 +- .../vision/solid/three/ThreeLabelFactory.kt | 6 +- .../vision/solid/three/ThreeLineFactory.kt | 6 +- .../vision/solid/three/ThreeMaterials.kt | 4 ++ .../vision/solid/three/ThreePlugin.kt | 4 +- .../solid/three/ThreeReferenceFactory.kt | 6 +- 27 files changed, 238 insertions(+), 148 deletions(-) delete mode 100644 visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/valueWidget.kt diff --git a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt index bd7abf90..761cd982 100644 --- a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt @@ -42,7 +42,7 @@ fun main() { val targetVision = sat[target] as Solid targetVision.color("red") delay(300) - targetVision.color("green") + targetVision.color("darkgreen") delay(10) } } diff --git a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt index 366acfc4..2910e0d3 100644 --- a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt +++ b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt @@ -16,8 +16,6 @@ import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Object3D import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.objects.Mesh -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlin.math.max internal fun SolidGroup.varBox( @@ -62,7 +60,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV mesh.scale.set(xSize, ySize, zSize) //add listener to object properties - propertyNameFlow.onEach { name -> + onPropertyChange(three.context) { name -> when { name.startsWith(GEOMETRY_KEY) -> { val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0 @@ -78,7 +76,8 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV } else -> mesh.updateProperty(this@VariableBox, name) } - }.launchIn(three.context) + } + return mesh } diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt index 14a93eb8..b9a30bc8 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt +++ b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt @@ -13,11 +13,12 @@ public fun RBuilder.visionPropertyEditor( descriptor: NodeDescriptor? = vision.descriptor, key: Any? = null, ) { + card("Properties") { propertyEditor( provider = vision.ownProperties, defaultProvider = vision.allProperties(), - updateFlow = vision.propertyNameFlow, + updateFlow = vision.propertyChanges, descriptor = descriptor, key = key) } diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt index c730123b..ebd62473 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt @@ -7,6 +7,7 @@ import hep.dataforge.names.NameToken import hep.dataforge.names.lastOrNull import hep.dataforge.names.plus import hep.dataforge.values.Value +import hep.dataforge.vision.hidden import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.awaitClose @@ -66,6 +67,9 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { val itemName by useState { props.name ?: Name.EMPTY } var item: MetaItem<*>? by useState { props.provider.getItem(itemName) } val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName) + + if(descriptorItem?.hidden == true) return //fail fast for hidden property + var actualItem: MetaItem? by useState { item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem() } @@ -106,8 +110,6 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { update() } - - if (actualItem is MetaItem.NodeItem) { styledDiv { css { diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt index 783b376c..d90fe655 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt @@ -5,6 +5,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.asName import hep.dataforge.names.plus +import kotlinx.coroutines.launch /** * A container for styles @@ -54,7 +55,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) val tokens: Collection = ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet())) .map { it.asName() } - tokens.forEach { parent?.notifyPropertyChanged(it) } + parent?.scope?.launch { + tokens.forEach { parent?.notifyPropertyChanged(it) } + } } if (this is VisionGroup) { for (obj in this) { diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt index abf24bc1..ba585b46 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt @@ -9,9 +9,10 @@ import hep.dataforge.names.asName import hep.dataforge.names.toName import hep.dataforge.provider.Type import hep.dataforge.vision.Vision.Companion.TYPE -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.serialization.Transient /** @@ -29,7 +30,7 @@ public interface Vision : Described { /** * A coroutine scope for asynchronous calls and locks */ - public val scope: CoroutineScope get() = parent?.scope?: GlobalScope + public val scope: CoroutineScope get() = parent?.scope ?: GlobalScope /** * A fast accessor method to get own property (no inheritance or styles). @@ -55,17 +56,26 @@ public interface Vision : Described { */ public fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean = true) + public fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) + /** * Flow of property invalidation events. It does not contain property values after invalidation since it is not clear * if it should include inherited properties etc. */ - public val propertyNameFlow: Flow + public val propertyChanges: Flow get() = callbackFlow { + coroutineScope { + onPropertyChange(this) { + send(it) + } + awaitClose { cancel() } + } + } /** * Notify all listeners that a property has been changed and should be invalidated */ - public fun notifyPropertyChanged(propertyName: Name): Unit + public suspend fun notifyPropertyChanged(propertyName: Name): Unit /** * Update this vision using external meta. Children are not updated. @@ -82,6 +92,12 @@ public interface Vision : Described { } } +public fun Vision.asyncNotifyPropertyChange(propertyName: Name){ + scope.launch { + notifyPropertyChanged(propertyName) + } +} + /** * Own properties, excluding inheritance, styles and descriptor */ diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt index 27c9c92e..141366d1 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt @@ -11,8 +11,11 @@ import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.values.ValueType import hep.dataforge.vision.Vision.Companion.STYLE_KEY +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -44,7 +47,9 @@ public open class VisionBase : Vision { properties = newProperties newProperties.onChange(this) { name, oldItem, newItem -> if (oldItem != newItem) { - notifyPropertyChanged(name) + scope.launch { + notifyPropertyChanged(name) + } } } } @@ -77,14 +82,16 @@ public open class VisionBase : Vision { @Synchronized override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) { getOrCreateConfig().setItem(name, item) - if(notify) { - notifyPropertyChanged(name) + if (notify) { + scope.launch { + notifyPropertyChanged(name) + } } } override val descriptor: NodeDescriptor? get() = null - private fun updateStyles(names: List) { + private suspend fun updateStyles(names: List) { names.mapNotNull { getStyle(it) }.asSequence() .flatMap { it.items.asSequence() } .distinctBy { it.key } @@ -94,17 +101,19 @@ public open class VisionBase : Vision { } @Transient - private val _propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow() + private val propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow() - override val propertyNameFlow: SharedFlow get() = _propertyInvalidationFlow + override val propertyChanges: Flow get() = propertyInvalidationFlow - override fun notifyPropertyChanged(propertyName: Name) { + override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) { + propertyInvalidationFlow.onEach(callback).launchIn(scope) + } + + override suspend fun notifyPropertyChanged(propertyName: Name) { if (propertyName == STYLE_KEY) { updateStyles(styles) } - scope.launch { - _propertyInvalidationFlow.emit(propertyName) - } + propertyInvalidationFlow.emit(propertyName) } public fun configure(block: MutableMeta<*>.() -> Unit) { 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 c2717108..7dddd655 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt @@ -67,6 +67,9 @@ public class VisionChange( @Serializable(MetaSerializer::class) public val properties: Meta? = null, public val children: Map? = null, ) { + init { + (vision as? VisionGroup)?.attachChildren() + } } @@ -81,10 +84,10 @@ private fun CoroutineScope.collectChange( ) { //Collect properties change - source.propertyNameFlow.onEach { propertyName -> + source.onPropertyChange(this) { propertyName -> val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false) collector().propertyChanged(name, propertyName, newItem) - }.launchIn(this) + } if (source is VisionGroup) { //Subscribe for children changes diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroupBase.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroupBase.kt index 2d4f271f..0752b98b 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroupBase.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroupBase.kt @@ -28,7 +28,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup { */ override val children: Map get() = childrenInternal - override fun notifyPropertyChanged(propertyName: Name) { + override suspend fun notifyPropertyChanged(propertyName: Name) { super.notifyPropertyChanged(propertyName) for (obj in this) { obj.notifyPropertyChanged(propertyName) diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt index 75cb8f40..ef1882fe 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt @@ -1,15 +1,11 @@ package hep.dataforge.vision import hep.dataforge.meta.* -import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.names.Name -import hep.dataforge.values.ValueType import hep.dataforge.values.asValue @DslMarker public annotation class VisionBuilder - public fun Sequence?>.merge(): MetaItem<*>? { return when (val first = firstOrNull { it != null }) { null -> null @@ -22,14 +18,6 @@ public fun Sequence?>.merge(): MetaItem<*>? { } } -public inline fun > NodeDescriptor.enum(key: Name, default: E?): Unit = value(key) { - type(ValueType.STRING) - default?.let { - default(default) - } - allowedValues = enumValues().map { it.asValue() } -} - @DFExperimental public val Vision.properties: Config? get() = (this as? VisionBase)?.properties diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/valueWidget.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/valueWidget.kt deleted file mode 100644 index f563d87c..00000000 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/valueWidget.kt +++ /dev/null @@ -1,27 +0,0 @@ -package hep.dataforge.vision - -import hep.dataforge.meta.* -import hep.dataforge.meta.descriptors.ValueDescriptor -import hep.dataforge.meta.descriptors.attributes - -/** - * Extension property to access the "widget" key of [ValueDescriptor] - */ -public var ValueDescriptor.widget: Meta - get() = attributes["widget"].node ?: Meta.EMPTY - set(value) { - attributes { - set("widget", value) - } - } - -/** - * Extension property to access the "widget.type" key of [ValueDescriptor] - */ -public var ValueDescriptor.widgetType: String? - get() = attributes["widget.type"].string - set(value) { - attributes{ - set("widget.type", value) - } - } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt index 0e068827..59001d15 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt @@ -1,11 +1,13 @@ package hep.dataforge.vision -import hep.dataforge.meta.Meta -import hep.dataforge.meta.boolean +import hep.dataforge.meta.* import hep.dataforge.meta.descriptors.ItemDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.meta.descriptors.attributes -import hep.dataforge.meta.get -import hep.dataforge.meta.set +import hep.dataforge.names.Name +import hep.dataforge.values.ValueType +import hep.dataforge.values.asValue private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited" private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles" @@ -30,3 +32,48 @@ public val Vision.describedProperties: Meta } } +/** + * Extension property to access the "widget" key of [ValueDescriptor] + */ +public var ValueDescriptor.widget: Meta + get() = attributes["widget"].node ?: Meta.EMPTY + set(value) { + attributes { + set("widget", value) + } + } + +/** + * Extension property to access the "widget.type" key of [ValueDescriptor] + */ +public var ValueDescriptor.widgetType: String? + get() = attributes["widget.type"].string + set(value) { + attributes { + set("widget.type", value) + } + } + +/** + * If true, this item is hidden in property editor. Default is false + */ +public val ItemDescriptor.hidden: Boolean + get() = attributes["widget.hide"].boolean ?: false + +public fun ItemDescriptor.hide(): Unit = attributes { + set("widget.hide", true) +} + + +public inline fun > NodeDescriptor.enum( + key: Name, + default: E?, + crossinline modifier: ValueDescriptor.() -> Unit = {}, +): Unit = value(key) { + type(ValueType.STRING) + default?.let { + default(default) + } + allowedValues = enumValues().map { it.asValue() } + modifier() +} \ No newline at end of file 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 eefffc17..36f0ee1f 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 @@ -18,9 +18,6 @@ import org.w3c.dom.WebSocket import org.w3c.dom.asList import org.w3c.dom.get import org.w3c.dom.url.URL -import kotlin.collections.HashMap -import kotlin.collections.forEach -import kotlin.collections.maxByOrNull import kotlin.collections.set import kotlin.reflect.KClass @@ -84,14 +81,18 @@ public class VisionClient : AbstractPlugin() { renderVision(element, embeddedVision, outputMeta) } - val endpoint = resolveEndpoint(element) - logger.info { "Vision server is resolved to $endpoint" } + element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr -> - element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { - - val fetchUrl = URL(endpoint).apply { + val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") { + val endpoint = resolveEndpoint(element) + logger.info { "Vision server is resolved to $endpoint" } + URL(endpoint).apply { + pathname += "/vision" + } + } else { + URL(attr.value) + }.apply { searchParams.append("name", name) - pathname += "/vision" } logger.info { "Fetching vision data from $fetchUrl" } @@ -102,16 +103,22 @@ public class VisionClient : AbstractPlugin() { renderVision(element, vision, outputMeta) } } else { - logger.error { "Failed to fetch initial vision state from $endpoint" } + logger.error { "Failed to fetch initial vision state from $fetchUrl" } } } } - element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { - - val wsUrl = URL(endpoint).apply { - pathname += "/ws" + element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr -> + val wsUrl = if (attr.value.isBlank() || attr.value == "auto") { + val endpoint = resolveEndpoint(element) + logger.info { "Vision server is resolved to $endpoint" } + URL(endpoint).apply { + pathname += "/ws" + } + } else { + URL(attr.value) + }.apply { protocol = "ws" searchParams.append("name", name) } @@ -122,15 +129,20 @@ public class VisionClient : AbstractPlugin() { onmessage = { messageEvent -> val stringData: String? = messageEvent.data as? String if (stringData != null) { - val dif = visionManager.jsonFormat.decodeFromString( + val change = visionManager.jsonFormat.decodeFromString( VisionChange.serializer(), stringData ) - logger.debug { "Got update $dif for output with name $name" } - visionMap[element]?.update(dif) + + if(change.vision!= null){ + renderVision(element, change.vision, outputMeta) + } + + logger.debug { "Got update $change for output with name $name" } + visionMap[element]?.update(change) ?: console.info("Target vision for element $element with name $name not found") } else { - console.error ("WebSocket message data is not a string") + console.error("WebSocket message data is not a string") } } onopen = { diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt index 9b15df72..14845845 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt @@ -4,8 +4,6 @@ import hep.dataforge.names.* import hep.dataforge.vision.Vision import javafx.scene.Group import javafx.scene.Node -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlin.reflect.KClass class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory { @@ -15,7 +13,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory + obj.onPropertyChange(plugin.context) { name-> if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) { val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'") val propertyName = name.cutFirst() @@ -23,7 +21,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory?>>() init { - obj.propertyNameFlow.onEach { name -> + obj.onPropertyChange(fx.context) { name -> bindings.filter { it.key.startsWith(name) }.forEach { entry -> Platform.runLater { entry.value.invalidate() @@ -30,7 +28,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) { // bindings[currentName]?.invalidate() // currentName = currentName.cutLast() // } - }.launchIn(fx.context) + } } operator fun get(key: Name): ObjectBinding?> { 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 eaa38618..13528ff7 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 @@ -41,6 +41,13 @@ import java.awt.Desktop import java.net.URI import kotlin.time.milliseconds +public enum class VisionServerDataMode { + EMBED, + FETCH, + CONNECT +} + + /** * A ktor plugin container with given [routing] */ @@ -52,6 +59,7 @@ public class VisionServer internal constructor( override val config: Config = Config() public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY) public var cacheFragments: Boolean by config.boolean(true) + public var dataMode: VisionServerDataMode = VisionServerDataMode.CONNECT /** * a list of headers that should be applied to all pages @@ -72,10 +80,23 @@ public class VisionServer internal constructor( val consumer = object : VisionTagConsumer(consumer) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) { visionMap[name] = vision - - // Toggle updates - attributes[OUTPUT_FETCH_ATTRIBUTE] = "true" - attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true" + // Toggle update mode + when (dataMode) { + VisionServerDataMode.EMBED -> { + script { + attributes["class"] = OUTPUT_DATA_CLASS + unsafe { + +visionManager.encodeToString(vision) + } + } + } + VisionServerDataMode.FETCH -> { + attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto" + } + VisionServerDataMode.CONNECT -> { + attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto" + } + } } } @@ -110,10 +131,19 @@ public class VisionServer internal constructor( application.log.debug("Opened server socket for $name") val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") + try { withContext(visionManager.context.coroutineContext) { + + val initialVision = VisionChange(vision = vision) + val initialJson = visionManager.jsonFormat.encodeToString( + VisionChange.serializer(), + initialVision + ) + outgoing.send(Frame.Text(initialJson)) + vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update -> - val json = VisionManager.defaultJson.encodeToString( + val json = visionManager.jsonFormat.encodeToString( VisionChange.serializer(), update ) diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt index 65cf37e4..987fec3c 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt @@ -61,6 +61,7 @@ public interface Solid : Vision { public val descriptor: NodeDescriptor by lazy { NodeDescriptor { value(VISIBLE_KEY) { + inherited = false type(ValueType.BOOLEAN) default(true) } @@ -69,11 +70,14 @@ public interface Solid : Vision { value(Vision.STYLE_KEY) { type(ValueType.STRING) multiple = true + hide() } item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor) - enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) + enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) { + hide() + } } } @@ -152,21 +156,21 @@ public var Solid.x: Number get() = position?.x ?: 0f set(value) { position().x = value.toDouble() - notifyPropertyChanged(Solid.X_POSITION_KEY) + asyncNotifyPropertyChange(Solid.X_POSITION_KEY) } public var Solid.y: Number get() = position?.y ?: 0f set(value) { position().y = value.toDouble() - notifyPropertyChanged(Solid.Y_POSITION_KEY) + asyncNotifyPropertyChange(Solid.Y_POSITION_KEY) } public var Solid.z: Number get() = position?.z ?: 0f set(value) { position().z = value.toDouble() - notifyPropertyChanged(Solid.Z_POSITION_KEY) + asyncNotifyPropertyChange(Solid.Z_POSITION_KEY) } private fun Solid.rotation(): Point3D = @@ -176,21 +180,21 @@ public var Solid.rotationX: Number get() = rotation?.x ?: 0f set(value) { rotation().x = value.toDouble() - notifyPropertyChanged(Solid.X_ROTATION_KEY) + asyncNotifyPropertyChange(Solid.X_ROTATION_KEY) } public var Solid.rotationY: Number get() = rotation?.y ?: 0f set(value) { rotation().y = value.toDouble() - notifyPropertyChanged(Solid.Y_ROTATION_KEY) + asyncNotifyPropertyChange(Solid.Y_ROTATION_KEY) } public var Solid.rotationZ: Number get() = rotation?.z ?: 0f set(value) { rotation().z = value.toDouble() - notifyPropertyChanged(Solid.Z_ROTATION_KEY) + asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY) } private fun Solid.scale(): Point3D = @@ -200,19 +204,19 @@ public var Solid.scaleX: Number get() = scale?.x ?: 1f set(value) { scale().x = value.toDouble() - notifyPropertyChanged(Solid.X_SCALE_KEY) + asyncNotifyPropertyChange(Solid.X_SCALE_KEY) } public var Solid.scaleY: Number get() = scale?.y ?: 1f set(value) { scale().y = value.toDouble() - notifyPropertyChanged(Solid.Y_SCALE_KEY) + asyncNotifyPropertyChange(Solid.Y_SCALE_KEY) } public var Solid.scaleZ: Number get() = scale?.z ?: 1f set(value) { scale().z = value.toDouble() - notifyPropertyChanged(Solid.Z_SCALE_KEY) + asyncNotifyPropertyChange(Solid.Z_SCALE_KEY) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt index e472bbd0..c9d1654a 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt @@ -42,7 +42,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder { * Ger a prototype redirecting the request to the parent if prototype is not found */ override fun getPrototype(name: Name): Solid? = - prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name) + (prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name) /** * Create or edit prototype node as a group @@ -108,7 +108,7 @@ public fun VisionContainerBuilder.group(name: String, action: SolidGroup @Serializable(Prototypes.Companion::class) internal class Prototypes( children: Map = emptyMap(), -) : VisionGroupBase() { +) : VisionGroupBase(), PrototypeHolder { init { childrenInternal.putAll(children) @@ -155,4 +155,10 @@ internal class Prototypes( mapSerializer.serialize(encoder, value.children) } } + + override fun prototypes(builder: VisionContainerBuilder.() -> Unit) { + apply(builder) + } + + override fun getPrototype(name: Name): Solid? = get(name) as? Solid } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt index e1f2c940..d845df8b 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt @@ -89,18 +89,24 @@ public class SolidMaterial : Scheme() { public override val descriptor: NodeDescriptor by lazy { //must be lazy to avoid initialization bug NodeDescriptor { + inherited = true + usesStyles = true + value(COLOR_KEY) { inherited = true usesStyles = true type(ValueType.STRING, ValueType.NUMBER) widgetType = "color" } -// value(SPECULAR_COLOR_KEY) { -// inherited = true -// usesStyles = true -// type(ValueType.STRING, ValueType.NUMBER) -// widgetType = "color" -// } + + value(SPECULAR_COLOR_KEY) { + inherited = true + usesStyles = true + type(ValueType.STRING, ValueType.NUMBER) + widgetType = "color" + hide() + } + value(OPACITY_KEY) { inherited = true usesStyles = true diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt index 4512cf54..6430fc3d 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt @@ -5,9 +5,7 @@ import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.get import hep.dataforge.names.* import hep.dataforge.vision.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.CoroutineScope import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -128,14 +126,15 @@ public class SolidReferenceGroup( error("Setting a parent for a reference child is not possible") } - override val propertyNameFlow: Flow - get() = this@SolidReferenceGroup.propertyNameFlow.filter { name -> - name.startsWith(childToken(childName)) - }.map { name -> - name.cutFirst() + override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) { + this@SolidReferenceGroup.onPropertyChange(scope) { name -> + if (name.startsWith(childToken(childName))) { + callback(name.cutFirst()) + } } + } - override fun notifyPropertyChanged(propertyName: Name) { + override suspend fun notifyPropertyChanged(propertyName: Name) { this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName)) } diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt index 3adaf603..813086fd 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt @@ -15,8 +15,6 @@ import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.geometries.WireframeGeometry import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.Mesh -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlin.reflect.KClass /** @@ -45,7 +43,7 @@ public abstract class MeshThreeFactory( }.applyProperties(obj) //add listener to object properties - obj.propertyNameFlow.onEach { name -> + obj.onPropertyChange(three.updateScope) { name -> when { name.startsWith(Solid.GEOMETRY_KEY) -> { val oldGeometry = mesh.geometry as BufferGeometry @@ -59,7 +57,7 @@ public abstract class MeshThreeFactory( name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj) else -> mesh.updateProperty(obj, name) } - }.launchIn(three.updateScope) + } return mesh } diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt index 33cfcc60..d6be69e1 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt @@ -47,7 +47,7 @@ public fun Object3D.updatePosition(obj: Vision) { */ public fun Object3D.updateProperty(source: Vision, propertyName: Name) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) { - this.material = getMaterial(source, false) + this.material = getMaterial(source, true) } else if ( propertyName.startsWith(Solid.POSITION_KEY) || propertyName.startsWith(Solid.ROTATION) diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt index 4d1c7d99..258628bb 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt @@ -8,8 +8,6 @@ import info.laht.threekt.core.Object3D import info.laht.threekt.geometries.TextBufferGeometry import info.laht.threekt.objects.Mesh import kotlinext.js.jsObject -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlin.reflect.KClass /** @@ -27,10 +25,10 @@ public object ThreeLabelFactory : ThreeFactory { }) return Mesh(textGeo, getMaterial(obj, true)).apply { updatePosition(obj) - obj.propertyNameFlow.onEach { _ -> + obj.onPropertyChange(three.updateScope){ _ -> //TODO three.logger.warn{"Label parameter change not implemented"} - }.launchIn(three.updateScope) + } } } } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt index d914a218..0d7a8e5c 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt @@ -9,8 +9,6 @@ import info.laht.threekt.core.Geometry import info.laht.threekt.core.Object3D import info.laht.threekt.math.Color import info.laht.threekt.objects.LineSegments -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlin.reflect.KClass public object ThreeLineFactory : ThreeFactory { @@ -30,9 +28,9 @@ public object ThreeLineFactory : ThreeFactory { updatePosition(obj) //layers.enable(obj.layer) //add listener to object properties - obj.propertyNameFlow.onEach { propertyName -> + obj.onPropertyChange(three.updateScope) { propertyName -> updateProperty(obj, propertyName) - }.launchIn(three.updateScope) + } } } 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 4401e8cd..1736ce82 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 @@ -59,6 +59,10 @@ public object ThreeMaterials { MeshPhongMaterial().apply { color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR specular = meta[SolidMaterial.SPECULAR_COLOR_KEY]!!.getColor() + emissive = specular + reflectivity = 1.0 + refractionRatio = 1.0 + shininess = 100.0 opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0 transparent = opacity < 1.0 wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt index 834a56a7..a7a74ce3 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt @@ -67,7 +67,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { updatePosition(obj) //obj.onChildrenChange() - obj.propertyNameFlow.onEach { name -> + obj.onPropertyChange(updateScope) { name -> if ( name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.ROTATION) || @@ -78,7 +78,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } else if (name == Vision.VISIBLE_KEY) { visible = obj.visible ?: true } - }.launchIn(updateScope) + } obj.structureChanges.onEach { (nameToken, _, child) -> // if (name.isEmpty()) { diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt index 08866d46..8ccbc37f 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt @@ -9,8 +9,6 @@ import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_ import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Object3D import info.laht.threekt.objects.Mesh -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlin.reflect.KClass public object ThreeReferenceFactory : ThreeFactory { @@ -47,7 +45,7 @@ public object ThreeReferenceFactory : ThreeFactory { //TODO apply child properties - obj.propertyNameFlow.onEach { name-> + obj.onPropertyChange(three.updateScope) { name-> if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'") val propertyName = name.cutFirst() @@ -57,7 +55,7 @@ public object ThreeReferenceFactory : ThreeFactory { } else { object3D.updateProperty(obj, name) } - }.launchIn(three.updateScope) + } return object3D