Another huge refactoring of property updates

This commit is contained in:
Alexander Nozik 2020-12-20 21:43:29 +03:00
parent 4c235b0f53
commit 0259d4eb15
27 changed files with 238 additions and 148 deletions

View File

@ -42,7 +42,7 @@ fun main() {
val targetVision = sat[target] as Solid val targetVision = sat[target] as Solid
targetVision.color("red") targetVision.color("red")
delay(300) delay(300)
targetVision.color("green") targetVision.color("darkgreen")
delay(10) delay(10)
} }
} }

View File

@ -16,8 +16,6 @@ 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(
@ -62,7 +60,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
propertyNameFlow.onEach { name -> onPropertyChange(three.context) { 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
@ -78,7 +76,8 @@ 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

@ -13,11 +13,12 @@ public fun RBuilder.visionPropertyEditor(
descriptor: NodeDescriptor? = vision.descriptor, descriptor: NodeDescriptor? = vision.descriptor,
key: Any? = null, key: Any? = null,
) { ) {
card("Properties") { card("Properties") {
propertyEditor( propertyEditor(
provider = vision.ownProperties, provider = vision.ownProperties,
defaultProvider = vision.allProperties(), defaultProvider = vision.allProperties(),
updateFlow = vision.propertyNameFlow, updateFlow = vision.propertyChanges,
descriptor = descriptor, descriptor = descriptor,
key = key) key = key)
} }

View File

@ -7,6 +7,7 @@ import hep.dataforge.names.NameToken
import hep.dataforge.names.lastOrNull import hep.dataforge.names.lastOrNull
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.vision.hidden
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
@ -66,6 +67,9 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
val itemName by useState { props.name ?: Name.EMPTY } val itemName by useState { props.name ?: Name.EMPTY }
var item: MetaItem<*>? by useState { props.provider.getItem(itemName) } var item: MetaItem<*>? by useState { props.provider.getItem(itemName) }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName) val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName)
if(descriptorItem?.hidden == true) return //fail fast for hidden property
var actualItem: MetaItem<Meta>? by useState { var actualItem: MetaItem<Meta>? by useState {
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem() item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
} }
@ -106,8 +110,6 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
update() update()
} }
if (actualItem is MetaItem.NodeItem) { if (actualItem is MetaItem.NodeItem) {
styledDiv { styledDiv {
css { css {

View File

@ -5,6 +5,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import kotlinx.coroutines.launch
/** /**
* A container for styles * A container for styles
@ -54,8 +55,10 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
val tokens: Collection<Name> = val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet())) ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() } .map { it.asName() }
parent?.scope?.launch {
tokens.forEach { parent?.notifyPropertyChanged(it) } tokens.forEach { parent?.notifyPropertyChanged(it) }
} }
}
if (this is VisionGroup) { if (this is VisionGroup) {
for (obj in this) { for (obj in this) {
obj.styleChanged(key, oldStyle, newStyle) obj.styleChanged(key, oldStyle, newStyle)

View File

@ -9,9 +9,10 @@ import hep.dataforge.names.asName
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.vision.Vision.Companion.TYPE import hep.dataforge.vision.Vision.Companion.TYPE
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
/** /**
@ -55,17 +56,26 @@ public interface Vision : Described {
*/ */
public fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean = true) public fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean = true)
public fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit)
/** /**
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear * 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. * if it should include inherited properties etc.
*/ */
public val propertyNameFlow: Flow<Name> public val propertyChanges: Flow<Name> get() = callbackFlow<Name> {
coroutineScope {
onPropertyChange(this) {
send(it)
}
awaitClose { cancel() }
}
}
/** /**
* Notify all listeners that a property has been changed and should be invalidated * Notify all listeners that a property has been changed and should be invalidated
*/ */
public fun notifyPropertyChanged(propertyName: Name): Unit public suspend fun notifyPropertyChanged(propertyName: Name): Unit
/** /**
* Update this vision using external meta. Children are not updated. * Update this vision using external meta. Children are not updated.
@ -82,6 +92,12 @@ public interface Vision : Described {
} }
} }
public fun Vision.asyncNotifyPropertyChange(propertyName: Name){
scope.launch {
notifyPropertyChanged(propertyName)
}
}
/** /**
* Own properties, excluding inheritance, styles and descriptor * Own properties, excluding inheritance, styles and descriptor
*/ */

View File

@ -11,8 +11,11 @@ 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.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -44,10 +47,12 @@ public open class VisionBase : Vision {
properties = newProperties properties = newProperties
newProperties.onChange(this) { name, oldItem, newItem -> newProperties.onChange(this) { name, oldItem, newItem ->
if (oldItem != newItem) { if (oldItem != newItem) {
scope.launch {
notifyPropertyChanged(name) notifyPropertyChanged(name)
} }
} }
} }
}
return properties!! return properties!!
} }
@ -78,13 +83,15 @@ public open class VisionBase : Vision {
override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) { override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) {
getOrCreateConfig().setItem(name, item) getOrCreateConfig().setItem(name, item)
if (notify) { if (notify) {
scope.launch {
notifyPropertyChanged(name) notifyPropertyChanged(name)
} }
} }
}
override val descriptor: NodeDescriptor? get() = null override val descriptor: NodeDescriptor? get() = null
private fun updateStyles(names: List<String>) { private suspend fun updateStyles(names: List<String>) {
names.mapNotNull { getStyle(it) }.asSequence() names.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() } .flatMap { it.items.asSequence() }
.distinctBy { it.key } .distinctBy { it.key }
@ -94,17 +101,19 @@ public open class VisionBase : Vision {
} }
@Transient @Transient
private val _propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow() private val propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()
override val propertyNameFlow: SharedFlow<Name> get() = _propertyInvalidationFlow override val propertyChanges: Flow<Name> get() = propertyInvalidationFlow
override fun notifyPropertyChanged(propertyName: Name) { override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
propertyInvalidationFlow.onEach(callback).launchIn(scope)
}
override suspend fun notifyPropertyChanged(propertyName: Name) {
if (propertyName == STYLE_KEY) { if (propertyName == STYLE_KEY) {
updateStyles(styles) updateStyles(styles)
} }
scope.launch { propertyInvalidationFlow.emit(propertyName)
_propertyInvalidationFlow.emit(propertyName)
}
} }
public fun configure(block: MutableMeta<*>.() -> Unit) { public fun configure(block: MutableMeta<*>.() -> Unit) {

View File

@ -67,6 +67,9 @@ public class VisionChange(
@Serializable(MetaSerializer::class) public val properties: Meta? = null, @Serializable(MetaSerializer::class) public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null, public val children: Map<Name, VisionChange>? = null,
) { ) {
init {
(vision as? VisionGroup)?.attachChildren()
}
} }
@ -81,10 +84,10 @@ private fun CoroutineScope.collectChange(
) { ) {
//Collect properties change //Collect properties change
source.propertyNameFlow.onEach { propertyName -> source.onPropertyChange(this) { propertyName ->
val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false) val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
collector().propertyChanged(name, propertyName, newItem) collector().propertyChanged(name, propertyName, newItem)
}.launchIn(this) }
if (source is VisionGroup) { if (source is VisionGroup) {
//Subscribe for children changes //Subscribe for children changes

View File

@ -28,7 +28,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
*/ */
override val children: Map<NameToken, Vision> get() = childrenInternal override val children: Map<NameToken, Vision> get() = childrenInternal
override fun notifyPropertyChanged(propertyName: Name) { override suspend fun notifyPropertyChanged(propertyName: Name) {
super.notifyPropertyChanged(propertyName) super.notifyPropertyChanged(propertyName)
for (obj in this) { for (obj in this) {
obj.notifyPropertyChanged(propertyName) obj.notifyPropertyChanged(propertyName)

View File

@ -1,15 +1,11 @@
package hep.dataforge.vision package hep.dataforge.vision
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.Name
import hep.dataforge.values.ValueType
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@DslMarker @DslMarker
public annotation class VisionBuilder public annotation class VisionBuilder
public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? { public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
return when (val first = firstOrNull { it != null }) { return when (val first = firstOrNull { it != null }) {
null -> null null -> null
@ -22,14 +18,6 @@ public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
} }
} }
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(key: Name, default: E?): Unit = value(key) {
type(ValueType.STRING)
default?.let {
default(default)
}
allowedValues = enumValues<E>().map { it.asValue() }
}
@DFExperimental @DFExperimental
public val Vision.properties: Config? public val Vision.properties: Config?
get() = (this as? VisionBase)?.properties get() = (this as? VisionBase)?.properties

View File

@ -1,27 +0,0 @@
package hep.dataforge.vision
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.descriptors.attributes
/**
* Extension property to access the "widget" key of [ValueDescriptor]
*/
public var ValueDescriptor.widget: Meta
get() = attributes["widget"].node ?: Meta.EMPTY
set(value) {
attributes {
set("widget", value)
}
}
/**
* Extension property to access the "widget.type" key of [ValueDescriptor]
*/
public var ValueDescriptor.widgetType: String?
get() = attributes["widget.type"].string
set(value) {
attributes{
set("widget.type", value)
}
}

View File

@ -1,11 +1,13 @@
package hep.dataforge.vision package hep.dataforge.vision
import hep.dataforge.meta.Meta import hep.dataforge.meta.*
import hep.dataforge.meta.boolean
import hep.dataforge.meta.descriptors.ItemDescriptor import hep.dataforge.meta.descriptors.ItemDescriptor
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.descriptors.attributes import hep.dataforge.meta.descriptors.attributes
import hep.dataforge.meta.get import hep.dataforge.names.Name
import hep.dataforge.meta.set import hep.dataforge.values.ValueType
import hep.dataforge.values.asValue
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited" private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles" private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
@ -30,3 +32,48 @@ public val Vision.describedProperties: Meta
} }
} }
/**
* Extension property to access the "widget" key of [ValueDescriptor]
*/
public var ValueDescriptor.widget: Meta
get() = attributes["widget"].node ?: Meta.EMPTY
set(value) {
attributes {
set("widget", value)
}
}
/**
* Extension property to access the "widget.type" key of [ValueDescriptor]
*/
public var ValueDescriptor.widgetType: String?
get() = attributes["widget.type"].string
set(value) {
attributes {
set("widget.type", value)
}
}
/**
* If true, this item is hidden in property editor. Default is false
*/
public val ItemDescriptor.hidden: Boolean
get() = attributes["widget.hide"].boolean ?: false
public fun ItemDescriptor.hide(): Unit = attributes {
set("widget.hide", true)
}
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(
key: Name,
default: E?,
crossinline modifier: ValueDescriptor.() -> Unit = {},
): Unit = value(key) {
type(ValueType.STRING)
default?.let {
default(default)
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}

View File

@ -18,9 +18,6 @@ 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.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -84,15 +81,19 @@ public class VisionClient : AbstractPlugin() {
renderVision(element, embeddedVision, outputMeta) renderVision(element, embeddedVision, outputMeta)
} }
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr ->
val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
val endpoint = resolveEndpoint(element) val endpoint = resolveEndpoint(element)
logger.info { "Vision server is resolved to $endpoint" } logger.info { "Vision server is resolved to $endpoint" }
URL(endpoint).apply {
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
val fetchUrl = URL(endpoint).apply {
searchParams.append("name", name)
pathname += "/vision" pathname += "/vision"
} }
} else {
URL(attr.value)
}.apply {
searchParams.append("name", name)
}
logger.info { "Fetching vision data from $fetchUrl" } logger.info { "Fetching vision data from $fetchUrl" }
window.fetch(fetchUrl).then { response -> window.fetch(fetchUrl).then { response ->
@ -102,16 +103,22 @@ public class VisionClient : AbstractPlugin() {
renderVision(element, vision, outputMeta) renderVision(element, vision, outputMeta)
} }
} else { } else {
logger.error { "Failed to fetch initial vision state from $endpoint" } logger.error { "Failed to fetch initial vision state from $fetchUrl" }
} }
} }
} }
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") {
val wsUrl = URL(endpoint).apply { val endpoint = resolveEndpoint(element)
logger.info { "Vision server is resolved to $endpoint" }
URL(endpoint).apply {
pathname += "/ws" pathname += "/ws"
}
} else {
URL(attr.value)
}.apply {
protocol = "ws" protocol = "ws"
searchParams.append("name", name) searchParams.append("name", name)
} }
@ -122,12 +129,17 @@ public class VisionClient : AbstractPlugin() {
onmessage = { messageEvent -> onmessage = { messageEvent ->
val stringData: String? = messageEvent.data as? String val stringData: String? = messageEvent.data as? String
if (stringData != null) { if (stringData != null) {
val dif = visionManager.jsonFormat.decodeFromString( val change = visionManager.jsonFormat.decodeFromString(
VisionChange.serializer(), VisionChange.serializer(),
stringData stringData
) )
logger.debug { "Got update $dif for output with name $name" }
visionMap[element]?.update(dif) if(change.vision!= null){
renderVision(element, change.vision, outputMeta)
}
logger.debug { "Got update $change for output with name $name" }
visionMap[element]?.update(change)
?: console.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

@ -4,8 +4,6 @@ import hep.dataforge.names.*
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import javafx.scene.Group import javafx.scene.Group
import javafx.scene.Node import javafx.scene.Node
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> { class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
@ -15,7 +13,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
val prototype = obj.prototype val prototype = obj.prototype
val node = plugin.buildNode(prototype) val node = plugin.buildNode(prototype)
obj.propertyNameFlow.onEach { name-> obj.onPropertyChange(plugin.context) { name->
if (name.firstOrNull()?.body == SolidReferenceGroup.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 childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst() val propertyName = name.cutFirst()
@ -23,7 +21,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found") val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
child.updateProperty(referenceChild, propertyName) child.updateProperty(referenceChild, propertyName)
} }
}.launchIn(plugin.context) }
return node return node
} }
} }

View File

@ -7,8 +7,6 @@ import hep.dataforge.names.toName
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.binding.ObjectBinding import javafx.beans.binding.ObjectBinding
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import tornadofx.* import tornadofx.*
/** /**
@ -18,7 +16,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>() private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
init { init {
obj.propertyNameFlow.onEach { name -> obj.onPropertyChange(fx.context) { name ->
bindings.filter { it.key.startsWith(name) }.forEach { entry -> bindings.filter { it.key.startsWith(name) }.forEach { entry ->
Platform.runLater { Platform.runLater {
entry.value.invalidate() entry.value.invalidate()
@ -30,7 +28,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
// bindings[currentName]?.invalidate() // bindings[currentName]?.invalidate()
// currentName = currentName.cutLast() // currentName = currentName.cutLast()
// } // }
}.launchIn(fx.context) }
} }
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> { operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {

View File

@ -41,6 +41,13 @@ import java.awt.Desktop
import java.net.URI import java.net.URI
import kotlin.time.milliseconds import kotlin.time.milliseconds
public enum class VisionServerDataMode {
EMBED,
FETCH,
CONNECT
}
/** /**
* A ktor plugin container with given [routing] * A ktor plugin container with given [routing]
*/ */
@ -52,6 +59,7 @@ public class VisionServer internal constructor(
override val config: Config = Config() override val config: Config = Config()
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY) public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
public var cacheFragments: Boolean by config.boolean(true) public var cacheFragments: Boolean by config.boolean(true)
public var dataMode: VisionServerDataMode = VisionServerDataMode.CONNECT
/** /**
* a list of headers that should be applied to all pages * a list of headers that should be applied to all pages
@ -72,10 +80,23 @@ public class VisionServer internal constructor(
val consumer = object : VisionTagConsumer<Any?>(consumer) { val consumer = object : VisionTagConsumer<Any?>(consumer) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision visionMap[name] = vision
// Toggle update mode
// Toggle updates when (dataMode) {
attributes[OUTPUT_FETCH_ATTRIBUTE] = "true" VisionServerDataMode.EMBED -> {
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true" script {
attributes["class"] = OUTPUT_DATA_CLASS
unsafe {
+visionManager.encodeToString(vision)
}
}
}
VisionServerDataMode.FETCH -> {
attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto"
}
VisionServerDataMode.CONNECT -> {
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
}
}
} }
} }
@ -110,10 +131,19 @@ public class VisionServer internal constructor(
application.log.debug("Opened server socket for $name") application.log.debug("Opened server socket for $name")
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
try { try {
withContext(visionManager.context.coroutineContext) { withContext(visionManager.context.coroutineContext) {
val initialVision = VisionChange(vision = vision)
val initialJson = visionManager.jsonFormat.encodeToString(
VisionChange.serializer(),
initialVision
)
outgoing.send(Frame.Text(initialJson))
vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update -> vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update ->
val json = VisionManager.defaultJson.encodeToString( val json = visionManager.jsonFormat.encodeToString(
VisionChange.serializer(), VisionChange.serializer(),
update update
) )

View File

@ -61,6 +61,7 @@ public interface Solid : Vision {
public val descriptor: NodeDescriptor by lazy { public val descriptor: NodeDescriptor by lazy {
NodeDescriptor { NodeDescriptor {
value(VISIBLE_KEY) { value(VISIBLE_KEY) {
inherited = false
type(ValueType.BOOLEAN) type(ValueType.BOOLEAN)
default(true) default(true)
} }
@ -69,11 +70,14 @@ public interface Solid : Vision {
value(Vision.STYLE_KEY) { value(Vision.STYLE_KEY) {
type(ValueType.STRING) type(ValueType.STRING)
multiple = true multiple = true
hide()
} }
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor) item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) {
hide()
}
} }
} }
@ -152,21 +156,21 @@ public var Solid.x: Number
get() = position?.x ?: 0f get() = position?.x ?: 0f
set(value) { set(value) {
position().x = value.toDouble() position().x = value.toDouble()
notifyPropertyChanged(Solid.X_POSITION_KEY) asyncNotifyPropertyChange(Solid.X_POSITION_KEY)
} }
public var Solid.y: Number public var Solid.y: Number
get() = position?.y ?: 0f get() = position?.y ?: 0f
set(value) { set(value) {
position().y = value.toDouble() position().y = value.toDouble()
notifyPropertyChanged(Solid.Y_POSITION_KEY) asyncNotifyPropertyChange(Solid.Y_POSITION_KEY)
} }
public var Solid.z: Number public var Solid.z: Number
get() = position?.z ?: 0f get() = position?.z ?: 0f
set(value) { set(value) {
position().z = value.toDouble() position().z = value.toDouble()
notifyPropertyChanged(Solid.Z_POSITION_KEY) asyncNotifyPropertyChange(Solid.Z_POSITION_KEY)
} }
private fun Solid.rotation(): Point3D = private fun Solid.rotation(): Point3D =
@ -176,21 +180,21 @@ public var Solid.rotationX: Number
get() = rotation?.x ?: 0f get() = rotation?.x ?: 0f
set(value) { set(value) {
rotation().x = value.toDouble() rotation().x = value.toDouble()
notifyPropertyChanged(Solid.X_ROTATION_KEY) asyncNotifyPropertyChange(Solid.X_ROTATION_KEY)
} }
public var Solid.rotationY: Number public var Solid.rotationY: Number
get() = rotation?.y ?: 0f get() = rotation?.y ?: 0f
set(value) { set(value) {
rotation().y = value.toDouble() rotation().y = value.toDouble()
notifyPropertyChanged(Solid.Y_ROTATION_KEY) asyncNotifyPropertyChange(Solid.Y_ROTATION_KEY)
} }
public var Solid.rotationZ: Number public var Solid.rotationZ: Number
get() = rotation?.z ?: 0f get() = rotation?.z ?: 0f
set(value) { set(value) {
rotation().z = value.toDouble() rotation().z = value.toDouble()
notifyPropertyChanged(Solid.Z_ROTATION_KEY) asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY)
} }
private fun Solid.scale(): Point3D = private fun Solid.scale(): Point3D =
@ -200,19 +204,19 @@ public var Solid.scaleX: Number
get() = scale?.x ?: 1f get() = scale?.x ?: 1f
set(value) { set(value) {
scale().x = value.toDouble() scale().x = value.toDouble()
notifyPropertyChanged(Solid.X_SCALE_KEY) asyncNotifyPropertyChange(Solid.X_SCALE_KEY)
} }
public var Solid.scaleY: Number public var Solid.scaleY: Number
get() = scale?.y ?: 1f get() = scale?.y ?: 1f
set(value) { set(value) {
scale().y = value.toDouble() scale().y = value.toDouble()
notifyPropertyChanged(Solid.Y_SCALE_KEY) asyncNotifyPropertyChange(Solid.Y_SCALE_KEY)
} }
public var Solid.scaleZ: Number public var Solid.scaleZ: Number
get() = scale?.z ?: 1f get() = scale?.z ?: 1f
set(value) { set(value) {
scale().z = value.toDouble() scale().z = value.toDouble()
notifyPropertyChanged(Solid.Z_SCALE_KEY) asyncNotifyPropertyChange(Solid.Z_SCALE_KEY)
} }

View File

@ -42,7 +42,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
* Ger a prototype redirecting the request to the parent if prototype is not found * Ger a prototype redirecting the request to the parent if prototype is not found
*/ */
override fun getPrototype(name: Name): Solid? = override fun getPrototype(name: Name): Solid? =
prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name) (prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name)
/** /**
* Create or edit prototype node as a group * Create or edit prototype node as a group
@ -108,7 +108,7 @@ public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup
@Serializable(Prototypes.Companion::class) @Serializable(Prototypes.Companion::class)
internal class Prototypes( internal class Prototypes(
children: Map<NameToken, Vision> = emptyMap(), children: Map<NameToken, Vision> = emptyMap(),
) : VisionGroupBase() { ) : VisionGroupBase(), PrototypeHolder {
init { init {
childrenInternal.putAll(children) childrenInternal.putAll(children)
@ -155,4 +155,10 @@ internal class Prototypes(
mapSerializer.serialize(encoder, value.children) mapSerializer.serialize(encoder, value.children)
} }
} }
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit) {
apply(builder)
}
override fun getPrototype(name: Name): Solid? = get(name) as? Solid
} }

View File

@ -89,18 +89,24 @@ public class SolidMaterial : Scheme() {
public override val descriptor: NodeDescriptor by lazy { public override val descriptor: NodeDescriptor by lazy {
//must be lazy to avoid initialization bug //must be lazy to avoid initialization bug
NodeDescriptor { NodeDescriptor {
inherited = true
usesStyles = true
value(COLOR_KEY) { value(COLOR_KEY) {
inherited = true inherited = true
usesStyles = true usesStyles = true
type(ValueType.STRING, ValueType.NUMBER) type(ValueType.STRING, ValueType.NUMBER)
widgetType = "color" widgetType = "color"
} }
// value(SPECULAR_COLOR_KEY) {
// inherited = true value(SPECULAR_COLOR_KEY) {
// usesStyles = true inherited = true
// type(ValueType.STRING, ValueType.NUMBER) usesStyles = true
// widgetType = "color" type(ValueType.STRING, ValueType.NUMBER)
// } widgetType = "color"
hide()
}
value(OPACITY_KEY) { value(OPACITY_KEY) {
inherited = true inherited = true
usesStyles = true usesStyles = true

View File

@ -5,9 +5,7 @@ import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.get import hep.dataforge.meta.descriptors.get
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.vision.* import hep.dataforge.vision.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -128,14 +126,15 @@ public class SolidReferenceGroup(
error("Setting a parent for a reference child is not possible") error("Setting a parent for a reference child is not possible")
} }
override val propertyNameFlow: Flow<Name> override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
get() = this@SolidReferenceGroup.propertyNameFlow.filter { name -> this@SolidReferenceGroup.onPropertyChange(scope) { name ->
name.startsWith(childToken(childName)) if (name.startsWith(childToken(childName))) {
}.map { name -> callback(name.cutFirst())
name.cutFirst() }
}
} }
override fun notifyPropertyChanged(propertyName: Name) { override suspend fun notifyPropertyChanged(propertyName: Name) {
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName)) this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
} }

View File

@ -15,8 +15,6 @@ import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -45,7 +43,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
}.applyProperties(obj) }.applyProperties(obj)
//add listener to object properties //add listener to object properties
obj.propertyNameFlow.onEach { name -> obj.onPropertyChange(three.updateScope) { name ->
when { when {
name.startsWith(Solid.GEOMETRY_KEY) -> { name.startsWith(Solid.GEOMETRY_KEY) -> {
val oldGeometry = mesh.geometry as BufferGeometry val oldGeometry = mesh.geometry as BufferGeometry
@ -59,7 +57,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj) name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
else -> mesh.updateProperty(obj, name) else -> mesh.updateProperty(obj, name)
} }
}.launchIn(three.updateScope) }
return mesh return mesh
} }

View File

@ -47,7 +47,7 @@ public fun Object3D.updatePosition(obj: Vision) {
*/ */
public fun Object3D.updateProperty(source: Vision, propertyName: Name) { public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
this.material = getMaterial(source, false) this.material = getMaterial(source, true)
} else if ( } else if (
propertyName.startsWith(Solid.POSITION_KEY) propertyName.startsWith(Solid.POSITION_KEY)
|| propertyName.startsWith(Solid.ROTATION) || propertyName.startsWith(Solid.ROTATION)

View File

@ -8,8 +8,6 @@ import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.TextBufferGeometry import info.laht.threekt.geometries.TextBufferGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinext.js.jsObject import kotlinext.js.jsObject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -27,10 +25,10 @@ public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
}) })
return Mesh(textGeo, getMaterial(obj, true)).apply { return Mesh(textGeo, getMaterial(obj, true)).apply {
updatePosition(obj) updatePosition(obj)
obj.propertyNameFlow.onEach { _ -> obj.onPropertyChange(three.updateScope){ _ ->
//TODO //TODO
three.logger.warn{"Label parameter change not implemented"} three.logger.warn{"Label parameter change not implemented"}
}.launchIn(three.updateScope) }
} }
} }
} }

