Fixing new property update model

This commit is contained in:
Alexander Nozik 2020-12-16 13:24:38 +03:00
parent 929832f3a5
commit 6a6d9659ca
17 changed files with 55 additions and 50 deletions

View File

@ -2,7 +2,7 @@ plugins {
id("ru.mipt.npm.project") id("ru.mipt.npm.project")
} }
val dataforgeVersion by extra("0.2.1-dev-4") val dataforgeVersion by extra("0.2.1-dev-5")
val ktorVersion by extra("1.4.3") val ktorVersion by extra("1.4.3")
val htmlVersion by extra("0.7.2") val htmlVersion by extra("0.7.2")
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20") val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20")

View File

@ -5,6 +5,7 @@ import hep.dataforge.names.plus
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import hep.dataforge.vision.getProperty import hep.dataforge.vision.getProperty
import hep.dataforge.vision.properties
import hep.dataforge.vision.set import hep.dataforge.vision.set
import hep.dataforge.vision.setProperty import hep.dataforge.vision.setProperty
import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.*
@ -16,6 +17,8 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.math.max import kotlin.math.max
internal fun SolidGroup.varBox( internal fun SolidGroup.varBox(
@ -31,11 +34,11 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
scaleX = xSize scaleX = xSize
scaleY = ySize scaleY = ySize
scaleZ = zSize scaleZ = zSize
config[MeshThreeFactory.EDGES_ENABLED_KEY] = false properties[MeshThreeFactory.EDGES_ENABLED_KEY] = false
config[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false properties[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false
} }
override fun render(): Object3D { override fun render(three: ThreePlugin): Object3D {
val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0 val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0 val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0 val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
@ -60,7 +63,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
mesh.scale.set(xSize, ySize, zSize) mesh.scale.set(xSize, ySize, zSize)
//add listener to object properties //add listener to object properties
onPropertyChange(mesh) { name -> propertyInvalidated.onEach { name ->
when { when {
name.startsWith(GEOMETRY_KEY) -> { name.startsWith(GEOMETRY_KEY) -> {
val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0 val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
@ -76,7 +79,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
} }
else -> mesh.updateProperty(this@VariableBox, name) else -> mesh.updateProperty(this@VariableBox, name)
} }
} }.launchIn(three.context)
return mesh return mesh
} }

View File

@ -4,6 +4,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.describedProperties
import hep.dataforge.vision.react.objectTree import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.solid.three.ThreeCanvas import hep.dataforge.vision.solid.three.ThreeCanvas
import kotlinx.css.* import kotlinx.css.*
@ -53,7 +54,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
if (selectedObject != null) { if (selectedObject != null) {
visionPropertyEditor( visionPropertyEditor(
selectedObject, selectedObject,
default = selectedObject.allProperties, default = selectedObject.describedProperties,
key = selected key = selected
) )
} }

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.getStyle import hep.dataforge.vision.getStyle
import hep.dataforge.vision.properties
import hep.dataforge.vision.react.configEditor import hep.dataforge.vision.react.configEditor
import hep.dataforge.vision.react.metaViewer import hep.dataforge.vision.react.metaViewer
import org.w3c.dom.Element import org.w3c.dom.Element
@ -17,7 +18,7 @@ public fun RBuilder.visionPropertyEditor(
key: Any? = null key: Any? = null
) { ) {
card("Properties") { card("Properties") {
configEditor(item.config, descriptor, default, key) configEditor(item.properties, descriptor, default, key)
} }
val styles = item.styles val styles = item.styles
if(styles.isNotEmpty()) { if(styles.isNotEmpty()) {

View File

@ -19,7 +19,7 @@ public external interface ConfigEditorItemProps : RProps {
/** /**
* Root config object - always non null * Root config object - always non null
*/ */
public var root: Config public var root: MutableItemProvider
/** /**
* Full path to the displayed node in [root]. Could be empty * Full path to the displayed node in [root]. Could be empty
@ -44,7 +44,7 @@ private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> =
private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
var expanded: Boolean by useState { true } var expanded: Boolean by useState { true }
var item: MetaItem<Config>? by useState { props.root[props.name] } var item: MetaItem<*>? by useState { props.root.getItem(props.name) }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val defaultItem = props.default?.get(props.name) val defaultItem = props.default?.get(props.name)
var actualItem: MetaItem<Meta>? by useState { item ?: defaultItem ?: descriptorItem?.defaultItem() } var actualItem: MetaItem<Meta>? by useState { item ?: defaultItem ?: descriptorItem?.defaultItem() }
@ -52,7 +52,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
val token = props.name.lastOrNull()?.toString() ?: "Properties" val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() { fun update() {
item = props.root[props.name] item = props.root.getItem(props.name)
actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem() actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
} }
@ -192,7 +192,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
public external interface ConfigEditorProps : RProps { public external interface ConfigEditorProps : RProps {
public var id: Name public var id: Name
public var root: Config public var root: MutableItemProvider
public var default: Meta? public var default: Meta?
public var descriptor: NodeDescriptor? public var descriptor: NodeDescriptor?
} }
@ -229,7 +229,7 @@ public fun Element.configEditor(
} }
public fun RBuilder.configEditor( public fun RBuilder.configEditor(
config: Config, config: MutableItemProvider,
descriptor: NodeDescriptor? = null, descriptor: NodeDescriptor? = null,
default: Meta? = null, default: Meta? = null,
key: Any? = null, key: Any? = null,

View File

@ -8,7 +8,6 @@ import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.vision.Vision.Companion.STYLE_KEY import hep.dataforge.vision.Vision.Companion.STYLE_KEY
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -32,32 +31,33 @@ public open class VisionBase : Vision {
* Object own properties excluding styles and inheritance * Object own properties excluding styles and inheritance
*/ */
@SerialName("properties") @SerialName("properties")
private var _properties: Config? = null protected var innerProperties: Config? = null
private set
/** /**
* All own properties as a read-only Meta * All own properties as a read-only Meta
*/ */
public val ownProperties: Meta get() = _properties?: Meta.EMPTY public val ownProperties: Meta get() = innerProperties ?: Meta.EMPTY
@Synchronized @Synchronized
private fun getOrCreateConfig(): Config { private fun getOrCreateConfig(): Config {
if (_properties == null) { if (innerProperties == null) {
val newProperties = Config() val newProperties = Config()
_properties = newProperties innerProperties = newProperties
newProperties.onChange(this) { name, oldItem, newItem -> newProperties.onChange(this) { name, oldItem, newItem ->
if (oldItem != newItem) { if (oldItem != newItem) {
notifyPropertyChanged(name) notifyPropertyChanged(name)
} }
} }
} }
return _properties!! return innerProperties!!
} }
/** /**
* A fast accessor method to get own property (no inheritance or styles * A fast accessor method to get own property (no inheritance or styles
*/ */
override fun getOwnProperty(name: Name): MetaItem<*>? { override fun getOwnProperty(name: Name): MetaItem<*>? {
return _properties?.getItem(name) return innerProperties?.getItem(name)
} }
override fun getProperty( override fun getProperty(
@ -93,9 +93,8 @@ public open class VisionBase : Vision {
} }
} }
private val _propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow( @Transient
onBufferOverflow = BufferOverflow.DROP_OLDEST private val _propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()
)
override val propertyInvalidated: SharedFlow<Name> get() = _propertyInvalidationFlow override val propertyInvalidated: SharedFlow<Name> get() = _propertyInvalidationFlow

View File

@ -1,7 +1,6 @@
package hep.dataforge.vision package hep.dataforge.vision
import hep.dataforge.names.* import hep.dataforge.names.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -36,9 +35,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
} }
@Transient @Transient
private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow( private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow()
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges

View File

@ -18,6 +18,10 @@ import org.w3c.dom.WebSocket
import org.w3c.dom.asList import org.w3c.dom.asList
import org.w3c.dom.get import org.w3c.dom.get
import org.w3c.dom.url.URL import org.w3c.dom.url.URL
import kotlin.collections.HashMap
import kotlin.collections.forEach
import kotlin.collections.maxByOrNull
import kotlin.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class VisionClient : AbstractPlugin() { public class VisionClient : AbstractPlugin() {
@ -124,7 +128,7 @@ public class VisionClient : AbstractPlugin() {
) )
logger.debug { "Got update $dif for output with name $name" } logger.debug { "Got update $dif for output with name $name" }
visionMap[element]?.update(dif) visionMap[element]?.update(dif)
?: logger.info { "Target vision for element $element with name $name not found" } ?: console.info("Target vision for element $element with name $name not found")
} else { } else {
console.error ("WebSocket message data is not a string") console.error ("WebSocket message data is not a string")
} }

View File

@ -81,7 +81,7 @@ public interface Solid : Vision {
if (first.position != second.position) return false if (first.position != second.position) return false
if (first.rotation != second.rotation) return false if (first.rotation != second.rotation) return false
if (first.scale != second.scale) return false if (first.scale != second.scale) return false
if (first.properties != second.properties) return false if (first.ownProperties != second.ownProperties) return false
return true return true
} }

View File

@ -38,6 +38,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
/** /**
* Create or edit prototype node as a group * Create or edit prototype node as a group
*/ */
@VisionBuilder
public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit { public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
(prototypes ?: Prototypes().also { (prototypes ?: Prototypes().also {
prototypes = it prototypes = it
@ -107,10 +108,9 @@ internal class Prototypes(
children: Map<NameToken, Vision> = emptyMap(), children: Map<NameToken, Vision> = emptyMap(),
) : VisionGroupBase(), PrototypeHolder { ) : VisionGroupBase(), PrototypeHolder {
override var parent: VisionGroup? = null init {
childrenInternal.putAll(children)
private val _children = HashMap(children) }
override val children: Map<NameToken, Vision> get() = _children
override val prototypes: MutableVisionGroup get() = this override val prototypes: MutableVisionGroup get() = this

View File

@ -2,6 +2,7 @@ package hep.dataforge.vision.solid
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.vision.getProperty
import hep.dataforge.vision.setProperty import hep.dataforge.vision.setProperty
import hep.dataforge.vision.styleSheet import hep.dataforge.vision.styleSheet
import hep.dataforge.vision.useStyle import hep.dataforge.vision.useStyle
@ -19,7 +20,7 @@ class PropertyTest {
box = box(100, 100, 100) box = box(100, 100, 100)
} }
} }
assertEquals(22, box?.getProperty("test".asName()).int) assertEquals(22, box?.getProperty("test").int)
} }
@Test @Test
@ -37,7 +38,7 @@ class PropertyTest {
} }
} }
} }
assertEquals(22, box?.getProperty("test".asName()).int) assertEquals(22, box?.getProperty("test").int)
} }
@Test @Test

