v0.2.0-dev-22 #47
@ -1,12 +1,13 @@
|
||||
package ru.mipt.npm.sat
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.ResourceLocation
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.fragment
|
||||
import hep.dataforge.vision.solid.box
|
||||
import hep.dataforge.vision.three.server.makeFile
|
||||
import hep.dataforge.vision.three.server.solid
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
fun main() {
|
||||
val fragment = VisionManager.fragment {
|
||||
vision("canvas") {
|
||||
|
@ -117,5 +117,4 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
width = 100.pct
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,6 @@ import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.number
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import hep.dataforge.values.string
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
|
@ -6,8 +6,6 @@ import hep.dataforge.names.plus
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.*
|
||||
import kotlin.time.Duration
|
||||
|
||||
@ -15,13 +13,16 @@ import kotlin.time.Duration
|
||||
* An update for a [Vision] or a [VisionGroup]
|
||||
*/
|
||||
public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||
|
||||
private val propertyChange = HashMap<Name, Config>()
|
||||
private val childrenChange = HashMap<Name, Vision?>()
|
||||
|
||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
|
||||
|
||||
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) {
|
||||
propertyChange.getOrPut(visionName) { Config() }.setItem(propertyName, item)
|
||||
propertyChange
|
||||
.getOrPut(visionName) { Config() }
|
||||
.setItem(propertyName, item)
|
||||
}
|
||||
|
||||
override fun set(name: Name, child: Vision?) {
|
||||
@ -32,7 +33,7 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||
* Isolate collected changes by creating detached copies of given visions
|
||||
*/
|
||||
public fun isolate(manager: VisionManager): VisionChange = VisionChange(
|
||||
propertyChange,
|
||||
propertyChange.mapValues { it.value.seal() },
|
||||
childrenChange.mapValues { it.value?.isolate(manager) }
|
||||
)
|
||||
//TODO optimize isolation for visions without parents?
|
||||
@ -64,42 +65,40 @@ public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilde
|
||||
private fun CoroutineScope.collectChange(
|
||||
name: Name,
|
||||
source: Vision,
|
||||
mutex: Mutex,
|
||||
collector: () -> VisionChangeBuilder,
|
||||
) {
|
||||
|
||||
//Collect properties change
|
||||
source.config.onChange(mutex) { propertyName, oldItem, newItem ->
|
||||
source.config.onChange(this) { propertyName, oldItem, newItem ->
|
||||
if (oldItem != newItem) {
|
||||
launch {
|
||||
mutex.withLock {
|
||||
collector().propertyChanged(name, propertyName, newItem)
|
||||
}
|
||||
collector().propertyChanged(name, propertyName, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
source.config.removeListener(mutex)
|
||||
source.config.removeListener(this)
|
||||
}
|
||||
|
||||
if (source is VisionGroup) {
|
||||
//Subscribe for children changes
|
||||
source.children.forEach { (token, child) ->
|
||||
collectChange(name + token, child, mutex, collector)
|
||||
collectChange(name + token, child, collector)
|
||||
}
|
||||
|
||||
//Subscribe for structure change
|
||||
if (source is MutableVisionGroup) {
|
||||
source.onStructureChange(mutex) { token, before, after ->
|
||||
before?.removeChangeListener(mutex)
|
||||
(before as? MutableVisionGroup)?.removeStructureChangeListener(mutex)
|
||||
source.onStructureChange(this) { token, before, after ->
|
||||
before?.removeChangeListener(this)
|
||||
(before as? MutableVisionGroup)?.removeStructureChangeListener(this)
|
||||
if (after != null) {
|
||||
collectChange(name + token, after, mutex, collector)
|
||||
collectChange(name + token, after, collector)
|
||||
}
|
||||
collector()[name + token] = after
|
||||
}
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
source.removeStructureChangeListener(mutex)
|
||||
source.removeStructureChangeListener(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,10 +109,9 @@ public fun Vision.flowChanges(
|
||||
manager: VisionManager,
|
||||
collectionDuration: Duration,
|
||||
): Flow<VisionChange> = flow {
|
||||
val mutex = Mutex()
|
||||
|
||||
var collector = VisionChangeBuilder()
|
||||
manager.context.collectChange(Name.EMPTY, this@flowChanges, mutex) { collector }
|
||||
manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector }
|
||||
|
||||
while (currentCoroutineContext().isActive) {
|
||||
//Wait for changes to accumulate
|
||||
@ -121,11 +119,9 @@ public fun Vision.flowChanges(
|
||||
//Propagate updates only if something is changed
|
||||
if (!collector.isEmpty()) {
|
||||
//emit changes
|
||||
mutex.withLock {
|
||||
emit(collector.isolate(manager))
|
||||
//Reset the collector
|
||||
collector = VisionChangeBuilder()
|
||||
}
|
||||
emit(collector.isolate(manager))
|
||||
//Reset the collector
|
||||
collector = VisionChangeBuilder()
|
||||
}
|
||||
}
|
||||
}
|
@ -97,8 +97,8 @@ public abstract class VisionTagConsumer<R>(
|
||||
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
|
||||
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data"
|
||||
|
||||
public const val OUTPUT_FETCH_VISION_ATTRIBUTE: String = "data-output-fetch-vision"
|
||||
public const val OUTPUT_FETCH_UPDATE_ATTRIBUTE: String = "data-output-fetch-update"
|
||||
public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch"
|
||||
public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect"
|
||||
|
||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||
|
@ -7,8 +7,9 @@ import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionChange
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.VisionTagConsumer
|
||||
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
||||
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
||||
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_FETCH_UPDATE_ATTRIBUTE
|
||||
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE
|
||||
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.browser.window
|
||||
@ -23,6 +24,8 @@ public class VisionClient : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
private val visionManager: VisionManager by require(VisionManager)
|
||||
|
||||
private val visionMap = HashMap<Element, Vision>()
|
||||
|
||||
/**
|
||||
* Up-going tree traversal in search for endpoint attribute
|
||||
*/
|
||||
@ -39,19 +42,27 @@ public class VisionClient : AbstractPlugin() {
|
||||
|
||||
private fun getRenderers() = context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values
|
||||
|
||||
public fun findRendererFor(vision: Vision): ElementVisionRenderer? =
|
||||
private fun findRendererFor(vision: Vision): ElementVisionRenderer? =
|
||||
getRenderers().maxByOrNull { it.rateVision(vision) }
|
||||
|
||||
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML
|
||||
|
||||
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value == "true"
|
||||
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null
|
||||
|
||||
private fun renderVision(element: Element, vision: Vision?, outputMeta: Meta) {
|
||||
if (vision != null) {
|
||||
visionMap[element] = vision
|
||||
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
||||
renderer.render(element, vision, outputMeta)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch from server and render a vision, described in a given with [VisionTagConsumer.OUTPUT_CLASS] class.
|
||||
*/
|
||||
public fun renderVisionAt(element: Element) {
|
||||
val name = resolveName(element) ?: error("The element is not a vision output")
|
||||
console.info("Found DF output with name $name")
|
||||
logger.info { "Found DF output with name $name" }
|
||||
if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element")
|
||||
|
||||
|
||||
@ -63,66 +74,72 @@ public class VisionClient : AbstractPlugin() {
|
||||
val embeddedVision = element.getEmbeddedData(VisionTagConsumer.OUTPUT_DATA_CLASS)?.let {
|
||||
visionManager.decodeFromString(it)
|
||||
}
|
||||
|
||||
if (embeddedVision != null) {
|
||||
val renderer = findRendererFor(embeddedVision) ?: error("Could nof find renderer for $embeddedVision")
|
||||
renderer.render(element, embeddedVision, outputMeta)
|
||||
logger.info { "Found embedded vision for output with name $name" }
|
||||
renderVision(element, embeddedVision, outputMeta)
|
||||
}
|
||||
|
||||
if(element.getFlag(VisionTagConsumer.OUTPUT_FETCH_VISION_ATTRIBUTE)) {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
|
||||
val endpoint = resolveEndpoint(element)
|
||||
console.info("Vision server is resolved to $endpoint")
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
|
||||
|
||||
val fetchUrl = URL(endpoint).apply {
|
||||
searchParams.append("name", name)
|
||||
pathname += "/vision"
|
||||
}
|
||||
|
||||
console.info("Fetching vision data from $fetchUrl")
|
||||
logger.info { "Fetching vision data from $fetchUrl" }
|
||||
window.fetch(fetchUrl).then { response ->
|
||||
if (response.ok) {
|
||||
response.text().then { text ->
|
||||
val vision = visionManager.decodeFromString(text)
|
||||
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
||||
renderer.render(element, vision, outputMeta)
|
||||
if (element.getFlag(OUTPUT_FETCH_UPDATE_ATTRIBUTE)) {
|
||||
val wsUrl = URL(endpoint).apply {
|
||||
pathname += "/ws"
|
||||
protocol = "ws"
|
||||
searchParams.append("name", name)
|
||||
}
|
||||
WebSocket(wsUrl.toString()).apply {
|
||||
onmessage = { messageEvent ->
|
||||
val stringData: String? = messageEvent.data as? String
|
||||
if (stringData != null) {
|
||||
val dif = visionManager.jsonFormat.decodeFromString(
|
||||
VisionChange.serializer(),
|
||||
stringData
|
||||
)
|
||||
vision.update(dif)
|
||||
} else {
|
||||
console.error("WebSocket message data is not a string")
|
||||
}
|
||||
}
|
||||
onopen = {
|
||||
console.info("WebSocket update channel established for output '$name'")
|
||||
}
|
||||
onclose = {
|
||||
console.info("WebSocket update channel closed for output '$name'")
|
||||
}
|
||||
onerror = {
|
||||
console.error("WebSocket update channel error for output '$name'")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
renderVision(element, vision, outputMeta)
|
||||
}
|
||||
} else {
|
||||
console.error("Failed to fetch initial vision state from $endpoint")
|
||||
logger.error { "Failed to fetch initial vision state from $endpoint" }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let {
|
||||
|
||||
val wsUrl = URL(endpoint).apply {
|
||||
pathname += "/ws"
|
||||
protocol = "ws"
|
||||
searchParams.append("name", name)
|
||||
}
|
||||
|
||||
logger.info { "Updating vision data from $wsUrl" }
|
||||
|
||||
val ws = WebSocket(wsUrl.toString()).apply {
|
||||
onmessage = { messageEvent ->
|
||||
val stringData: String? = messageEvent.data as? String
|
||||
if (stringData != null) {
|
||||
val dif = visionManager.jsonFormat.decodeFromString(
|
||||
VisionChange.serializer(),
|
||||
stringData
|
||||
)
|
||||
logger.debug { "Got update $dif for output with name $name" }
|
||||
visionMap[element]?.update(dif)
|
||||
?: logger.info { "Target vision for element $element with name $name not found" }
|
||||
} else {
|
||||
console.error("WebSocket message data is not a string")
|
||||
}
|
||||
}
|
||||
onopen = {
|
||||
console.info("WebSocket update channel established for output '$name'")
|
||||
}
|
||||
onclose = {
|
||||
console.info("WebSocket update channel closed for output '$name'")
|
||||
}
|
||||
onerror = {
|
||||
console.error("WebSocket update channel error for output '$name'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<VisionClient> {
|
||||
|
@ -25,37 +25,4 @@ public interface ElementVisionRenderer {
|
||||
public const val ZERO_RATING: Int = 0
|
||||
public const val DEFAULT_RATING: Int = 10
|
||||
}
|
||||
}
|
||||
//
|
||||
//@DFExperimental
|
||||
//public fun Map<String, Vision>.bind(rendererFactory: (Vision) -> ElementVisionRenderer) {
|
||||
// forEach { (id, vision) ->
|
||||
// val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
||||
// rendererFactory(vision).render(element, vision)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@DFExperimental
|
||||
//public fun Element.renderAllVisions(
|
||||
// visionProvider: (Name) -> Vision,
|
||||
// rendererFactory: (Vision) -> ElementVisionRenderer,
|
||||
//) {
|
||||
// val elements = getElementsByClassName(VisionTagConsumer.OUTPUT_CLASS)
|
||||
// elements.asList().forEach { element ->
|
||||
// val name = element.attributes[VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE]?.value
|
||||
// if (name == null) {
|
||||
// console.error("Attribute ${VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE} not defined in the output element")
|
||||
// return@forEach
|
||||
// }
|
||||
// val vision = visionProvider(name.toName())
|
||||
// rendererFactory(vision).render(element, vision)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@DFExperimental
|
||||
//public fun Document.renderAllVisions(
|
||||
// visionProvider: (Name) -> Vision,
|
||||
// rendererFactory: (Vision) -> ElementVisionRenderer,
|
||||
//): Unit {
|
||||
// documentElement?.renderAllVisions(visionProvider, rendererFactory)
|
||||
//}
|
||||
}
|
@ -6,7 +6,6 @@ import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.values.string
|
||||
import javafx.scene.control.ColorPicker
|
||||
import javafx.scene.paint.Color
|
||||
import org.slf4j.LoggerFactory
|
||||
|
@ -12,7 +12,6 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.parseValue
|
||||
import hep.dataforge.values.string
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.scene.control.ComboBox
|
||||
import javafx.util.StringConverter
|
||||
|
@ -17,7 +17,6 @@
|
||||
package hep.dataforge.vision.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.values.string
|
||||
import hep.dataforge.vision.dfIconView
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.scene.control.TreeItem
|
||||
|
@ -6,7 +6,6 @@ import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import hep.dataforge.values.string
|
||||
import hep.dataforge.vision.Colors
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.Material
|
||||
|
@ -74,8 +74,8 @@ public class VisionServer internal constructor(
|
||||
visionMap[name] = vision
|
||||
|
||||
// Toggle updates
|
||||
attributes[OUTPUT_FETCH_VISION_ATTRIBUTE] = "true"
|
||||
attributes[OUTPUT_FETCH_UPDATE_ATTRIBUTE] = "true"
|
||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = "true"
|
||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vision.solid.three
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import hep.dataforge.values.string
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
|
Loading…
Reference in New Issue
Block a user