View File

@ -9,8 +9,6 @@ import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
public object ThreeLineFactory : ThreeFactory<PolyLine> { public object ThreeLineFactory : ThreeFactory<PolyLine> {
@ -30,9 +28,9 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
updatePosition(obj) updatePosition(obj)
//layers.enable(obj.layer) //layers.enable(obj.layer)
//add listener to object properties //add listener to object properties
obj.propertyNameFlow.onEach { propertyName -> obj.onPropertyChange(three.updateScope) { propertyName ->
updateProperty(obj, propertyName) updateProperty(obj, propertyName)
}.launchIn(three.updateScope) }
} }
} }

View File

@ -59,6 +59,10 @@ public object ThreeMaterials {
MeshPhongMaterial().apply { MeshPhongMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
specular = meta[SolidMaterial.SPECULAR_COLOR_KEY]!!.getColor() specular = meta[SolidMaterial.SPECULAR_COLOR_KEY]!!.getColor()
emissive = specular
reflectivity = 1.0
refractionRatio = 1.0
shininess = 100.0
opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0 opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0 transparent = opacity < 1.0
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false

View File

@ -67,7 +67,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
updatePosition(obj) updatePosition(obj)
//obj.onChildrenChange() //obj.onChildrenChange()
obj.propertyNameFlow.onEach { name -> obj.onPropertyChange(updateScope) { name ->
if ( if (
name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.POSITION_KEY) ||
name.startsWith(Solid.ROTATION) || name.startsWith(Solid.ROTATION) ||
@ -78,7 +78,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
} else if (name == Vision.VISIBLE_KEY) { } else if (name == Vision.VISIBLE_KEY) {
visible = obj.visible ?: true visible = obj.visible ?: true
} }
}.launchIn(updateScope) }
obj.structureChanges.onEach { (nameToken, _, child) -> obj.structureChanges.onEach { (nameToken, _, child) ->
// if (name.isEmpty()) { // if (name.isEmpty()) {

View File

@ -9,8 +9,6 @@ import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> { public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
@ -47,7 +45,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
//TODO apply child properties //TODO apply child properties
obj.propertyNameFlow.onEach { name-> obj.onPropertyChange(three.updateScope) { name->
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'") val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst() val propertyName = name.cutFirst()
@ -57,7 +55,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
} else { } else {
object3D.updateProperty(obj, name) object3D.updateProperty(obj, name)
} }
}.launchIn(three.updateScope) }
return object3D return object3D