Refactor client

This commit is contained in:
Alexander Nozik 2020-12-13 20:25:36 +03:00
parent f4970955cb
commit 382686b9aa
13 changed files with 88 additions and 114 deletions

View File

@ -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.ResourceLocation
import hep.dataforge.vision.VisionManager import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.html.fragment 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.makeFile
import hep.dataforge.vision.three.server.solid import hep.dataforge.vision.three.server.solid
@OptIn(DFExperimental::class)
fun main() { fun main() {
val fragment = VisionManager.fragment { val fragment = VisionManager.fragment {
vision("canvas") { vision("canvas") {

View File

@ -117,5 +117,4 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
width = 100.pct width = 100.pct
} }
} }
} }

View File

@ -5,7 +5,6 @@ import hep.dataforge.meta.get
import hep.dataforge.meta.number import hep.dataforge.meta.number
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.int import hep.dataforge.values.int
import hep.dataforge.values.string
import kotlin.math.max import kotlin.math.max
/** /**

View File

@ -6,8 +6,6 @@ import hep.dataforge.names.plus
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlin.time.Duration import kotlin.time.Duration
@ -15,13 +13,16 @@ import kotlin.time.Duration
* An update for a [Vision] or a [VisionGroup] * An update for a [Vision] or a [VisionGroup]
*/ */
public class VisionChangeBuilder : VisionContainerBuilder<Vision> { public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
private val propertyChange = HashMap<Name, Config>() private val propertyChange = HashMap<Name, Config>()
private val childrenChange = HashMap<Name, Vision?>() private val childrenChange = HashMap<Name, Vision?>()
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty() public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) { 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?) { 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 * Isolate collected changes by creating detached copies of given visions
*/ */
public fun isolate(manager: VisionManager): VisionChange = VisionChange( public fun isolate(manager: VisionManager): VisionChange = VisionChange(
propertyChange, propertyChange.mapValues { it.value.seal() },
childrenChange.mapValues { it.value?.isolate(manager) } childrenChange.mapValues { it.value?.isolate(manager) }
) )
//TODO optimize isolation for visions without parents? //TODO optimize isolation for visions without parents?
@ -64,42 +65,40 @@ public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilde
private fun CoroutineScope.collectChange( private fun CoroutineScope.collectChange(
name: Name, name: Name,
source: Vision, source: Vision,
mutex: Mutex,
collector: () -> VisionChangeBuilder, collector: () -> VisionChangeBuilder,
) { ) {
//Collect properties change //Collect properties change
source.config.onChange(mutex) { propertyName, oldItem, newItem -> source.config.onChange(this) { propertyName, oldItem, newItem ->
if (oldItem != newItem) { if (oldItem != newItem) {
launch { launch {
mutex.withLock { collector().propertyChanged(name, propertyName, newItem)
collector().propertyChanged(name, propertyName, newItem)
}
} }
} }
} }
coroutineContext[Job]?.invokeOnCompletion { coroutineContext[Job]?.invokeOnCompletion {
source.config.removeListener(mutex) source.config.removeListener(this)
} }
if (source is VisionGroup) { if (source is VisionGroup) {
//Subscribe for children changes //Subscribe for children changes
source.children.forEach { (token, child) -> source.children.forEach { (token, child) ->
collectChange(name + token, child, mutex, collector) collectChange(name + token, child, collector)
} }
//Subscribe for structure change //Subscribe for structure change
if (source is MutableVisionGroup) { if (source is MutableVisionGroup) {
source.onStructureChange(mutex) { token, before, after -> source.onStructureChange(this) { token, before, after ->
before?.removeChangeListener(mutex) before?.removeChangeListener(this)
(before as? MutableVisionGroup)?.removeStructureChangeListener(mutex) (before as? MutableVisionGroup)?.removeStructureChangeListener(this)
if (after != null) { if (after != null) {
collectChange(name + token, after, mutex, collector) collectChange(name + token, after, collector)
} }
collector()[name + token] = after collector()[name + token] = after
} }
coroutineContext[Job]?.invokeOnCompletion { coroutineContext[Job]?.invokeOnCompletion {
source.removeStructureChangeListener(mutex) source.removeStructureChangeListener(this)
} }
} }
} }
@ -110,10 +109,9 @@ public fun Vision.flowChanges(
manager: VisionManager, manager: VisionManager,
collectionDuration: Duration, collectionDuration: Duration,
): Flow<VisionChange> = flow { ): Flow<VisionChange> = flow {
val mutex = Mutex()
var collector = VisionChangeBuilder() var collector = VisionChangeBuilder()
manager.context.collectChange(Name.EMPTY, this@flowChanges, mutex) { collector } manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector }
while (currentCoroutineContext().isActive) { while (currentCoroutineContext().isActive) {
//Wait for changes to accumulate //Wait for changes to accumulate
@ -121,11 +119,9 @@ public fun Vision.flowChanges(
//Propagate updates only if something is changed //Propagate updates only if something is changed
if (!collector.isEmpty()) { if (!collector.isEmpty()) {
//emit changes //emit changes
mutex.withLock { emit(collector.isolate(manager))
emit(collector.isolate(manager)) //Reset the collector
//Reset the collector collector = VisionChangeBuilder()
collector = VisionChangeBuilder()
}
} }
} }
} }

View File

@ -97,8 +97,8 @@ public abstract class VisionTagConsumer<R>(
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta" public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data" 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_ATTRIBUTE: String = "data-output-fetch"
public const val OUTPUT_FETCH_UPDATE_ATTRIBUTE: String = "data-output-fetch-update" public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect"
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name" public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint" public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"

View File

@ -7,8 +7,9 @@ import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionChange import hep.dataforge.vision.VisionChange
import hep.dataforge.vision.VisionManager import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.html.VisionTagConsumer 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_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 hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
@ -23,6 +24,8 @@ public class VisionClient : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
private val visionManager: VisionManager by require(VisionManager) private val visionManager: VisionManager by require(VisionManager)
private val visionMap = HashMap<Element, Vision>()
/** /**
* Up-going tree traversal in search for endpoint attribute * 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 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) } getRenderers().maxByOrNull { it.rateVision(vision) }
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML 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. * Fetch from server and render a vision, described in a given with [VisionTagConsumer.OUTPUT_CLASS] class.
*/ */
public fun renderVisionAt(element: Element) { public fun renderVisionAt(element: Element) {
val name = resolveName(element) ?: error("The element is not a vision output") 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") 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 { val embeddedVision = element.getEmbeddedData(VisionTagConsumer.OUTPUT_DATA_CLASS)?.let {
visionManager.decodeFromString(it) visionManager.decodeFromString(it)
} }
if (embeddedVision != null) { if (embeddedVision != null) {
val renderer = findRendererFor(embeddedVision) ?: error("Could nof find renderer for $embeddedVision") logger.info { "Found embedded vision for output with name $name" }
renderer.render(element, embeddedVision, outputMeta) 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) element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
console.info("Vision server is resolved to $endpoint")
val fetchUrl = URL(endpoint).apply { val fetchUrl = URL(endpoint).apply {
searchParams.append("name", name) searchParams.append("name", name)
pathname += "/vision" pathname += "/vision"
} }
console.info("Fetching vision data from $fetchUrl") logger.info { "Fetching vision data from $fetchUrl" }
window.fetch(fetchUrl).then { response -> window.fetch(fetchUrl).then { response ->
if (response.ok) { if (response.ok) {
response.text().then { text -> response.text().then { text ->
val vision = visionManager.decodeFromString(text) val vision = visionManager.decodeFromString(text)
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") renderVision(element, vision, outputMeta)
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'")
}
}
}
} }
} else { } 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> { public companion object : PluginFactory<VisionClient> {

View File

@ -26,36 +26,3 @@ public interface ElementVisionRenderer {
public const val DEFAULT_RATING: Int = 10 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)
//}

View File

@ -6,7 +6,6 @@ import hep.dataforge.names.asName
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import hep.dataforge.values.string
import javafx.scene.control.ColorPicker import javafx.scene.control.ColorPicker
import javafx.scene.paint.Color import javafx.scene.paint.Color
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory

View File

@ -12,7 +12,6 @@ import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.parseValue import hep.dataforge.values.parseValue
import hep.dataforge.values.string
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.scene.control.ComboBox import javafx.scene.control.ComboBox
import javafx.util.StringConverter import javafx.util.StringConverter

View File

@ -17,7 +17,6 @@
package hep.dataforge.vision.editor package hep.dataforge.vision.editor
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.values.string
import hep.dataforge.vision.dfIconView import hep.dataforge.vision.dfIconView
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.scene.control.TreeItem import javafx.scene.control.TreeItem

View File

@ -6,7 +6,6 @@ import hep.dataforge.meta.get
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.int import hep.dataforge.values.int
import hep.dataforge.values.string
import hep.dataforge.vision.Colors import hep.dataforge.vision.Colors
import javafx.scene.paint.Color import javafx.scene.paint.Color
import javafx.scene.paint.Material import javafx.scene.paint.Material

View File

@ -74,8 +74,8 @@ public class VisionServer internal constructor(
visionMap[name] = vision visionMap[name] = vision
// Toggle updates // Toggle updates
attributes[OUTPUT_FETCH_VISION_ATTRIBUTE] = "true" attributes[OUTPUT_FETCH_ATTRIBUTE] = "true"
attributes[OUTPUT_FETCH_UPDATE_ATTRIBUTE] = "true" attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true"
} }
} }

View File

@ -3,7 +3,6 @@ package hep.dataforge.vision.solid.three
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.int import hep.dataforge.values.int
import hep.dataforge.values.string
import hep.dataforge.vision.Colors import hep.dataforge.vision.Colors
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.solid.SolidMaterial import hep.dataforge.vision.solid.SolidMaterial