Change property and structure subscription to flows.
This commit is contained in:
parent
85bb690699
commit
b2ba92e745
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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<NameToken, Meta>? 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<Name> =
|
||||
((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
|
||||
|
@ -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<Name>
|
||||
|
||||
|
||||
/**
|
||||
* 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<String>
|
||||
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<Any?, MetaItem<*>?> {
|
||||
// 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<*>? {
|
||||
|
@ -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<String>) {
|
||||
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<PropertyListener>()
|
||||
|
||||
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<String>) {
|
||||
names.mapNotNull { getStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
notifyPropertyChanged(it.key.asName())
|
||||
}
|
||||
}
|
||||
|
||||
private val _propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow(
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
|
||||
override val propertyInvalidated: SharedFlow<Name> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Vision> {
|
||||
|
||||
private val propertyChange = HashMap<Name, Config>()
|
||||
private val childrenChange = HashMap<Name, Vision?>()
|
||||
private var reset: Boolean = false
|
||||
private var vision: Vision? = null
|
||||
private val propertyChange = Config()
|
||||
private val children: HashMap<Name, VisionChangeBuilder> = 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<Name, @Serializable(MetaSerializer::class) Meta>,
|
||||
val childrenChange: Map<Name, Vision?>,
|
||||
public class VisionChange(
|
||||
public val reset: Boolean = false,
|
||||
public val vision: Vision? = null,
|
||||
public val properties: Meta? = null,
|
||||
public val children: Map<Name, VisionChange>? = 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<VisionChange> = 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<out V : Vision> {
|
||||
public operator fun get(name: Name): V?
|
||||
@ -75,19 +76,12 @@ public interface VisionContainerBuilder<in V : Vision> {
|
||||
*/
|
||||
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
|
||||
|
||||
/**
|
||||
* 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<StructureChange>
|
||||
}
|
||||
|
||||
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
|
||||
|
@ -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<NameToken, Vision> 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<MutableVisionGroup.StructureChange> = MutableSharedFlow(
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
|
||||
@Transient
|
||||
private val structureChangeListeners = HashSet<StructureChangeListener>()
|
||||
|
||||
/**
|
||||
* 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<MutableVisionGroup.StructureChange> 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)
|
||||
}
|
||||
|
@ -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 <reified E : Enum<E>> NodeDescriptor.enum(key: Name, default:
|
||||
default(default)
|
||||
}
|
||||
allowedValues = enumValues<E>().map { it.asValue() }
|
||||
}
|
||||
}
|
||||
|
||||
@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))
|
@ -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() }
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -6,15 +6,15 @@ import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReference> {
|
||||
override val type: KClass<in SolidReference> get() = SolidReference::class
|
||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
|
||||
override val type: KClass<in SolidReferenceGroup> 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")
|
||||
|
@ -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<Name, MutableList<SolidReference>>()
|
||||
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
|
||||
|
||||
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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Solid>.composite(
|
||||
type: CompositeType,
|
||||
name: String = "",
|
||||
builder: SolidGroup.() -> Unit
|
||||
builder: SolidGroup.() -> Unit,
|
||||
): Composite {
|
||||
val group = SolidGroup().apply(builder)
|
||||
val children = group.children.values.filterIsInstance<Solid>()
|
||||
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<Solid>.subtract(name: String = "", buil
|
||||
composite(CompositeType.SUBTRACT, name, builder = builder)
|
||||
|
||||
@VisionBuilder
|
||||
public inline fun VisionContainerBuilder<Solid>.intersect(name: String = "", builder: SolidGroup.() -> Unit): Composite =
|
||||
public inline fun VisionContainerBuilder<Solid>.intersect(
|
||||
name: String = "",
|
||||
builder: SolidGroup.() -> Unit,
|
||||
): Composite =
|
||||
composite(CompositeType.INTERSECT, name, builder = builder)
|
@ -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)
|
||||
}
|
@ -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<Vision>.group(name: Name = Name.EMPTY, action: SolidGroup.() -> Unit = {}): SolidGroup =
|
||||
public fun VisionContainerBuilder<Vision>.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<NameToken, Vision> = 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<NameToken, Vision> 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<MutableVisionGroup> {
|
||||
|
||||
private val mapSerializer: KSerializer<Map<NameToken, Vision>> =
|
||||
|
@ -36,7 +36,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
|
||||
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
||||
subclass(SolidGroup.serializer())
|
||||
subclass(SolidReference.serializer())
|
||||
subclass(SolidReferenceGroup.serializer())
|
||||
subclass(Composite.serializer())
|
||||
subclass(Tube.serializer())
|
||||
subclass(Box.serializer())
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<String>
|
||||
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<Name, Config> = HashMap()
|
||||
|
||||
|
||||
override val children: Map<NameToken, SolidReference.ReferenceChild>
|
||||
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<NameToken, Vision>
|
||||
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)
|
||||
}
|
@ -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<NameToken, Vision>
|
||||
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<NameToken, Vision>
|
||||
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<Name>
|
||||
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)
|
||||
}
|
@ -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<SolidGroup>() {
|
||||
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()
|
||||
}
|
||||
|
@ -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<SolidGroup>() {
|
||||
@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
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<SolidGroup>() {
|
||||
|
||||
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
|
||||
|
@ -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") {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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<in T : Solid>(
|
||||
override val type: KClass<in T>
|
||||
override val type: KClass<in T>,
|
||||
) : ThreeFactory<T> {
|
||||
/**
|
||||
* 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<in T : Solid>(
|
||||
|
||||
//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<in T : Solid>(
|
||||
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<in T : Solid>(
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
|
@ -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>(Convex::class) {
|
||||
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
|
||||
@Suppress("USELESS_CAST") val vectors = obj.points.toTypedArray() as Array<Vector3>
|
||||
val vectors = obj.points.map { it.toVector() }.toTypedArray()
|
||||
return ConvexBufferGeometry(vectors)
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ public interface ThreeFactory<in T : Vision> {
|
||||
|
||||
public val type: KClass<in T>
|
||||
|
||||
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"
|
||||
|
@ -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<SolidLabel> {
|
||||
override val type: KClass<in SolidLabel> 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<PolyLine> {
|
||||
override val type: KClass<PolyLine> 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<PolyLine> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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<SolidReference> {
|
||||
public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReferenceGroup> {
|
||||
private val cache = HashMap<Solid, Object3D>()
|
||||
|
||||
override val type: KClass<SolidReference> = SolidReference::class
|
||||
override val type: KClass<SolidReferenceGroup> = 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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user