Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Alexander Nozik 2020-12-14 13:48:11 +03:00
commit c83954dca9
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.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") {

View File

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

View File

@ -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
/**

View File

@ -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()
}
}
}

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_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"

View File

@ -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> {

View File

@ -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)
//}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
}
}

View File

@ -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