Change property and structure subscription to flows.

This commit is contained in:
Alexander Nozik 2020-12-15 19:18:04 +03:00
parent 85bb690699
commit b2ba92e745
34 changed files with 562 additions and 518 deletions

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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<*>? {

View File

@ -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)
}
}

View File

@ -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()
}
}
}
}

View File

@ -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())

View File

@ -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)
}

View File

@ -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))

View File

@ -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() }

View File

@ -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)
}

View File

@ -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 {

View File

@ -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")

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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>> =

View File

@ -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())

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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

View File

@ -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") {

View File

@ -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)
}
}

View File

@ -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

View File

@ -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),

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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
}