diff --git a/build.gradle.kts b/build.gradle.kts index d4147448..38355ee0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("ru.mipt.npm.project") } -val dataforgeVersion by extra("0.2.1-dev-4") +val dataforgeVersion by extra("0.2.1-dev-5") val ktorVersion by extra("1.4.3") val htmlVersion by extra("0.7.2") val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20") 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 ff610211..d4e8d833 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 @@ -5,6 +5,7 @@ import hep.dataforge.names.plus import hep.dataforge.names.startsWith import hep.dataforge.values.asValue import hep.dataforge.vision.getProperty +import hep.dataforge.vision.properties import hep.dataforge.vision.set import hep.dataforge.vision.setProperty import hep.dataforge.vision.solid.* @@ -16,6 +17,8 @@ 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( @@ -31,11 +34,11 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV scaleX = xSize scaleY = ySize scaleZ = zSize - config[MeshThreeFactory.EDGES_ENABLED_KEY] = false - config[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false + properties[MeshThreeFactory.EDGES_ENABLED_KEY] = false + properties[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false } - override fun render(): Object3D { + override fun render(three: ThreePlugin): Object3D { val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0 val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0 val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0 @@ -60,7 +63,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV mesh.scale.set(xSize, ySize, zSize) //add listener to object properties - onPropertyChange(mesh) { name -> + propertyInvalidated.onEach { name -> when { name.startsWith(GEOMETRY_KEY) -> { val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0 @@ -71,12 +74,12 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV } name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(this@VariableBox) name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox) - name.startsWith(MATERIAL_COLOR_KEY)->{ + name.startsWith(MATERIAL_COLOR_KEY) -> { mesh.material = getMaterial(this, true) } else -> mesh.updateProperty(this@VariableBox, name) } - } + }.launchIn(three.context) return mesh } @@ -100,7 +103,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV color(r.toUByte(), g.toUByte(), b.toUByte()) } - companion object{ + companion object { private val X_SIZE_KEY = GEOMETRY_KEY + "xSize" private val Y_SIZE_KEY = GEOMETRY_KEY + "ySize" private val Z_SIZE_KEY = GEOMETRY_KEY + "zSize" diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/threeControls.kt b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/threeControls.kt index 68d90705..ea835f4f 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/threeControls.kt +++ b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/threeControls.kt @@ -4,6 +4,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.isEmpty import hep.dataforge.vision.Vision import hep.dataforge.vision.VisionGroup +import hep.dataforge.vision.describedProperties import hep.dataforge.vision.react.objectTree import hep.dataforge.vision.solid.three.ThreeCanvas import kotlinx.css.* @@ -53,7 +54,7 @@ public val ThreeControls: FunctionalComponent = functionalCo if (selectedObject != null) { visionPropertyEditor( selectedObject, - default = selectedObject.allProperties, + default = selectedObject.describedProperties, key = selected ) } 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 5bc10577..9734bc74 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 @@ -4,6 +4,7 @@ import hep.dataforge.meta.Meta import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.vision.Vision import hep.dataforge.vision.getStyle +import hep.dataforge.vision.properties import hep.dataforge.vision.react.configEditor import hep.dataforge.vision.react.metaViewer import org.w3c.dom.Element @@ -17,7 +18,7 @@ public fun RBuilder.visionPropertyEditor( key: Any? = null ) { card("Properties") { - configEditor(item.config, descriptor, default, key) + configEditor(item.properties, descriptor, default, key) } val styles = item.styles if(styles.isNotEmpty()) { diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt index a4ecce5e..b659452f 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt @@ -19,7 +19,7 @@ public external interface ConfigEditorItemProps : RProps { /** * Root config object - always non null */ - public var root: Config + public var root: MutableItemProvider /** * Full path to the displayed node in [root]. Could be empty @@ -44,7 +44,7 @@ private val ConfigEditorItem: FunctionalComponent = private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { var expanded: Boolean by useState { true } - var item: MetaItem? by useState { props.root[props.name] } + var item: MetaItem<*>? by useState { props.root.getItem(props.name) } val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) val defaultItem = props.default?.get(props.name) var actualItem: MetaItem? by useState { item ?: defaultItem ?: descriptorItem?.defaultItem() } @@ -52,7 +52,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { val token = props.name.lastOrNull()?.toString() ?: "Properties" fun update() { - item = props.root[props.name] + item = props.root.getItem(props.name) actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem() } @@ -192,7 +192,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { public external interface ConfigEditorProps : RProps { public var id: Name - public var root: Config + public var root: MutableItemProvider public var default: Meta? public var descriptor: NodeDescriptor? } @@ -229,7 +229,7 @@ public fun Element.configEditor( } public fun RBuilder.configEditor( - config: Config, + config: MutableItemProvider, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null, 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 19080b67..aa1fcba2 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt @@ -8,7 +8,6 @@ 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.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.serialization.SerialName @@ -32,32 +31,33 @@ public open class VisionBase : Vision { * Object own properties excluding styles and inheritance */ @SerialName("properties") - private var _properties: Config? = null + protected var innerProperties: Config? = null + private set /** * All own properties as a read-only Meta */ - public val ownProperties: Meta get() = _properties?: Meta.EMPTY + public val ownProperties: Meta get() = innerProperties ?: Meta.EMPTY @Synchronized private fun getOrCreateConfig(): Config { - if (_properties == null) { + if (innerProperties == null) { val newProperties = Config() - _properties = newProperties + innerProperties = newProperties newProperties.onChange(this) { name, oldItem, newItem -> if (oldItem != newItem) { notifyPropertyChanged(name) } } } - return _properties!! + return innerProperties!! } /** * A fast accessor method to get own property (no inheritance or styles */ override fun getOwnProperty(name: Name): MetaItem<*>? { - return _properties?.getItem(name) + return innerProperties?.getItem(name) } override fun getProperty( @@ -93,9 +93,8 @@ public open class VisionBase : Vision { } } - private val _propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow( - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + @Transient + private val _propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow() override val propertyInvalidated: SharedFlow get() = _propertyInvalidationFlow 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 a1170b48..0bb40007 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroupBase.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroupBase.kt @@ -1,7 +1,6 @@ package hep.dataforge.vision import hep.dataforge.names.* -import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.serialization.SerialName @@ -36,9 +35,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup { } @Transient - private val _structureChanges: MutableSharedFlow = MutableSharedFlow( - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + private val _structureChanges: MutableSharedFlow = MutableSharedFlow() override val structureChanges: SharedFlow get() = _structureChanges 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 8dda1ebf..eefffc17 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,6 +18,10 @@ 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 public class VisionClient : AbstractPlugin() { @@ -124,9 +128,9 @@ public class VisionClient : AbstractPlugin() { ) 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" } + ?: 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-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt index a342c961..d690c6b1 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 @@ -81,7 +81,7 @@ public interface Solid : Vision { if (first.position != second.position) return false if (first.rotation != second.rotation) return false if (first.scale != second.scale) return false - if (first.properties != second.properties) return false + if (first.ownProperties != second.ownProperties) return false return true } 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 e4b9f0f7..cc3b92e4 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 @@ -38,6 +38,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder { /** * Create or edit prototype node as a group */ + @VisionBuilder public fun prototypes(builder: VisionContainerBuilder.() -> Unit): Unit { (prototypes ?: Prototypes().also { prototypes = it @@ -107,10 +108,9 @@ internal class Prototypes( children: Map = emptyMap(), ) : VisionGroupBase(), PrototypeHolder { - override var parent: VisionGroup? = null - - private val _children = HashMap(children) - override val children: Map get() = _children + init { + childrenInternal.putAll(children) + } override val prototypes: MutableVisionGroup get() = this diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReferenceGroup.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt similarity index 100% rename from visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReferenceGroup.kt rename to visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt diff --git a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt index 6e045744..55efe64a 100644 --- a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt @@ -2,6 +2,7 @@ package hep.dataforge.vision.solid import hep.dataforge.meta.int import hep.dataforge.names.asName +import hep.dataforge.vision.getProperty import hep.dataforge.vision.setProperty import hep.dataforge.vision.styleSheet import hep.dataforge.vision.useStyle @@ -19,7 +20,7 @@ class PropertyTest { box = box(100, 100, 100) } } - assertEquals(22, box?.getProperty("test".asName()).int) + assertEquals(22, box?.getProperty("test").int) } @Test @@ -37,7 +38,7 @@ class PropertyTest { } } } - assertEquals(22, box?.getProperty("test".asName()).int) + assertEquals(22, box?.getProperty("test").int) } @Test 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 519fa9b3..8a80527d 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 @@ -85,7 +85,7 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { } } -internal fun Mesh.applyEdges(obj: Solid) { +public fun Mesh.applyEdges(obj: Solid) { val edges = children.find { it.name == "@edges" } as? LineSegments //inherited edges definition, enabled by default if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) { @@ -111,7 +111,7 @@ internal fun Mesh.applyEdges(obj: Solid) { } } -internal fun Mesh.applyWireFrame(obj: Solid) { +public fun Mesh.applyWireFrame(obj: Solid) { children.find { it.name == "@wireframe" }?.let { remove(it) (it as LineSegments).dispose() diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvasLabelFactory.kt index 90027451..f8d26cec 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeCanvasLabelFactory.kt @@ -22,7 +22,7 @@ import kotlin.reflect.KClass public object ThreeCanvasLabelFactory : ThreeFactory { override val type: KClass get() = SolidLabel::class - override fun invoke(obj: SolidLabel): Object3D { + override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D { val canvas = document.createElement("canvas") as HTMLCanvasElement val context = canvas.getContext("2d") as CanvasRenderingContext2D context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" 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 9eacdea9..1bd46f37 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 @@ -25,7 +25,6 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { private val objectFactories = HashMap, ThreeFactory<*>>() private val compositeFactory = ThreeCompositeFactory(this) - private val refFactory = ThreeReferenceFactory(this) //TODO generate a separate supervisor update scope internal val updateScope: CoroutineScope get() = context @@ -49,8 +48,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { public fun buildObject3D(obj: Solid): Object3D { return when (obj) { - is ThreeVision -> obj.render() - is SolidReferenceGroup -> refFactory(obj) + is ThreeVision -> obj.render(this) + is SolidReferenceGroup -> ThreeReferenceFactory(this, obj) is SolidGroup -> { val group = ThreeGroup() obj.children.forEach { (token, child) -> @@ -108,13 +107,13 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { }.launchIn(updateScope) } } - is Composite -> compositeFactory(obj) + is Composite -> compositeFactory(this, obj) else -> { //find specialized factory for this type if it is present val factory: ThreeFactory? = findObjectFactory(obj::class) when { - factory != null -> factory(obj) - obj is GeometrySolid -> ThreeShapeFactory(obj) + factory != null -> factory(this, obj) + obj is GeometrySolid -> ThreeShapeFactory(this, obj) else -> error("Renderer for ${obj::class} not found") } } 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 02739b1c..2374c05f 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 @@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlin.reflect.KClass -public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory { +public object ThreeReferenceFactory : ThreeFactory { private val cache = HashMap() override val type: KClass = SolidReferenceGroup::class @@ -32,7 +32,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory } } - override fun invoke(obj: SolidReferenceGroup): Object3D { + override fun invoke(three: ThreePlugin, obj: SolidReferenceGroup): Object3D { val template = obj.prototype val cachedObject = cache.getOrPut(template) { three.buildObject3D(template) diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt index 67870229..8a6d2a52 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt @@ -7,5 +7,5 @@ import info.laht.threekt.core.Object3D * A custom visual object that has its own Three.js renderer */ public abstract class ThreeVision : SolidBase() { - public abstract fun render(): Object3D + public abstract fun render(three: ThreePlugin): Object3D }