Another huge refactoring of property updates
This commit is contained in:
parent
4c235b0f53
commit
0259d4eb15
@ -42,7 +42,7 @@ fun main() {
|
||||
val targetVision = sat[target] as Solid
|
||||
targetVision.color("red")
|
||||
delay(300)
|
||||
targetVision.color("green")
|
||||
targetVision.color("darkgreen")
|
||||
delay(10)
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.BoxBufferGeometry
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.math.max
|
||||
|
||||
internal fun SolidGroup.varBox(
|
||||
@ -62,7 +60,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
||||
mesh.scale.set(xSize, ySize, zSize)
|
||||
|
||||
//add listener to object properties
|
||||
propertyNameFlow.onEach { name ->
|
||||
onPropertyChange(three.context) { name ->
|
||||
when {
|
||||
name.startsWith(GEOMETRY_KEY) -> {
|
||||
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)
|
||||
}
|
||||
}.launchIn(three.context)
|
||||
}
|
||||
|
||||
return mesh
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,12 @@ public fun RBuilder.visionPropertyEditor(
|
||||
descriptor: NodeDescriptor? = vision.descriptor,
|
||||
key: Any? = null,
|
||||
) {
|
||||
|
||||
card("Properties") {
|
||||
propertyEditor(
|
||||
provider = vision.ownProperties,
|
||||
defaultProvider = vision.allProperties(),
|
||||
updateFlow = vision.propertyNameFlow,
|
||||
updateFlow = vision.propertyChanges,
|
||||
descriptor = descriptor,
|
||||
key = key)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.lastOrNull
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.vision.hidden
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@ -66,6 +67,9 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
val itemName by useState { props.name ?: Name.EMPTY }
|
||||
var item: MetaItem<*>? by useState { props.provider.getItem(itemName) }
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName)
|
||||
|
||||
if(descriptorItem?.hidden == true) return //fail fast for hidden property
|
||||
|
||||
var actualItem: MetaItem<Meta>? by useState {
|
||||
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
||||
}
|
||||
@ -106,8 +110,6 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
update()
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (actualItem is MetaItem.NodeItem) {
|
||||
styledDiv {
|
||||
css {
|
||||
|
@ -5,6 +5,7 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A container for styles
|
||||
@ -54,7 +55,9 @@ 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?.notifyPropertyChanged(it) }
|
||||
parent?.scope?.launch {
|
||||
tokens.forEach { parent?.notifyPropertyChanged(it) }
|
||||
}
|
||||
}
|
||||
if (this is VisionGroup) {
|
||||
for (obj in this) {
|
||||
|
@ -9,9 +9,10 @@ import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.vision.Vision.Companion.TYPE
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
/**
|
||||
@ -29,7 +30,7 @@ public interface Vision : Described {
|
||||
/**
|
||||
* A coroutine scope for asynchronous calls and locks
|
||||
*/
|
||||
public val scope: CoroutineScope get() = parent?.scope?: GlobalScope
|
||||
public val scope: CoroutineScope get() = parent?.scope ?: GlobalScope
|
||||
|
||||
/**
|
||||
* A fast accessor method to get own property (no inheritance or styles).
|
||||
@ -55,17 +56,26 @@ public interface Vision : Described {
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
public fun notifyPropertyChanged(propertyName: Name): Unit
|
||||
public suspend fun notifyPropertyChanged(propertyName: Name): Unit
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -11,8 +11,11 @@ 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.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -44,7 +47,9 @@ public open class VisionBase : Vision {
|
||||
properties = newProperties
|
||||
newProperties.onChange(this) { name, oldItem, newItem ->
|
||||
if (oldItem != newItem) {
|
||||
notifyPropertyChanged(name)
|
||||
scope.launch {
|
||||
notifyPropertyChanged(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,14 +82,16 @@ public open class VisionBase : Vision {
|
||||
@Synchronized
|
||||
override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) {
|
||||
getOrCreateConfig().setItem(name, item)
|
||||
if(notify) {
|
||||
notifyPropertyChanged(name)
|
||||
if (notify) {
|
||||
scope.launch {
|
||||
notifyPropertyChanged(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor? get() = null
|
||||
|
||||
private fun updateStyles(names: List<String>) {
|
||||
private suspend fun updateStyles(names: List<String>) {
|
||||
names.mapNotNull { getStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
@ -94,17 +101,19 @@ public open class VisionBase : Vision {
|
||||
}
|
||||
|
||||
@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) {
|
||||
updateStyles(styles)
|
||||
}
|
||||
scope.launch {
|
||||
_propertyInvalidationFlow.emit(propertyName)
|
||||
}
|
||||
propertyInvalidationFlow.emit(propertyName)
|
||||
}
|
||||
|
||||
public fun configure(block: MutableMeta<*>.() -> Unit) {
|
||||
|
@ -67,6 +67,9 @@ public class VisionChange(
|
||||
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
|
||||
public val children: Map<Name, VisionChange>? = null,
|
||||
) {
|
||||
init {
|
||||
(vision as? VisionGroup)?.attachChildren()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -81,10 +84,10 @@ private fun CoroutineScope.collectChange(
|
||||
) {
|
||||
|
||||
//Collect properties change
|
||||
source.propertyNameFlow.onEach { propertyName ->
|
||||
source.onPropertyChange(this) { 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
|
||||
|
@ -28,7 +28,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
*/
|
||||
override val children: Map<NameToken, Vision> get() = childrenInternal
|
||||
|
||||
override fun notifyPropertyChanged(propertyName: Name) {
|
||||
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||
super.notifyPropertyChanged(propertyName)
|
||||
for (obj in this) {
|
||||
obj.notifyPropertyChanged(propertyName)
|
||||
|
@ -1,15 +1,11 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
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
|
||||
|
||||
@DslMarker
|
||||
public annotation class VisionBuilder
|
||||
|
||||
|
||||
public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
|
||||
return when (val first = firstOrNull { it != 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
|
||||
public val Vision.properties: Config?
|
||||
get() = (this as? VisionBase)?.properties
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.meta.*
|
||||
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.get
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
|
||||
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
|
||||
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()
|
||||
}
|
@ -18,9 +18,6 @@ import org.w3c.dom.WebSocket
|
||||
import org.w3c.dom.asList
|
||||
import org.w3c.dom.get
|
||||
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
|
||||
|
||||
@ -84,14 +81,18 @@ public class VisionClient : AbstractPlugin() {
|
||||
renderVision(element, embeddedVision, outputMeta)
|
||||
}
|
||||
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr ->
|
||||
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
|
||||
|
||||
val fetchUrl = URL(endpoint).apply {
|
||||
val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
pathname += "/vision"
|
||||
}
|
||||
} else {
|
||||
URL(attr.value)
|
||||
}.apply {
|
||||
searchParams.append("name", name)
|
||||
pathname += "/vision"
|
||||
}
|
||||
|
||||
logger.info { "Fetching vision data from $fetchUrl" }
|
||||
@ -102,16 +103,22 @@ public class VisionClient : AbstractPlugin() {
|
||||
renderVision(element, vision, outputMeta)
|
||||
}
|
||||
} 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 {
|
||||
|
||||
val wsUrl = URL(endpoint).apply {
|
||||
pathname += "/ws"
|
||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
pathname += "/ws"
|
||||
}
|
||||
} else {
|
||||
URL(attr.value)
|
||||
}.apply {
|
||||
protocol = "ws"
|
||||
searchParams.append("name", name)
|
||||
}
|
||||
@ -122,15 +129,20 @@ public class VisionClient : AbstractPlugin() {
|
||||
onmessage = { messageEvent ->
|
||||
val stringData: String? = messageEvent.data as? String
|
||||
if (stringData != null) {
|
||||
val dif = visionManager.jsonFormat.decodeFromString(
|
||||
val change = visionManager.jsonFormat.decodeFromString(
|
||||
VisionChange.serializer(),
|
||||
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")
|
||||
} else {
|
||||
console.error ("WebSocket message data is not a string")
|
||||
console.error("WebSocket message data is not a string")
|
||||
}
|
||||
}
|
||||
onopen = {
|
||||
|
@ -4,8 +4,6 @@ import hep.dataforge.names.*
|
||||
import hep.dataforge.vision.Vision
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
|
||||
@ -15,7 +13,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
|
||||
val prototype = obj.prototype
|
||||
val node = plugin.buildNode(prototype)
|
||||
|
||||
obj.propertyNameFlow.onEach { name->
|
||||
obj.onPropertyChange(plugin.context) { name->
|
||||
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()
|
||||
@ -23,7 +21,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
|
||||
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||
child.updateProperty(referenceChild, propertyName)
|
||||
}
|
||||
}.launchIn(plugin.context)
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.Vision
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.binding.ObjectBinding
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
@ -18,7 +16,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
|
||||
private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
||||
|
||||
init {
|
||||
obj.propertyNameFlow.onEach { name ->
|
||||
obj.onPropertyChange(fx.context) { name ->
|
||||
bindings.filter { it.key.startsWith(name) }.forEach { entry ->
|
||||
Platform.runLater {
|
||||
entry.value.invalidate()
|
||||
@ -30,7 +28,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
|
||||
// bindings[currentName]?.invalidate()
|
||||
// currentName = currentName.cutLast()
|
||||
// }
|
||||
}.launchIn(fx.context)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
||||
|
@ -41,6 +41,13 @@ import java.awt.Desktop
|
||||
import java.net.URI
|
||||
import kotlin.time.milliseconds
|
||||
|
||||
public enum class VisionServerDataMode {
|
||||
EMBED,
|
||||
FETCH,
|
||||
CONNECT
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A ktor plugin container with given [routing]
|
||||
*/
|
||||
@ -52,6 +59,7 @@ public class VisionServer internal constructor(
|
||||
override val config: Config = Config()
|
||||
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||
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
|
||||
@ -72,10 +80,23 @@ public class VisionServer internal constructor(
|
||||
val consumer = object : VisionTagConsumer<Any?>(consumer) {
|
||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||
visionMap[name] = vision
|
||||
|
||||
// Toggle updates
|
||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = "true"
|
||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true"
|
||||
// Toggle update mode
|
||||
when (dataMode) {
|
||||
VisionServerDataMode.EMBED -> {
|
||||
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")
|
||||
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
|
||||
|
||||
try {
|
||||
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 ->
|
||||
val json = VisionManager.defaultJson.encodeToString(
|
||||
val json = visionManager.jsonFormat.encodeToString(
|
||||
VisionChange.serializer(),
|
||||
update
|
||||
)
|
||||
|
@ -61,6 +61,7 @@ public interface Solid : Vision {
|
||||
public val descriptor: NodeDescriptor by lazy {
|
||||
NodeDescriptor {
|
||||
value(VISIBLE_KEY) {
|
||||
inherited = false
|
||||
type(ValueType.BOOLEAN)
|
||||
default(true)
|
||||
}
|
||||
@ -69,11 +70,14 @@ public interface Solid : Vision {
|
||||
value(Vision.STYLE_KEY) {
|
||||
type(ValueType.STRING)
|
||||
multiple = true
|
||||
hide()
|
||||
}
|
||||
|
||||
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
|
||||
set(value) {
|
||||
position().x = value.toDouble()
|
||||
notifyPropertyChanged(Solid.X_POSITION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.X_POSITION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.y: Number
|
||||
get() = position?.y ?: 0f
|
||||
set(value) {
|
||||
position().y = value.toDouble()
|
||||
notifyPropertyChanged(Solid.Y_POSITION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Y_POSITION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.z: Number
|
||||
get() = position?.z ?: 0f
|
||||
set(value) {
|
||||
position().z = value.toDouble()
|
||||
notifyPropertyChanged(Solid.Z_POSITION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Z_POSITION_KEY)
|
||||
}
|
||||
|
||||
private fun Solid.rotation(): Point3D =
|
||||
@ -176,21 +180,21 @@ public var Solid.rotationX: Number
|
||||
get() = rotation?.x ?: 0f
|
||||
set(value) {
|
||||
rotation().x = value.toDouble()
|
||||
notifyPropertyChanged(Solid.X_ROTATION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.X_ROTATION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.rotationY: Number
|
||||
get() = rotation?.y ?: 0f
|
||||
set(value) {
|
||||
rotation().y = value.toDouble()
|
||||
notifyPropertyChanged(Solid.Y_ROTATION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Y_ROTATION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.rotationZ: Number
|
||||
get() = rotation?.z ?: 0f
|
||||
set(value) {
|
||||
rotation().z = value.toDouble()
|
||||
notifyPropertyChanged(Solid.Z_ROTATION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY)
|
||||
}
|
||||
|
||||
private fun Solid.scale(): Point3D =
|
||||
@ -200,19 +204,19 @@ public var Solid.scaleX: Number
|
||||
get() = scale?.x ?: 1f
|
||||
set(value) {
|
||||
scale().x = value.toDouble()
|
||||
notifyPropertyChanged(Solid.X_SCALE_KEY)
|
||||
asyncNotifyPropertyChange(Solid.X_SCALE_KEY)
|
||||
}
|
||||
|
||||
public var Solid.scaleY: Number
|
||||
get() = scale?.y ?: 1f
|
||||
set(value) {
|
||||
scale().y = value.toDouble()
|
||||
notifyPropertyChanged(Solid.Y_SCALE_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Y_SCALE_KEY)
|
||||
}
|
||||
|
||||
public var Solid.scaleZ: Number
|
||||
get() = scale?.z ?: 1f
|
||||
set(value) {
|
||||
scale().z = value.toDouble()
|
||||
notifyPropertyChanged(Solid.Z_SCALE_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Z_SCALE_KEY)
|
||||
}
|
@ -42,7 +42,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
|
||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||
*/
|
||||
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
|
||||
@ -108,7 +108,7 @@ public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup
|
||||
@Serializable(Prototypes.Companion::class)
|
||||
internal class Prototypes(
|
||||
children: Map<NameToken, Vision> = emptyMap(),
|
||||
) : VisionGroupBase() {
|
||||
) : VisionGroupBase(), PrototypeHolder {
|
||||
|
||||
init {
|
||||
childrenInternal.putAll(children)
|
||||
@ -155,4 +155,10 @@ internal class Prototypes(
|
||||
mapSerializer.serialize(encoder, value.children)
|
||||
}
|
||||
}
|
||||
|
||||
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit) {
|
||||
apply(builder)
|
||||
}
|
||||
|
||||
override fun getPrototype(name: Name): Solid? = get(name) as? Solid
|
||||
}
|
||||
|
@ -89,18 +89,24 @@ public class SolidMaterial : Scheme() {
|
||||
public override val descriptor: NodeDescriptor by lazy {
|
||||
//must be lazy to avoid initialization bug
|
||||
NodeDescriptor {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
|
||||
value(COLOR_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
type(ValueType.STRING, ValueType.NUMBER)
|
||||
widgetType = "color"
|
||||
}
|
||||
// value(SPECULAR_COLOR_KEY) {
|
||||
// inherited = true
|
||||
// usesStyles = true
|
||||
// type(ValueType.STRING, ValueType.NUMBER)
|
||||
// widgetType = "color"
|
||||
// }
|
||||
|
||||
value(SPECULAR_COLOR_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
type(ValueType.STRING, ValueType.NUMBER)
|
||||
widgetType = "color"
|
||||
hide()
|
||||
}
|
||||
|
||||
value(OPACITY_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
|
@ -5,9 +5,7 @@ import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.get
|
||||
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.coroutines.CoroutineScope
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@ -128,14 +126,15 @@ public class SolidReferenceGroup(
|
||||
error("Setting a parent for a reference child is not possible")
|
||||
}
|
||||
|
||||
override val propertyNameFlow: Flow<Name>
|
||||
get() = this@SolidReferenceGroup.propertyNameFlow.filter { name ->
|
||||
name.startsWith(childToken(childName))
|
||||
}.map { name ->
|
||||
name.cutFirst()
|
||||
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||
this@SolidReferenceGroup.onPropertyChange(scope) { name ->
|
||||
if (name.startsWith(childToken(childName))) {
|
||||
callback(name.cutFirst())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun notifyPropertyChanged(propertyName: Name) {
|
||||
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,6 @@ 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
|
||||
|
||||
/**
|
||||
@ -45,7 +43,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
||||
}.applyProperties(obj)
|
||||
|
||||
//add listener to object properties
|
||||
obj.propertyNameFlow.onEach { name ->
|
||||
obj.onPropertyChange(three.updateScope) { name ->
|
||||
when {
|
||||
name.startsWith(Solid.GEOMETRY_KEY) -> {
|
||||
val oldGeometry = mesh.geometry as BufferGeometry
|
||||
@ -59,7 +57,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
||||
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||
else -> mesh.updateProperty(obj, name)
|
||||
}
|
||||
}.launchIn(three.updateScope)
|
||||
}
|
||||
|
||||
return mesh
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public fun Object3D.updatePosition(obj: Vision) {
|
||||
*/
|
||||
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
|
||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||
this.material = getMaterial(source, false)
|
||||
this.material = getMaterial(source, true)
|
||||
} else if (
|
||||
propertyName.startsWith(Solid.POSITION_KEY)
|
||||
|| propertyName.startsWith(Solid.ROTATION)
|
||||
|
@ -8,8 +8,6 @@ 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
|
||||
|
||||
/**
|
||||
@ -27,10 +25,10 @@ public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
||||
})
|
||||
return Mesh(textGeo, getMaterial(obj, true)).apply {
|
||||
updatePosition(obj)
|
||||
obj.propertyNameFlow.onEach { _ ->
|
||||
obj.onPropertyChange(three.updateScope){ _ ->
|
||||
//TODO
|
||||
three.logger.warn{"Label parameter change not implemented"}
|
||||
}.launchIn(three.updateScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,6 @@ 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> {
|
||||
@ -30,9 +28,9 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||
updatePosition(obj)
|
||||
//layers.enable(obj.layer)
|
||||
//add listener to object properties
|
||||
obj.propertyNameFlow.onEach { propertyName ->
|
||||
obj.onPropertyChange(three.updateScope) { propertyName ->
|
||||
updateProperty(obj, propertyName)
|
||||
}.launchIn(three.updateScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,10 @@ public object ThreeMaterials {
|
||||
MeshPhongMaterial().apply {
|
||||
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||
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
|
||||
transparent = opacity < 1.0
|
||||
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
|
||||
|
@ -67,7 +67,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
updatePosition(obj)
|
||||
//obj.onChildrenChange()
|
||||
|
||||
obj.propertyNameFlow.onEach { name ->
|
||||
obj.onPropertyChange(updateScope) { name ->
|
||||
if (
|
||||
name.startsWith(Solid.POSITION_KEY) ||
|
||||
name.startsWith(Solid.ROTATION) ||
|
||||
@ -78,7 +78,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
} else if (name == Vision.VISIBLE_KEY) {
|
||||
visible = obj.visible ?: true
|
||||
}
|
||||
}.launchIn(updateScope)
|
||||
}
|
||||
|
||||
obj.structureChanges.onEach { (nameToken, _, child) ->
|
||||
// if (name.isEmpty()) {
|
||||
|
@ -9,8 +9,6 @@ import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_
|
||||
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 object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
||||
@ -47,7 +45,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
||||
|
||||
//TODO apply child properties
|
||||
|
||||
obj.propertyNameFlow.onEach { name->
|
||||
obj.onPropertyChange(three.updateScope) { 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()
|
||||
@ -57,7 +55,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
||||
} else {
|
||||
object3D.updateProperty(obj, name)
|
||||
}
|
||||
}.launchIn(three.updateScope)
|
||||
}
|
||||
|
||||
|
||||
return object3D
|
||||
|
Loading…
Reference in New Issue
Block a user