View File

@ -85,7 +85,7 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
} }
} }
internal fun Mesh.applyEdges(obj: Solid) { public fun Mesh.applyEdges(obj: Solid) {
val edges = children.find { it.name == "@edges" } as? LineSegments val edges = children.find { it.name == "@edges" } as? LineSegments
//inherited edges definition, enabled by default //inherited edges definition, enabled by default
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) { if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
@ -111,7 +111,7 @@ internal fun Mesh.applyEdges(obj: Solid) {
} }
} }
internal fun Mesh.applyWireFrame(obj: Solid) { public fun Mesh.applyWireFrame(obj: Solid) {
children.find { it.name == "@wireframe" }?.let { children.find { it.name == "@wireframe" }?.let {
remove(it) remove(it)
(it as LineSegments).dispose() (it as LineSegments).dispose()

View File

@ -22,7 +22,7 @@ import kotlin.reflect.KClass
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> { public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(obj: SolidLabel): Object3D { override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
val canvas = document.createElement("canvas") as HTMLCanvasElement val canvas = document.createElement("canvas") as HTMLCanvasElement
val context = canvas.getContext("2d") as CanvasRenderingContext2D val context = canvas.getContext("2d") as CanvasRenderingContext2D
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"

View File

@ -25,7 +25,6 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>() private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
private val compositeFactory = ThreeCompositeFactory(this) private val compositeFactory = ThreeCompositeFactory(this)
private val refFactory = ThreeReferenceFactory(this)
//TODO generate a separate supervisor update scope //TODO generate a separate supervisor update scope
internal val updateScope: CoroutineScope get() = context internal val updateScope: CoroutineScope get() = context
@ -49,8 +48,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun buildObject3D(obj: Solid): Object3D { public fun buildObject3D(obj: Solid): Object3D {
return when (obj) { return when (obj) {
is ThreeVision -> obj.render() is ThreeVision -> obj.render(this)
is SolidReferenceGroup -> refFactory(obj) is SolidReferenceGroup -> ThreeReferenceFactory(this, obj)
is SolidGroup -> { is SolidGroup -> {
val group = ThreeGroup() val group = ThreeGroup()
obj.children.forEach { (token, child) -> obj.children.forEach { (token, child) ->
@ -108,13 +107,13 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
}.launchIn(updateScope) }.launchIn(updateScope)
} }
} }
is Composite -> compositeFactory(obj) is Composite -> compositeFactory(this, obj)
else -> { else -> {
//find specialized factory for this type if it is present //find specialized factory for this type if it is present
val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class) val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class)
when { when {
factory != null -> factory(obj) factory != null -> factory(this, obj)
obj is GeometrySolid -> ThreeShapeFactory(obj) obj is GeometrySolid -> ThreeShapeFactory(this, obj)
else -> error("Renderer for ${obj::class} not found") else -> error("Renderer for ${obj::class} not found")
} }
} }

View File

@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReferenceGroup> { public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
private val cache = HashMap<Solid, Object3D>() private val cache = HashMap<Solid, Object3D>()
override val type: KClass<SolidReferenceGroup> = SolidReferenceGroup::class override val type: KClass<SolidReferenceGroup> = SolidReferenceGroup::class
@ -32,7 +32,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
} }
} }
override fun invoke(obj: SolidReferenceGroup): Object3D { override fun invoke(three: ThreePlugin, obj: SolidReferenceGroup): Object3D {
val template = obj.prototype val template = obj.prototype
val cachedObject = cache.getOrPut(template) { val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template) three.buildObject3D(template)

View File

@ -7,5 +7,5 @@ import info.laht.threekt.core.Object3D
* A custom visual object that has its own Three.js renderer * A custom visual object that has its own Three.js renderer
*/ */
public abstract class ThreeVision : SolidBase() { public abstract class ThreeVision : SolidBase() {
public abstract fun render(): Object3D public abstract fun render(three: ThreePlugin): Object3D
} }