diff --git a/CHANGELOG.md b/CHANGELOG.md index 611e4c13..42282a71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,14 @@ ### Added - Server module - Change collector +- Customizable accessors for colors ### Changed - Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate. - Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines. - JavaFX support moved to a separate module - Threejs support moved to a separate module -- \[Breaking change!\] Stylesheets are moved into properties under `@stylesheet` key +- \[Format breaking change!\] Stylesheets are moved into properties under `@stylesheet` key ### Deprecated diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt index 73af1875..9027b26a 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt @@ -1,7 +1,6 @@ package ru.mipt.npm.muon.monitor import hep.dataforge.vision.removeAll -import hep.dataforge.vision.setProperty import hep.dataforge.vision.solid.* import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z @@ -63,7 +62,6 @@ class Model { fun reset() { map.values.forEach { - it.config it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null) } tracks.removeAll() 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 4d3ea1f3..e7a6d971 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt @@ -11,7 +11,7 @@ import hep.dataforge.names.plus */ public inline class StyleSheet(private val owner: VisionGroup) { - private val styleNode get() = owner.properties?.get(STYLESHEET_KEY).node + private val styleNode get() = owner.getOwnProperty(STYLESHEET_KEY).node public val items: Map? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY } @@ -21,11 +21,7 @@ public inline class StyleSheet(private val owner: VisionGroup) { * Define a style without notifying owner */ public fun define(key: String, style: Meta?) { - if (style == null) { - styleNode?.remove(key) - } else { - owner.config[STYLESHEET_KEY + key] = style - } + owner.setProperty(STYLESHEET_KEY + key, style) } /** @@ -58,7 +54,7 @@ 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?.propertyChanged(it) } + tokens.forEach { parent?.notifyPropertyChanged(it) } } if (this is VisionGroup) { for (obj in this) { @@ -78,7 +74,7 @@ public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this) * Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment. */ public fun Vision.useStyle(name: String) { - styles = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name + styles = (getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name } @@ -86,7 +82,7 @@ public fun Vision.useStyle(name: String) { * Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision]. */ public tailrec fun Vision.getStyle(name: String): Meta? = - properties?.get(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name) + getOwnProperty(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name) /** * Resolve an item in all style layers 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 058a04b3..4baa5b8a 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt @@ -10,13 +10,14 @@ import hep.dataforge.provider.Type import hep.dataforge.values.asValue import hep.dataforge.vision.Vision.Companion.TYPE import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY +import kotlinx.coroutines.flow.Flow import kotlinx.serialization.Transient /** * A root type for display hierarchy */ @Type(TYPE) -public interface Vision : Configurable, Described { +public interface Vision : Described { /** * The parent object of this one. If null, this one is a root. @@ -25,42 +26,52 @@ public interface Vision : Configurable, Described { public var parent: VisionGroup? /** - * Nullable version of [config] used to check if this [Vision] has custom properties + * A fast accessor method to get own property (no inheritance or styles */ - public val properties: Config? + public fun getOwnProperty(name: Name): MetaItem<*>? /** - * All properties including styles and prototypes if present, including inherited ones + * Get property. + * @param inherit toggles parent node property lookup + * @param includeStyles toggles inclusion of */ - public val allProperties: Laminate + public fun getProperty( + name: Name, + inherit: Boolean = true, + includeStyles: Boolean = true, + includeDefaults: Boolean = true, + ): MetaItem<*>? + /** - * Get property (including styles). [inherit] toggles parent node property lookup + * Set the property value */ - public fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? + public fun setProperty(name: Name, item: MetaItem<*>?) /** - * Trigger property invalidation event. If [name] is empty, notify that the whole object is changed + * 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 fun propertyChanged(name: Name): Unit + public val propertyInvalidated: Flow + /** - * Add listener triggering on property change + * Notify all listeners that a property has been changed and should be invalidated */ - public fun onPropertyChange(owner: Any?, action: (Name) -> Unit): Unit - - /** - * Remove change listeners with given owner. - */ - public fun removeChangeListener(owner: Any?) + public fun notifyPropertyChanged(propertyName: Name): Unit /** * List of names of styles applied to this object. Order matters. Not inherited. */ public var styles: List - get() = properties[STYLE_KEY]?.stringList ?: emptyList() + get() = getProperty( + STYLE_KEY, + inherit = false, + includeStyles = false, + includeDefaults = true + )?.stringList ?: emptyList() set(value) { - config[STYLE_KEY] = value + setProperty(STYLE_KEY, value) } /** @@ -78,24 +89,42 @@ public interface Vision : Configurable, Described { } } +/** + * Convenient accessor for all properties of a vision. Provided properties include styles and defaults, but do not inherit. + */ +public val Vision.properties: MutableItemProvider + get() = object : MutableItemProvider { + override fun getItem(name: Name): MetaItem<*>? = getProperty(name, + inherit = false, + includeStyles = true, + includeDefaults = true + ) + + override fun setItem(name: Name, item: MetaItem<*>?): Unit = setProperty(name, item) + } + /** * Get [Vision] property using key as a String */ -public fun Vision.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = - getProperty(key.toName(), inherit) +public fun Vision.getProperty( + key: String, + inherit: Boolean = true, + includeStyles: Boolean = true, + includeDefaults: Boolean = true, +): MetaItem<*>? = getProperty(key.toName(), inherit, includeStyles, includeDefaults) /** * A convenience method to pair [getProperty] */ public fun Vision.setProperty(key: Name, value: Any?) { - config[key] = value + properties[key] = value } /** * A convenience method to pair [getProperty] */ public fun Vision.setProperty(key: String, value: Any?) { - config[key] = value + properties[key] = value } /** @@ -103,27 +132,7 @@ public fun Vision.setProperty(key: String, value: Any?) { */ public var Vision.visible: Boolean? get() = getProperty(VISIBLE_KEY).boolean - set(value) = config.setValue(VISIBLE_KEY, value?.asValue()) - -///** -// * Convenience delegate for properties -// */ -//public fun Vision.property( -// default: MetaItem<*>? = null, -// key: Name? = null, -// inherit: Boolean = true, -//): MutableItemDelegate = -// object : ReadWriteProperty?> { -// override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? { -// val name = key ?: property.name.toName() -// return getProperty(name, inherit) ?: default -// } -// -// override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) { -// val name = key ?: property.name.toName() -// setProperty(name, value) -// } -// } + set(value) = setProperty(VISIBLE_KEY, value?.asValue()) public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider { override fun getItem(name: Name): MetaItem<*>? { 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 6127479c..19080b67 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt @@ -3,15 +3,18 @@ package hep.dataforge.vision import hep.dataforge.meta.* import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.defaultItem -import hep.dataforge.meta.descriptors.defaultMeta import hep.dataforge.meta.descriptors.get 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 import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import kotlin.jvm.Synchronized internal data class PropertyListener( val owner: Any? = null, @@ -28,83 +31,89 @@ public open class VisionBase : Vision { /** * Object own properties excluding styles and inheritance */ - override var properties: Config? = null - protected set + @SerialName("properties") + private var _properties: Config? = null - override val descriptor: NodeDescriptor? get() = null + /** + * All own properties as a read-only Meta + */ + public val ownProperties: Meta get() = _properties?: Meta.EMPTY - protected fun updateStyles(names: List) { - names.mapNotNull { getStyle(it) }.asSequence() - .flatMap { it.items.asSequence() } - .distinctBy { it.key } - .forEach { - propertyChanged(it.key.asName()) + @Synchronized + private fun getOrCreateConfig(): Config { + if (_properties == null) { + val newProperties = Config() + _properties = newProperties + newProperties.onChange(this) { name, oldItem, newItem -> + if (oldItem != newItem) { + notifyPropertyChanged(name) + } } + } + return _properties!! } /** - * The config is initialized and assigned on-demand. - * To avoid unnecessary allocations, one should access [getAllProperties] via [getProperty] instead. + * A fast accessor method to get own property (no inheritance or styles */ - override val config: Config by lazy { - properties ?: Config().also { config -> - properties = config.also { - it.onChange(this) { name, _, _ -> propertyChanged(name) } - } + override fun getOwnProperty(name: Name): MetaItem<*>? { + return _properties?.getItem(name) + } + + override fun getProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, + ): MetaItem<*>? = sequence { + yield(getOwnProperty(name)) + if (includeStyles) { + yieldAll(getStyleItems(name)) } - } - - @Transient - private val listeners = HashSet() - - override fun propertyChanged(name: Name) { - if (name == STYLE_KEY) { - updateStyles(properties?.get(STYLE_KEY)?.stringList ?: emptyList()) - } - for (listener in listeners) { - listener.action(name) - } - } - - override fun onPropertyChange(owner: Any?, action: (Name) -> Unit) { - listeners.add(PropertyListener(owner, action)) - } - - override fun removeChangeListener(owner: Any?) { - listeners.removeAll { owner == null || it.owner == owner } - } - - /** - * All available properties in a layered form - */ - override val allProperties: Laminate - get() = Laminate( - properties, - allStyles, - parent?.allProperties, - descriptor?.defaultMeta(), - ) - - override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence { - yield(properties?.get(name)) - yieldAll(getStyleItems(name)) if (inherit) { yield(parent?.getProperty(name, inherit)) } yield(descriptor?.get(name)?.defaultItem()) }.merge() - /** - * Reset all properties to their default values - */ - public fun resetProperties() { - properties?.removeListener(this) - properties = null + @Synchronized + override fun setProperty(name: Name, item: MetaItem<*>?) { + getOrCreateConfig().setItem(name, item) + notifyPropertyChanged(name) + } + + override val descriptor: NodeDescriptor? get() = null + + private fun updateStyles(names: List) { + names.mapNotNull { getStyle(it) }.asSequence() + .flatMap { it.items.asSequence() } + .distinctBy { it.key } + .forEach { + notifyPropertyChanged(it.key.asName()) + } + } + + private val _propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow( + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + + override val propertyInvalidated: SharedFlow get() = _propertyInvalidationFlow + + override fun notifyPropertyChanged(propertyName: Name) { + if (propertyName == STYLE_KEY) { + updateStyles(properties.getItem(STYLE_KEY)?.stringList ?: emptyList()) + } + + _propertyInvalidationFlow.tryEmit(propertyName) + } + + public fun configure(block: MutableMeta<*>.() -> Unit) { + getOrCreateConfig().block() } override fun update(change: VisionChange) { - change.propertyChange[Name.EMPTY]?.let { - config.update(it) + change.properties?.let { + getOrCreateConfig().update(it) } } 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 ca7396a7..f2ac5d6b 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt @@ -6,7 +6,10 @@ import hep.dataforge.names.plus import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.serialization.* +import kotlin.jvm.Synchronized import kotlin.time.Duration /** @@ -14,29 +17,41 @@ import kotlin.time.Duration */ public class VisionChangeBuilder : VisionContainerBuilder { - private val propertyChange = HashMap() - private val childrenChange = HashMap() + private var reset: Boolean = false + private var vision: Vision? = null + private val propertyChange = Config() + private val children: HashMap = HashMap() - public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty() + public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty() + + @Synchronized + private fun getOrPutChild(visionName: Name): VisionChangeBuilder = + children.getOrPut(visionName) { VisionChangeBuilder() } public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) { - propertyChange - .getOrPut(visionName) { Config() } - .setItem(propertyName, item) + if (visionName == Name.EMPTY) { + propertyChange[propertyName] = item + } else { + getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item) + } } override fun set(name: Name, child: Vision?) { - childrenChange[name] = child + getOrPutChild(name).apply { + vision = child + reset = vision == null + } } /** * Isolate collected changes by creating detached copies of given visions */ public fun isolate(manager: VisionManager): VisionChange = VisionChange( - propertyChange.mapValues { it.value.seal() }, - childrenChange.mapValues { it.value?.isolate(manager) } + reset, + vision?.isolate(manager), + if (propertyChange.isEmpty()) null else propertyChange.seal(), + if (children.isEmpty()) null else children.mapValues { it.value.isolate(manager) } ) - //TODO optimize isolation for visions without parents? } private fun Vision.isolate(manager: VisionManager): Vision { @@ -46,16 +61,13 @@ private fun Vision.isolate(manager: VisionManager): Vision { } @Serializable -public data class VisionChange( - val propertyChange: Map, - val childrenChange: Map, +public class VisionChange( + public val reset: Boolean = false, + public val vision: Vision? = null, + public val properties: Meta? = null, + public val children: Map? = null, ) { - public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty() - /** - * A shortcut to the top level property dif - */ - public val properties: Meta? get() = propertyChange[Name.EMPTY] } public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange = @@ -69,17 +81,10 @@ private fun CoroutineScope.collectChange( ) { //Collect properties change - source.config.onChange(this) { propertyName, oldItem, newItem -> - if (oldItem != newItem) { - launch { - collector().propertyChanged(name, propertyName, newItem) - } - } - } - - coroutineContext[Job]?.invokeOnCompletion { - source.config.removeListener(this) - } + source.propertyInvalidated.onEach { 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 @@ -89,17 +94,12 @@ private fun CoroutineScope.collectChange( //Subscribe for structure change if (source is MutableVisionGroup) { - source.onStructureChange(this) { token, before, after -> - before?.removeChangeListener(this) - (before as? MutableVisionGroup)?.removeStructureChangeListener(this) + source.structureChanges.onEach { (token, _, after) -> if (after != null) { collectChange(name + token, after, collector) } collector()[name + token] = after - } - coroutineContext[Job]?.invokeOnCompletion { - source.removeStructureChangeListener(this) - } + }.launchIn(this) } } } @@ -111,17 +111,19 @@ public fun Vision.flowChanges( ): Flow = flow { var collector = VisionChangeBuilder() - manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector } + coroutineScope { + collectChange(Name.EMPTY, this@flowChanges) { collector } - while (currentCoroutineContext().isActive) { - //Wait for changes to accumulate - delay(collectionDuration) - //Propagate updates only if something is changed - if (!collector.isEmpty()) { - //emit changes - emit(collector.isolate(manager)) - //Reset the collector - collector = VisionChangeBuilder() + while (currentCoroutineContext().isActive) { + //Wait for changes to accumulate + delay(collectionDuration) + //Propagate updates only if something is changed + if (!collector.isEmpty()) { + //emit changes + 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/VisionGroup.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt index fdfb9e7a..75855eb1 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt @@ -2,6 +2,7 @@ package hep.dataforge.vision import hep.dataforge.names.* import hep.dataforge.provider.Provider +import kotlinx.coroutines.flow.Flow public interface VisionContainer { public operator fun get(name: Name): V? @@ -75,19 +76,12 @@ public interface VisionContainerBuilder { */ public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder { - /** - * Add listener for children structure change. - * @param owner the handler to properly remove listeners - * @param action First argument of the action is the name of changed child. Second argument is the new value of the object. - */ - public fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit) + public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?) /** - * Remove children change listener + * Flow structure changes of this group. Unconsumed changes are discarded */ - public fun removeStructureChangeListener(owner: Any?) - -// public operator fun set(name: Name, child: Vision?) + public val structureChanges: Flow } public operator fun VisionContainer.get(str: String): V? = get(str.toName()) 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 996b0853..a1170b48 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,9 @@ package hep.dataforge.vision -import hep.dataforge.meta.configure import hep.dataforge.names.* +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -26,54 +28,25 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup { */ override val children: Map get() = childrenInternal - override fun propertyChanged(name: Name) { - super.propertyChanged(name) + override fun notifyPropertyChanged(propertyName: Name) { + super.notifyPropertyChanged(propertyName) for (obj in this) { - obj.propertyChanged(name) + obj.notifyPropertyChanged(propertyName) } } - private data class StructureChangeListener( - val owner: Any?, - val callback: (token: NameToken, before: Vision?, after: Vision?) -> Unit, + @Transient + private val _structureChanges: MutableSharedFlow = MutableSharedFlow( + onBufferOverflow = BufferOverflow.DROP_OLDEST ) - @Transient - private val structureChangeListeners = HashSet() - - /** - * Add listener for children change - */ - override fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit) { - structureChangeListeners.add( - StructureChangeListener( - owner, - action - ) - ) - } - - /** - * Remove children change listener - */ - override fun removeStructureChangeListener(owner: Any?) { - structureChangeListeners.removeAll { owner == null || it.owner === owner } - } + override val structureChanges: SharedFlow get() = _structureChanges /** * Propagate children change event upwards */ - protected fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) { - structureChangeListeners.forEach { it.callback(name, before, after) } - } - - /** - * Remove a child with given name token - */ - public fun removeChild(token: NameToken): Vision? { - val removed = childrenInternal.remove(token) - removed?.parent = null - return removed + private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) { + _structureChanges.tryEmit(MutableVisionGroup.StructureChange(name, before, after)) } /** @@ -91,12 +64,22 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup { /** * Set parent for given child and attach it */ - private fun attachChild(token: NameToken, child: Vision) { - if (child.parent == null) { - child.parent = this - childrenInternal[token] = child - } else if (child.parent !== this) { - error("Can't reassign existing parent for $child") + private fun attachChild(token: NameToken, child: Vision?) { + val before = children[token] + when { + child == null -> { + childrenInternal.remove(token) + } + child.parent == null -> { + child.parent = this + childrenInternal[token] = child + } + child.parent !== this -> { + error("Can't reassign existing parent for $child") + } + } + if (before != child) { + childrenChanged(token, before, child) } } @@ -133,13 +116,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup { } name.length == 1 -> { val token = name.tokens.first() - val before = children[token] - if (child == null) { - removeChild(token) - } else { - attachChild(token, child) - } - childrenChanged(token, before, child) + attachChild(token, child) } else -> { //TODO add safety check @@ -150,18 +127,12 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup { } override fun update(change: VisionChange) { - //update stylesheet -// val changeStyleSheet = change.styleSheet -// if (changeStyleSheet != null) { -// styleSheet { -// update(changeStyleSheet) -// } -// } - change.propertyChange.forEach {(childName,configChange)-> - get(childName)?.configure(configChange) - } - change.childrenChange.forEach { (name, child) -> - set(name, child) + change.children?.forEach { (name, change) -> + when { + change.reset -> set(name, null) + change.vision != null -> set(name, change.vision) + else -> get(name)?.update(change) + } } super.update(change) } 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 0e17c123..7c9a4521 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt @@ -1,9 +1,7 @@ package hep.dataforge.vision -import hep.dataforge.meta.Laminate -import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.* import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.node import hep.dataforge.names.Name import hep.dataforge.values.ValueType import hep.dataforge.values.asValue @@ -30,4 +28,20 @@ public inline fun > NodeDescriptor.enum(key: Name, default: default(default) } allowedValues = enumValues().map { it.asValue() } -} \ No newline at end of file +} + +@DFExperimental +public val Vision.ownProperties: Meta? + get() = (this as? VisionBase)?.ownProperties + +@DFExperimental +public val Vision.describedProperties: Meta + get() = Meta { + descriptor?.items?.forEach { (key, _) -> + key put getProperty(key) + } + } + +public fun Vision.configure(meta: Meta?): Unit = update(VisionChange(properties = meta)) + +public fun Vision.configure(block: MutableMeta<*>.() -> Unit): Unit = configure(Meta(block)) \ No newline at end of file diff --git a/visionforge-core/src/commonTest/kotlin/hep/dataforge/vision/html/HtmlTagTest.kt b/visionforge-core/src/commonTest/kotlin/hep/dataforge/vision/html/HtmlTagTest.kt index 0d40205a..0937bd64 100644 --- a/visionforge-core/src/commonTest/kotlin/hep/dataforge/vision/html/HtmlTagTest.kt +++ b/visionforge-core/src/commonTest/kotlin/hep/dataforge/vision/html/HtmlTagTest.kt @@ -1,7 +1,6 @@ package hep.dataforge.vision.html import hep.dataforge.meta.DFExperimental -import hep.dataforge.meta.configure import hep.dataforge.meta.set import hep.dataforge.vision.VisionBase import kotlinx.html.* @@ -35,7 +34,7 @@ class HtmlTagTest { div { h2 { +"Properties" } ul { - vision.properties?.items?.forEach { + (vision as? VisionBase)?.ownProperties?.items?.forEach { li { a { +it.key.toString() } p { +it.value.toString() } diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt index 251130c2..e8b22dbe 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt @@ -1,12 +1,8 @@ package hep.dataforge.vision.editor -import hep.dataforge.meta.Config -import hep.dataforge.meta.Meta +import hep.dataforge.meta.* import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.update -import hep.dataforge.vision.Vision -import hep.dataforge.vision.getStyle -import hep.dataforge.vision.setProperty +import hep.dataforge.vision.* import javafx.beans.binding.Binding import javafx.beans.property.SimpleObjectProperty import javafx.scene.Node @@ -23,8 +19,8 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() { constructor( item: Vision?, descriptor: NodeDescriptor?, - selector: (Vision) -> Config = { it.config } - ) : this(selector) { + selector: (Vision) -> MutableItemProvider = { it.properties } + ) : this({it.describedProperties}) { this.item = item this.descriptorProperty.set(descriptor) } diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt index 3f863a34..ce88a14c 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FX3DPlugin.kt @@ -45,7 +45,7 @@ class FX3DPlugin : AbstractPlugin() { fun buildNode(obj: Solid): Node { val binding = VisualObjectFXBinding(obj) return when (obj) { - is SolidReference -> referenceFactory(obj, binding) + is SolidReferenceGroup -> referenceFactory(obj, binding) is SolidGroup -> { Group(obj.children.mapNotNull { (token, obj) -> (obj as? Solid)?.let { 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 7c7258ac..77244de2 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 @@ -6,15 +6,15 @@ import javafx.scene.Group import javafx.scene.Node import kotlin.reflect.KClass -class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory { - override val type: KClass get() = SolidReference::class +class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory { + override val type: KClass get() = SolidReferenceGroup::class - override fun invoke(obj: SolidReference, binding: VisualObjectFXBinding): Node { + override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node { val prototype = obj.prototype val node = plugin.buildNode(prototype) obj.onPropertyChange(this) { name-> - if (name.firstOrNull()?.body == SolidReference.REFERENCE_CHILD_PROPERTY_PREFIX) { + 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() val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found") diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt index d3dd5b3a..58ec3b51 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt @@ -2,12 +2,12 @@ package hep.dataforge.vision.gdml import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.set import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.names.toName import hep.dataforge.vision.set +import hep.dataforge.vision.setProperty import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vision.styleSheet @@ -51,13 +51,13 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) { private val proto = SolidGroup() private val solids = proto.group(solidsName) { - config["edges.enabled"] = false + setProperty("edges.enabled", false) } - private val referenceStore = HashMap>() + private val referenceStore = HashMap>() - private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReference { + private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReferenceGroup { val templateName = solidsName + name if (proto[templateName] == null) { solids.addSolid(root, solid, name) @@ -67,7 +67,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) { return ref } - private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReference { + private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReferenceGroup { val templateName = volumesName + volume.name.asName() if (proto[templateName] == null) { proto[templateName] = volume(root, volume) diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt index 692c334b..bec3822c 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt @@ -2,16 +2,10 @@ package hep.dataforge.vision.gdml import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.sequence -import hep.dataforge.meta.set -import hep.dataforge.names.Name -import hep.dataforge.names.toName -import hep.dataforge.vision.* +import hep.dataforge.vision.Vision +import hep.dataforge.vision.ownProperties +import hep.dataforge.vision.properties import hep.dataforge.vision.solid.* -import hep.dataforge.vision.visitor.VisionVisitor -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import mu.KotlinLogging public expect class Counter() { public fun get(): Int @@ -24,6 +18,7 @@ private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && o (this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0)) } +@DFExperimental internal fun Vision.updateFrom(other: Vision): Vision { if (this is Solid && other is Solid) { position = position.safePlus(other.position) @@ -33,9 +28,9 @@ internal fun Vision.updateFrom(other: Vision): Vision { scaleY = scaleY.toDouble() * other.scaleY.toDouble() scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() } - other.properties?.sequence()?.forEach { (name, item) -> - if (properties?.getItem(name) == null) { - config[name] = item + other.ownProperties?.sequence()?.forEach { (name, item) -> + if (properties.getItem(name) == null) { + setProperty(name, item) } } } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt index 9264ee99..dacc79bd 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt @@ -1,4 +1,3 @@ - package hep.dataforge.vision.solid import hep.dataforge.meta.update @@ -18,7 +17,7 @@ public enum class CompositeType { public class Composite( public val compositeType: CompositeType, public val first: Solid, - public val second: Solid + public val second: Solid, ) : SolidBase(), Solid, VisionGroup { init { @@ -34,15 +33,13 @@ public class Composite( public inline fun VisionContainerBuilder.composite( type: CompositeType, name: String = "", - builder: SolidGroup.() -> Unit + builder: SolidGroup.() -> Unit, ): Composite { val group = SolidGroup().apply(builder) val children = group.children.values.filterIsInstance() if (children.size != 2) error("Composite requires exactly two children") return Composite(type, children[0], children[1]).also { - it.config.update(group.config) - //it.material = group.material - + it.configure { update(group.ownProperties) } if (group.position != null) { it.position = group.position } @@ -65,5 +62,8 @@ public inline fun VisionContainerBuilder.subtract(name: String = "", buil composite(CompositeType.SUBTRACT, name, builder = builder) @VisionBuilder -public inline fun VisionContainerBuilder.intersect(name: String = "", builder: SolidGroup.() -> Unit): Composite = +public inline fun VisionContainerBuilder.intersect( + name: String = "", + builder: SolidGroup.() -> Unit, +): Composite = composite(CompositeType.INTERSECT, name, builder = builder) \ No newline at end of file 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 278cc9e7..a342c961 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 @@ -1,18 +1,17 @@ package hep.dataforge.vision.solid -import hep.dataforge.meta.* +import hep.dataforge.meta.boolean import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.meta.enum +import hep.dataforge.meta.int import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.values.ValueType import hep.dataforge.values.asValue -import hep.dataforge.vision.Vision +import hep.dataforge.vision.* import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY -import hep.dataforge.vision.VisionBuilder -import hep.dataforge.vision.enum import hep.dataforge.vision.layout.Output -import hep.dataforge.vision.setProperty import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY import hep.dataforge.vision.solid.Solid.Companion.LAYER_KEY @@ -90,7 +89,7 @@ public interface Solid : Vision { var result = +(solid.position?.hashCode() ?: 0) result = 31 * result + (solid.rotation?.hashCode() ?: 0) result = 31 * result + (solid.scale?.hashCode() ?: 0) - result = 31 * result + (solid.properties?.hashCode() ?: 0) + result = 31 * result + solid.properties.hashCode() return result } } @@ -100,9 +99,9 @@ public interface Solid : Vision { * Get the layer number this solid belongs to. Return 0 if layer is not defined. */ public var Solid.layer: Int - get() = properties?.getItem(LAYER_KEY).int ?: 0 + get() = properties.getItem(LAYER_KEY).int ?: 0 set(value) { - config[LAYER_KEY] = value.asValue() + setProperty(LAYER_KEY, value) } @VisionBuilder @@ -153,21 +152,21 @@ public var Solid.x: Number get() = position?.x ?: 0f set(value) { position().x = value.toDouble() - propertyChanged(Solid.X_POSITION_KEY) + notifyPropertyChanged(Solid.X_POSITION_KEY) } public var Solid.y: Number get() = position?.y ?: 0f set(value) { position().y = value.toDouble() - propertyChanged(Solid.Y_POSITION_KEY) + notifyPropertyChanged(Solid.Y_POSITION_KEY) } public var Solid.z: Number get() = position?.z ?: 0f set(value) { position().z = value.toDouble() - propertyChanged(Solid.Z_POSITION_KEY) + notifyPropertyChanged(Solid.Z_POSITION_KEY) } private fun Solid.rotation(): Point3D = @@ -177,21 +176,21 @@ public var Solid.rotationX: Number get() = rotation?.x ?: 0f set(value) { rotation().x = value.toDouble() - propertyChanged(Solid.X_ROTATION_KEY) + notifyPropertyChanged(Solid.X_ROTATION_KEY) } public var Solid.rotationY: Number get() = rotation?.y ?: 0f set(value) { rotation().y = value.toDouble() - propertyChanged(Solid.Y_ROTATION_KEY) + notifyPropertyChanged(Solid.Y_ROTATION_KEY) } public var Solid.rotationZ: Number get() = rotation?.z ?: 0f set(value) { rotation().z = value.toDouble() - propertyChanged(Solid.Z_ROTATION_KEY) + notifyPropertyChanged(Solid.Z_ROTATION_KEY) } private fun Solid.scale(): Point3D = @@ -201,19 +200,19 @@ public var Solid.scaleX: Number get() = scale?.x ?: 1f set(value) { scale().x = value.toDouble() - propertyChanged(Solid.X_SCALE_KEY) + notifyPropertyChanged(Solid.X_SCALE_KEY) } public var Solid.scaleY: Number get() = scale?.y ?: 1f set(value) { scale().y = value.toDouble() - propertyChanged(Solid.Y_SCALE_KEY) + notifyPropertyChanged(Solid.Y_SCALE_KEY) } public var Solid.scaleZ: Number get() = scale?.z ?: 1f set(value) { scale().z = value.toDouble() - propertyChanged(Solid.Z_SCALE_KEY) + notifyPropertyChanged(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 b87cdaab..e4b9f0f7 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 @@ -1,6 +1,6 @@ package hep.dataforge.vision.solid -import hep.dataforge.meta.Config +import hep.dataforge.meta.MetaItem import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.names.Name import hep.dataforge.names.NameToken @@ -87,7 +87,10 @@ public tailrec fun PrototypeHolder.getPrototype(name: Name): Solid? = prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name) @VisionBuilder -public fun VisionContainerBuilder.group(name: Name = Name.EMPTY, action: SolidGroup.() -> Unit = {}): SolidGroup = +public fun VisionContainerBuilder.group( + name: Name = Name.EMPTY, + action: SolidGroup.() -> Unit = {}, +): SolidGroup = SolidGroup().apply(action).also { set(name, it) } /** @@ -104,15 +107,10 @@ internal class Prototypes( children: Map = emptyMap(), ) : VisionGroupBase(), PrototypeHolder { - init { - this.childrenInternal.putAll(children) - } + override var parent: VisionGroup? = null - override var properties: Config? - get() = null - set(_) { - error("Can't define properties for prototypes block") - } + private val _children = HashMap(children) + override val children: Map get() = _children override val prototypes: MutableVisionGroup get() = this @@ -123,6 +121,21 @@ internal class Prototypes( } } + override fun getOwnProperty(name: Name): MetaItem<*>? = null + + override fun getProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, + ): MetaItem<*>? = null + + override fun setProperty(name: Name, item: MetaItem<*>?) { + TODO("Not yet implemented") + } + + override val descriptor: NodeDescriptor? = null + companion object : KSerializer { private val mapSerializer: KSerializer> = diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt index a01cc961..5f3c59bc 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt @@ -36,7 +36,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) { private fun PolymorphicModuleBuilder.solids() { subclass(SolidGroup.serializer()) - subclass(SolidReference.serializer()) + subclass(SolidReferenceGroup.serializer()) subclass(Composite.serializer()) subclass(Tube.serializer()) subclass(Box.serializer()) 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 aebd168d..8cf8755c 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 @@ -10,13 +10,10 @@ import hep.dataforge.values.Value import hep.dataforge.values.ValueType import hep.dataforge.values.asValue import hep.dataforge.values.string -import hep.dataforge.vision.Colors -import hep.dataforge.vision.VisionBuilder -import hep.dataforge.vision.setProperty +import hep.dataforge.vision.* import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY -import hep.dataforge.vision.widgetType @VisionBuilder public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) { @@ -115,7 +112,7 @@ public class SolidMaterial : Scheme() { } } -public val Solid.color: ColorAccessor get() = ColorAccessor(config, MATERIAL_COLOR_KEY) +public val Solid.color: ColorAccessor get() = ColorAccessor(properties, MATERIAL_COLOR_KEY) public var Solid.material: SolidMaterial? get() = getProperty(MATERIAL_KEY).node?.let { SolidMaterial.read(it) } @@ -123,11 +120,11 @@ public var Solid.material: SolidMaterial? @VisionBuilder public fun Solid.material(builder: SolidMaterial.() -> Unit) { - val node = config[MATERIAL_KEY].node + val node = properties.getItem(MATERIAL_KEY).node if (node != null) { SolidMaterial.update(node, builder) } else { - config[MATERIAL_KEY] = SolidMaterial(builder) + setProperty(MATERIAL_KEY, SolidMaterial(builder)) } } 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 deleted file mode 100644 index 4df85142..00000000 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt +++ /dev/null @@ -1,156 +0,0 @@ -package hep.dataforge.vision.solid - -import hep.dataforge.meta.* -import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.names.* -import hep.dataforge.vision.* -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlin.collections.set - -public abstract class AbstractReference : SolidBase(), VisionGroup { - public abstract val prototype: Solid - - override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence { - yield(properties?.get(name)) - yieldAll(getStyleItems(name)) - yield(prototype.getProperty(name)) - if (inherit) { - yield(parent?.getProperty(name, inherit)) - } - }.merge() - - override var styles: List - get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles - set(value) { - config[Vision.STYLE_KEY] = value - } - - override val allProperties: Laminate - get() = Laminate( - properties, - allStyles, - prototype.allProperties, - parent?.allProperties, - ) - - override fun attachChildren() { - //do nothing - } - - override val descriptor: NodeDescriptor get() = prototype.descriptor -} - -/** - * A reference [Solid] to reuse a template object - */ -@Serializable -@SerialName("solid.ref") -public class SolidReference( - public val templateName: Name, -) : AbstractReference(), Solid { - - /** - * Recursively search for defined template in the parent - */ - override val prototype: Solid - get() = (parent as? SolidGroup)?.getPrototype(templateName) - ?: error("Prototype with name $templateName not found in $parent") - - @Transient - private val propertyCache: HashMap = HashMap() - - - override val children: Map - get() = (prototype as? VisionGroup)?.children - ?.filter { !it.key.toString().startsWith("@") } - ?.mapValues { - ReferenceChild(it.key.asName()) - } ?: emptyMap() - - private fun childPropertyName(childName: Name, propertyName: Name): Name { - return NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName - } - - private fun prototypeFor(name: Name): Solid { - return (prototype as? SolidGroup)?.get(name) as? Solid - ?: error("Prototype with name $name not found in $this") - } - - //override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) }) - - /** - * A ProxyChild is created temporarily only to interact with properties, it does not store any values - * (properties are stored in external cache) and created and destroyed on-demand). - */ - public inner class ReferenceChild(public val name: Name) : AbstractReference() { - - override val prototype: Solid get() = prototypeFor(name) - - override val children: Map - get() = (prototype as? VisionGroup)?.children - ?.filter { !it.key.toString().startsWith("@") } - ?.mapValues { (key, _) -> - ReferenceChild(name + key.asName()) - } ?: emptyMap() - - override var properties: Config? - get() = propertyCache[name] - set(value) { - if (value == null) { - propertyCache.remove(name)?.also { - //Removing listener if it is present - removeChangeListener(this@SolidReference) - } - } else { - propertyCache[name] = value.also { - onPropertyChange(this@SolidReference) { propertyName -> - this@SolidReference.propertyChanged(childPropertyName(name, propertyName)) - } - } - } - } - - } - - public companion object { - public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child" - } -} - -/** - * Get a vision prototype if it is a [SolidReference] or vision itself if it is not - */ -public val Vision.prototype: Vision - get() = when (this) { - is AbstractReference -> prototype - else -> this - } - -/** - * Create ref for existing prototype - */ -public fun SolidGroup.ref( - templateName: Name, - name: String = "", -): SolidReference = SolidReference(templateName).also { set(name, it) } - -/** - * Add new [SolidReference] wrapping given object and automatically adding it to the prototypes - */ -public fun SolidGroup.ref( - name: String, - obj: Solid, - templateName: Name = name.toName(), -): SolidReference { - val existing = getPrototype(templateName) - if (existing == null) { - prototypes { - this[templateName] = obj - } - } else if (existing != obj) { - error("Can't add different prototype on top of existing one") - } - return this@ref.ref(templateName, name) -} diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReferenceGroup.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReferenceGroup.kt new file mode 100644 index 00000000..efc7b1cb --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReferenceGroup.kt @@ -0,0 +1,189 @@ +package hep.dataforge.vision.solid + +import hep.dataforge.meta.* +import hep.dataforge.meta.descriptors.NodeDescriptor +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.serialization.SerialName +import kotlinx.serialization.Serializable + +public interface SolidReference : Vision { + public val prototype: Solid +} + +/** + * A reference [Solid] to reuse a template object + */ +@Serializable +@SerialName("solid.ref") +public class SolidReferenceGroup( + public val templateName: Name, +) : SolidBase(), SolidReference, VisionGroup { + + /** + * Recursively search for defined template in the parent + */ + override val prototype: Solid + get() = (parent as? SolidGroup)?.getPrototype(templateName) + ?: error("Prototype with name $templateName not found in $parent") + + override val children: Map + get() = (prototype as? VisionGroup)?.children + ?.filter { !it.key.toString().startsWith("@") } + ?.mapValues { + ReferenceChild(it.key.asName()) + } ?: emptyMap() + + private fun childToken(childName: Name): NameToken = + NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + + private fun childPropertyName(childName: Name, propertyName: Name): Name = + childToken(childName) + propertyName + + private fun getChildProperty(childName: Name, propertyName: Name): MetaItem<*>? { + return getOwnProperty(childPropertyName(childName, propertyName)) + } + + private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem<*>?) { + setProperty(childPropertyName(childName, propertyName), item) + } + + private fun prototypeFor(name: Name): Solid { + return if(name.isEmpty()) prototype else { + (prototype as? SolidGroup)?.get(name) as? Solid + ?: error("Prototype with name $name not found in $this") + } + } + + override fun getProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, + ): MetaItem<*>? = sequence { + yield(getOwnProperty(name)) + if (includeStyles) { + yieldAll(getStyleItems(name)) + } + yield(prototype.getProperty(name, inherit, includeStyles, includeDefaults)) + if (inherit) { + yield(parent?.getProperty(name, inherit)) + } + }.merge() + + override fun attachChildren() { + //do nothing + } + + override val descriptor: NodeDescriptor get() = prototype.descriptor + + + /** + * A ProxyChild is created temporarily only to interact with properties, it does not store any values + * (properties are stored in external cache) and created and destroyed on-demand). + */ + private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup { + + override val prototype: Solid get() = prototypeFor(childName) + + override val children: Map + get() = (prototype as? VisionGroup)?.children + ?.filter { !it.key.toString().startsWith("@") } + ?.mapValues { (key, _) -> + ReferenceChild(childName + key.asName()) + } ?: emptyMap() + + override fun getOwnProperty(name: Name): MetaItem<*>? = getChildProperty(childName, name) + + override fun setProperty(name: Name, item: MetaItem<*>?) { + setChildProperty(childName, name, item) + } + + override fun getProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, + ): MetaItem<*>? = sequence { + yield(getOwnProperty(name)) + if (includeStyles) { + yieldAll(getStyleItems(name)) + } + yield(prototype.getProperty(name, inherit, includeStyles, includeDefaults)) + if (inherit) { + yield(parent?.getProperty(name, inherit)) + } + }.merge() + + override var parent: VisionGroup? + get(){ + val parentName = childName.cutLast() + return if( parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName) + } + set(value) { + error("Setting a parent for a reference child is not possible") + } + + override val propertyInvalidated: Flow + get() = this@SolidReferenceGroup.propertyInvalidated.filter { name -> + name.startsWith(childToken(childName)) + }.map { name -> + name.cutFirst() + } + + override fun notifyPropertyChanged(propertyName: Name) { + this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName)) + } + + override fun update(change: VisionChange) { + TODO("Not yet implemented") + } + + override fun attachChildren() { + //do nothing + } + + override val descriptor: NodeDescriptor get() = prototype.descriptor + + } + + public companion object { + public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child" + } +} + +/** + * Get a vision prototype if it is a [SolidReferenceGroup] or vision itself if it is not + */ +public val Vision.prototype: Vision + get() = if (this is SolidReference) prototype else this + +/** + * Create ref for existing prototype + */ +public fun SolidGroup.ref( + templateName: Name, + name: String = "", +): SolidReferenceGroup = SolidReferenceGroup(templateName).also { set(name, it) } + +/** + * Add new [SolidReferenceGroup] wrapping given object and automatically adding it to the prototypes + */ +public fun SolidGroup.ref( + name: String, + obj: Solid, + templateName: Name = name.toName(), +): SolidReferenceGroup { + val existing = getPrototype(templateName) + if (existing == null) { + prototypes { + this[templateName] = obj + } + } else if (existing != obj) { + error("Can't add different prototype on top of existing one") + } + return this@ref.ref(templateName, name) +} diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt index ff879d7e..0b64a7b7 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt @@ -1,18 +1,15 @@ package hep.dataforge.vision.solid.transform import hep.dataforge.meta.DFExperimental -import hep.dataforge.meta.update import hep.dataforge.names.asName -import hep.dataforge.vision.MutableVisionGroup -import hep.dataforge.vision.Vision -import hep.dataforge.vision.VisionGroup +import hep.dataforge.vision.* import hep.dataforge.vision.solid.* @DFExperimental internal fun mergeChild(parent: VisionGroup, child: Vision): Vision { return child.apply { - config.update(parent.config) + configure(parent.ownProperties) //parent.properties?.let { config.update(it) } @@ -40,7 +37,7 @@ internal object RemoveSingleChild : VisualTreeTransform() { override fun SolidGroup.transformInPlace() { fun MutableVisionGroup.replaceChildren() { children.forEach { (childName, parent) -> - if (parent is SolidReference) return@forEach //ignore refs + if (parent is SolidReferenceGroup) return@forEach //ignore refs if (parent is MutableVisionGroup) { parent.replaceChildren() } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/UnRef.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/UnRef.kt index d45c2ca2..cb0ac4e2 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/UnRef.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/UnRef.kt @@ -6,7 +6,7 @@ import hep.dataforge.names.asName import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.solid.SolidGroup -import hep.dataforge.vision.solid.SolidReference +import hep.dataforge.vision.solid.SolidReferenceGroup @DFExperimental internal object UnRef : VisualTreeTransform() { @@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform() { counter.forEach { (key, value) -> reducer[key] = (reducer[key] ?: 0) + value } - } else if (obj is SolidReference) { + } else if (obj is SolidReferenceGroup) { reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1 } @@ -27,8 +27,8 @@ internal object UnRef : VisualTreeTransform() { private fun MutableVisionGroup.unref(name: Name) { (this as? SolidGroup)?.prototypes?.set(name, null) - children.filter { (it.value as? SolidReference)?.templateName == name }.forEach { (key, value) -> - val reference = value as SolidReference + children.filter { (it.value as? SolidReferenceGroup)?.templateName == name }.forEach { (key, value) -> + val reference = value as SolidReferenceGroup val newChild = mergeChild(reference, reference.prototype) newChild.parent = null set(key.asName(), newChild) // replace proxy with merged object 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 907edf3e..6e045744 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 @@ -1,8 +1,8 @@ package hep.dataforge.vision.solid import hep.dataforge.meta.int -import hep.dataforge.meta.set import hep.dataforge.names.asName +import hep.dataforge.vision.setProperty import hep.dataforge.vision.styleSheet import hep.dataforge.vision.useStyle import kotlin.test.Test @@ -14,7 +14,7 @@ class PropertyTest { fun testInheritedProperty() { var box: Box? = null val group = SolidGroup().apply { - config["test"] = 22 + setProperty("test", 22) group { box = box(100, 100, 100) } @@ -60,7 +60,7 @@ class PropertyTest { @Test fun testReferenceStyleProperty() { - var box: SolidReference? = null + var box: SolidReferenceGroup? = null val group = SolidGroup{ styleSheet { set("testStyle") { diff --git a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt index efef7fcd..017d4db3 100644 --- a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt @@ -4,6 +4,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.get +import hep.dataforge.vision.ownProperties import kotlin.test.Test import kotlin.test.assertEquals @@ -15,7 +16,7 @@ fun SolidGroup.refGroup( name: String, templateName: Name = name.toName(), block: MutableVisionGroup.() -> Unit -): SolidReference { +): SolidReferenceGroup { val group = SolidGroup().apply(block) return ref(name, group, templateName) } @@ -32,7 +33,7 @@ class SerializationTest { val string = SolidManager.encodeToString(cube) println(string) val newCube = SolidManager.decodeFromString(string) - assertEquals(cube.config, newCube.config) + assertEquals(cube.ownProperties, newCube.ownProperties) } @Test @@ -53,7 +54,7 @@ class SerializationTest { val string = SolidManager.encodeToString(group) println(string) val reconstructed = SolidManager.decodeFromString(string) as SolidGroup - assertEquals(group["cube"]?.config, reconstructed["cube"]?.config) + assertEquals(group["cube"]?.ownProperties, reconstructed["cube"]?.ownProperties) } } \ No newline at end of file diff --git a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt index f5ff4f14..79bba867 100644 --- a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt @@ -45,7 +45,7 @@ class VisionUpdateTest { val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change) println(serialized) val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized) - assertEquals(change.propertyChange,reconstructed.propertyChange) + assertEquals(change.properties,reconstructed.properties) } @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 44478c61..519fa9b3 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,20 +15,22 @@ 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 /** * Basic geometry-based factory */ public abstract class MeshThreeFactory( - override val type: KClass + override val type: KClass, ) : ThreeFactory { /** * Build a geometry for an object */ public abstract fun buildGeometry(obj: T): BufferGeometry - override fun invoke(obj: T): Mesh { + override fun invoke(three: ThreePlugin, obj: T): Mesh { val geometry = buildGeometry(obj) //JS sometimes tries to pass Geometry as BufferGeometry @@ -36,14 +38,14 @@ public abstract class MeshThreeFactory( //val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty - val mesh = Mesh(geometry, null).apply{ + val mesh = Mesh(geometry, null).apply { matrixAutoUpdate = false //set position for mesh updatePosition(obj) }.applyProperties(obj) //add listener to object properties - obj.onPropertyChange(this) { name -> + obj.propertyInvalidated.onEach { name -> when { name.startsWith(Solid.GEOMETRY_KEY) -> { val oldGeometry = mesh.geometry as BufferGeometry @@ -57,7 +59,8 @@ public abstract class MeshThreeFactory( name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj) else -> mesh.updateProperty(obj, name) } - } + }.launchIn(three.updateScope) + return mesh } @@ -72,7 +75,7 @@ public abstract class MeshThreeFactory( } } -fun Mesh.applyProperties(obj: Solid): Mesh = apply{ +internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { material = getMaterial(obj, true) applyEdges(obj) applyWireFrame(obj) @@ -82,7 +85,7 @@ fun Mesh.applyProperties(obj: Solid): Mesh = apply{ } } -fun Mesh.applyEdges(obj: Solid) { +internal 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) { @@ -108,7 +111,7 @@ fun Mesh.applyEdges(obj: Solid) { } } -fun Mesh.applyWireFrame(obj: Solid) { +internal fun Mesh.applyWireFrame(obj: Solid) { children.find { it.name == "@wireframe" }?.let { remove(it) (it as LineSegments).dispose() @@ -116,7 +119,8 @@ fun Mesh.applyWireFrame(obj: Solid) { //inherited wireframe definition, disabled by default if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) { val bufferGeometry = geometry as? BufferGeometry ?: return - val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true) + val material = + ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true) add( LineSegments( WireframeGeometry(bufferGeometry), diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeConvexFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeConvexFactory.kt index 0f5160f8..e41035c9 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeConvexFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeConvexFactory.kt @@ -2,11 +2,10 @@ package hep.dataforge.vision.solid.three import hep.dataforge.vision.solid.Convex import info.laht.threekt.external.geometries.ConvexBufferGeometry -import info.laht.threekt.math.Vector3 public object ThreeConvexFactory : MeshThreeFactory(Convex::class) { override fun buildGeometry(obj: Convex): ConvexBufferGeometry { - @Suppress("USELESS_CAST") val vectors = obj.points.toTypedArray() as Array + val vectors = obj.points.map { it.toVector() }.toTypedArray() return ConvexBufferGeometry(vectors) } } \ No newline at end of file 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 d01eed4c..33cfcc60 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 @@ -22,7 +22,7 @@ public interface ThreeFactory { public val type: KClass - public operator fun invoke(obj: T): Object3D + public operator fun invoke(three: ThreePlugin, obj: T): Object3D public companion object { public const val TYPE: String = "threeFactory" 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 ecea5d23..9ab3f401 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 @@ -1,12 +1,15 @@ package hep.dataforge.vision.solid.three +import hep.dataforge.context.logger import hep.dataforge.vision.solid.SolidLabel import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial 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 /** @@ -15,18 +18,19 @@ import kotlin.reflect.KClass public object ThreeLabelFactory : ThreeFactory { override val type: KClass get() = SolidLabel::class - override fun invoke(obj: SolidLabel): Object3D { + override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D { val textGeo = TextBufferGeometry(obj.text, jsObject { font = obj.fontFamily size = 20 height = 1 curveSegments = 1 }) - return Mesh(textGeo, getMaterial(obj,true)).apply { + return Mesh(textGeo, getMaterial(obj, true)).apply { updatePosition(obj) - obj.onPropertyChange(this@ThreeLabelFactory) { _ -> + obj.propertyInvalidated.onEach { _ -> //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 b5ec871e..199213ba 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,12 +9,14 @@ 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 { override val type: KClass get() = PolyLine::class - override fun invoke(obj: PolyLine): Object3D { + override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D { val geometry = Geometry().apply { vertices = Array(obj.points.size) { obj.points[it].toVector() } } @@ -28,9 +30,9 @@ public object ThreeLineFactory : ThreeFactory { updatePosition(obj) //layers.enable(obj.layer) //add listener to object properties - obj.onPropertyChange(this) { propertyName -> + obj.propertyInvalidated.onEach { propertyName -> updateProperty(obj, propertyName) - } + }.launchIn(three.updateScope) } } 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 bcf309bb..9eacdea9 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 @@ -9,6 +9,9 @@ import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.specifications.Canvas3DOptions import hep.dataforge.vision.visible import info.laht.threekt.core.Object3D +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.w3c.dom.Element import org.w3c.dom.HTMLElement import kotlin.collections.set @@ -24,6 +27,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { private val compositeFactory = ThreeCompositeFactory(this) private val refFactory = ThreeReferenceFactory(this) + //TODO generate a separate supervisor update scope + internal val updateScope: CoroutineScope get() = context + init { //Add specialized factories here objectFactories[Box::class] = ThreeBoxFactory @@ -44,7 +50,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { public fun buildObject3D(obj: Solid): Object3D { return when (obj) { is ThreeVision -> obj.render() - is SolidReference -> refFactory(obj) + is SolidReferenceGroup -> refFactory(obj) is SolidGroup -> { val group = ThreeGroup() obj.children.forEach { (token, child) -> @@ -63,7 +69,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { updatePosition(obj) //obj.onChildrenChange() - obj.onPropertyChange(this) { name -> + obj.propertyInvalidated.onEach { name -> if ( name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.ROTATION) || @@ -74,9 +80,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } else if (name == Vision.VISIBLE_KEY) { visible = obj.visible ?: true } - } + }.launchIn(updateScope) - obj.onStructureChange(this) { nameToken, _, child -> + obj.structureChanges.onEach { (nameToken, _, child) -> // if (name.isEmpty()) { // logger.error { "Children change with empty name on $group" } // return@onChildrenChange @@ -99,7 +105,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { logger.error(ex) { "Failed to render $child" } } } - } + }.launchIn(updateScope) } } is Composite -> compositeFactory(obj) 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 32ea564f..02739b1c 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 @@ -4,17 +4,19 @@ import hep.dataforge.names.cutFirst import hep.dataforge.names.firstOrNull import hep.dataforge.names.toName import hep.dataforge.vision.solid.Solid -import hep.dataforge.vision.solid.SolidReference -import hep.dataforge.vision.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX +import hep.dataforge.vision.solid.SolidReferenceGroup +import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_PROPERTY_PREFIX 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 class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory { +public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory { private val cache = HashMap() - override val type: KClass = SolidReference::class + override val type: KClass = SolidReferenceGroup::class private fun Object3D.replicate(): Object3D { return when (this) { @@ -30,7 +32,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory } } - override fun invoke(obj: SolidReference): Object3D { + override fun invoke(obj: SolidReferenceGroup): Object3D { val template = obj.prototype val cachedObject = cache.getOrPut(template) { three.buildObject3D(template) @@ -43,7 +45,9 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory object3D.applyProperties(obj) } - obj.onPropertyChange(this) { name -> + //TODO apply child properties + + obj.propertyInvalidated.onEach { 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() @@ -53,7 +57,8 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory } else { object3D.updateProperty(obj, name) } - } + }.launchIn(three.updateScope) + return object3D }