Working server prototype

This commit is contained in:
Alexander Nozik 2020-12-08 22:42:08 +03:00
parent a7136d3eff
commit faddb8a393
13 changed files with 117 additions and 95 deletions

View File

@ -3,17 +3,19 @@ package ru.mipt.npm.sat
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.get import hep.dataforge.vision.get
import hep.dataforge.vision.server.visionModule import hep.dataforge.vision.server.close
import hep.dataforge.vision.server.serve
import hep.dataforge.vision.server.show
import hep.dataforge.vision.solid.Solid import hep.dataforge.vision.solid.Solid
import hep.dataforge.vision.solid.SolidManager import hep.dataforge.vision.solid.SolidManager
import hep.dataforge.vision.solid.color import hep.dataforge.vision.solid.color
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.html.h1
import kotlinx.html.script import kotlinx.html.script
import kotlin.random.Random import kotlin.random.Random
@ -23,28 +25,36 @@ fun main() {
ySegments = 3, ySegments = 3,
) )
val context = Global.context("SAT"){ val context = Global.context("SAT") {
plugin(SolidManager) plugin(SolidManager)
} }
embeddedServer(CIO, 8080, host = "localhost"){ val server = context.plugins.fetch(VisionManager).serve {
visionModule(context).apply { header {
header { script {
script { src = "sat-demo.js"
src = "sat-demo.js"
}
}
page {
vision("main".asName(), sat)
} }
} }
page {
h1 { +"Satellite detector demo" }
vision("main".asName(), sat)
}
launch { launch {
while (isActive){ while (isActive) {
val currentLayer = Random.nextInt(10) val currentLayer = Random.nextInt(10)
(sat["layer[$currentLayer]"] as? Solid)?.color(123) (sat["layer[$currentLayer]"] as? Solid)?.color(123)
delay(300) delay(300)
(sat["layer[$currentLayer]"] as? Solid)?.color = null (sat["layer[$currentLayer]"] as? Solid)?.color = null
} }
} }
}.start(wait = true) }
server.show()
println("Press Enter to close server")
while (readLine()!="exit"){
//
}
server.close()
} }

View File

@ -20,7 +20,7 @@ import org.w3c.dom.Element
import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
class ThreeDemoGrid(element: Element, idPrefix: String = "") : Page<Solid> { class ThreeDemoGrid(element: Element) : Page<Solid> {
private lateinit var navigationElement: HTMLElement private lateinit var navigationElement: HTMLElement
private lateinit var contentElement: HTMLDivElement private lateinit var contentElement: HTMLDivElement

View File

@ -5,23 +5,23 @@ import hep.dataforge.vision.Vision
import kotlinx.html.FlowContent import kotlinx.html.FlowContent
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
public class BindingHtmlOutputScope<T, V : Vision>( public class BindingOutputTagConsumer<T, V : Vision>(
root: TagConsumer<T>, root: TagConsumer<T>,
prefix: String? = null, prefix: String? = null,
) : HtmlOutputScope<T, V>(root, prefix) { ) : OutputTagConsumer<T, V>(root, prefix) {
private val _bindings = HashMap<Name, V>() private val _bindings = HashMap<Name, V>()
public val bindings: Map<Name, V> get() = _bindings public val bindings: Map<Name, V> get() = _bindings
override fun renderVision(htmlOutput: HtmlOutput<V>, vision: V) { override fun FlowContent.renderVision(name: Name, vision: V) {
_bindings[htmlOutput.name] = vision _bindings[name] = vision
} }
} }
public fun <T : Any> TagConsumer<T>.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> { public fun <T : Any> TagConsumer<T>.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> {
return BindingHtmlOutputScope<T, Vision>(this).apply(fragment.content).bindings return BindingOutputTagConsumer<T, Vision>(this).apply(fragment.content).bindings
} }
public fun FlowContent.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> { public fun FlowContent.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> {
return BindingHtmlOutputScope<Any?, Vision>(consumer).apply(fragment.content).bindings return BindingOutputTagConsumer<Any?, Vision>(consumer).apply(fragment.content).bindings
} }

View File

@ -14,7 +14,7 @@ public fun FlowContent.fragment(fragment: HtmlFragment) {
fragment.content(consumer) fragment.content(consumer)
} }
public class HtmlVisionFragment<V : Vision>(public val content: HtmlOutputScope<*, V>.() -> Unit) public class HtmlVisionFragment<V : Vision>(public val content: OutputTagConsumer<*, V>.() -> Unit)
public fun buildVisionFragment(block: HtmlOutputScope<*, Vision>.() -> Unit): HtmlVisionFragment<Vision> = public fun buildVisionFragment(block: OutputTagConsumer<*, Vision>.() -> Unit): HtmlVisionFragment<Vision> =
HtmlVisionFragment(block) HtmlVisionFragment(block)

View File

@ -5,47 +5,52 @@ import hep.dataforge.names.toName
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import kotlinx.html.* import kotlinx.html.*
public class HtmlOutput<V : Vision>( /**
public val outputScope: HtmlOutputScope<*, V>, * An HTML div wrapper that includes the output [name] and inherited [render] function
*/
public class OutputDiv<in V : Vision>(
private val div: DIV,
public val name: Name, public val name: Name,
public val div: DIV, public val render: (V) -> Unit,
) ) : HtmlBlockTag by div
public abstract class HtmlOutputScope<R, V : Vision>( /**
* Modified [TagConsumer] that allows rendering output fragments and visions in them
*/
public abstract class OutputTagConsumer<R, V : Vision>(
private val root: TagConsumer<R>, private val root: TagConsumer<R>,
public val idPrefix: String? = null, private val idPrefix: String? = null,
) : TagConsumer<R> by root { ) : TagConsumer<R> by root {
public open fun resolveId(name: Name): String = (idPrefix ?: "output:") + name.toString() public open fun resolveId(name: Name): String = (idPrefix ?: "output:") + name.toString()
/** /**
* Create a placeholder but do not attach any [Vision] to it * Render a vision inside the output fragment
*/ */
public inline fun <T> TagConsumer<T>.visionOutput( protected abstract fun FlowContent.renderVision(name: Name, vision: V)
/**
* Create a placeholder for an output window
*/
public fun <T> TagConsumer<T>.visionOutput(
name: Name, name: Name,
crossinline block: HtmlOutput<V>.() -> Unit = {}, block: OutputDiv<V>.() -> Unit = {},
): T = div { ): T = div {
id = resolveId(name) id = resolveId(name)
classes = setOf(OUTPUT_CLASS) classes = setOf(OUTPUT_CLASS)
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString() attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
@Suppress("UNCHECKED_CAST") OutputDiv<V>(this, name) { renderVision(name, it) }.block()
HtmlOutput(this@HtmlOutputScope, name, this).block()
} }
public fun <T> TagConsumer<T>.visionOutput(
public inline fun <T> TagConsumer<T>.visionOutput(
name: String, name: String,
crossinline block: HtmlOutput<V>.() -> Unit = {}, block: OutputDiv<V>.() -> Unit = {},
): T = visionOutput(name.toName(), block) ): T = visionOutput(name.toName(), block)
/**
* Create a placeholder and put a [Vision] in it
*/
public abstract fun renderVision(htmlOutput: HtmlOutput<V>, vision: V)
public fun <T> TagConsumer<T>.vision(name: Name, vision: V): Unit { public fun <T> TagConsumer<T>.vision(name: Name, vision: V): Unit {
visionOutput(name) { visionOutput(name) {
renderVision(this, vision) render(vision)
} }
} }

View File

@ -1,5 +1,6 @@
package hep.dataforge.vision.html package hep.dataforge.vision.html
import hep.dataforge.names.Name
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import kotlinx.html.FlowContent import kotlinx.html.FlowContent
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
@ -7,22 +8,23 @@ import kotlinx.html.stream.createHTML
public typealias HtmlVisionRenderer<V> = FlowContent.(V) -> Unit public typealias HtmlVisionRenderer<V> = FlowContent.(V) -> Unit
public class StaticHtmlOutputScope<R, V : Vision>( /**
* An [OutputTagConsumer] that directly renders given [Vision] using provided [renderer]
*/
public class StaticOutputTagConsumer<R, V : Vision>(
root: TagConsumer<R>, root: TagConsumer<R>,
prefix: String? = null, prefix: String? = null,
private val render: HtmlVisionRenderer<V>, private val renderer: HtmlVisionRenderer<V>,
) : HtmlOutputScope<R, V>(root, prefix) { ) : OutputTagConsumer<R, V>(root, prefix) {
override fun renderVision(htmlOutput: HtmlOutput<V>, vision: V) { override fun FlowContent.renderVision(name: Name, vision: V): Unit = renderer(vision)
htmlOutput.div.render(vision)
}
} }
public fun <T : Any> HtmlVisionFragment<Vision>.renderToObject( public fun <T : Any> HtmlVisionFragment<Vision>.renderToObject(
root: TagConsumer<T>, root: TagConsumer<T>,
prefix: String? = null, prefix: String? = null,
renderer: HtmlVisionRenderer<Vision>, renderer: HtmlVisionRenderer<Vision>,
): T = StaticHtmlOutputScope(root, prefix, renderer).apply(content).finalize() ): T = StaticOutputTagConsumer(root, prefix, renderer).apply(content).finalize()
public fun HtmlVisionFragment<Vision>.renderToString(renderer: HtmlVisionRenderer<Vision>): String = public fun HtmlVisionFragment<Vision>.renderToString(renderer: HtmlVisionRenderer<Vision>): String =
renderToObject(createHTML(), null, renderer) renderToObject(createHTML(), null, renderer)

View File

@ -10,16 +10,17 @@ import kotlin.test.Test
class HtmlTagTest { class HtmlTagTest {
fun HtmlOutput<Vision>.vision(block: Vision.() -> Unit) = fun OutputDiv<Vision>.visionBase(block: VisionBase.() -> Unit) =
outputScope.renderVision(this, VisionBase().apply(block)) render(VisionBase().apply(block))
val fragment = buildVisionFragment { val fragment = buildVisionFragment {
div { div {
h1 { +"Head" } h1 { +"Head" }
visionOutput("ddd") { visionOutput("ddd") {
vision { visionBase {
configure { configure {
set("myProp", 82) set("myProp", 82)
set("otherProp", false)
} }
} }
} }

View File

@ -2,12 +2,14 @@ package hep.dataforge.vision.client
import hep.dataforge.context.* import hep.dataforge.context.*
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.node
import hep.dataforge.vision.Vision 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.HtmlOutputScope import hep.dataforge.vision.html.OutputTagConsumer
import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_ENDPOINT_ATTRIBUTE import hep.dataforge.vision.html.OutputTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_NAME_ATTRIBUTE import hep.dataforge.vision.html.OutputTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
import org.w3c.dom.Element import org.w3c.dom.Element
@ -41,12 +43,12 @@ public class VisionClient : AbstractPlugin() {
getRenderers().maxByOrNull { it.rateVision(vision) } getRenderers().maxByOrNull { it.rateVision(vision) }
/** /**
* Fetch from server and render a vision, described in a given with [HtmlOutputScope.OUTPUT_CLASS] class. * Fetch from server and render a vision, described in a given with [OutputTagConsumer.OUTPUT_CLASS] class.
*/ */
public fun fetchAndRenderVision(element: Element, requestUpdates: Boolean = true) { public fun fetchAndRenderVision(element: Element, requestUpdates: Boolean = true) {
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") console.info("Found DF output with name $name")
if (!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element") if (!element.classList.contains(OutputTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element")
val endpoint = resolveEndpoint(element) val endpoint = resolveEndpoint(element)
console.info("Vision server is resolved to $endpoint") console.info("Vision server is resolved to $endpoint")
@ -60,9 +62,9 @@ public class VisionClient : AbstractPlugin() {
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") val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
renderer.render(element, vision) val rendererConfiguration = vision.properties[RENDERER_CONFIGURATION_META_KEY].node ?: Meta.EMPTY
renderer.render(element, vision, rendererConfiguration)
if (requestUpdates) { if (requestUpdates) {
val wsUrl = URL(endpoint).apply { val wsUrl = URL(endpoint).apply {
pathname += "/ws" pathname += "/ws"
@ -73,9 +75,10 @@ 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) {
// console.info("Received WS update: $stringData") val dif = visionManager.jsonFormat.decodeFromString(
val dif = visionManager.jsonFormat VisionChange.serializer(),
.decodeFromString(VisionChange.serializer(), stringData) stringData
)
vision.update(dif) vision.update(dif)
} else { } else {
console.error("WebSocket message data is not a string") console.error("WebSocket message data is not a string")
@ -103,6 +106,8 @@ public class VisionClient : AbstractPlugin() {
public companion object : PluginFactory<VisionClient> { public companion object : PluginFactory<VisionClient> {
public const val RENDERER_CONFIGURATION_META_KEY: String = "@renderer"
override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient() override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient()
override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP)
@ -112,10 +117,10 @@ public class VisionClient : AbstractPlugin() {
} }
/** /**
* Fetch and render visions for all elements with [HtmlOutputScope.OUTPUT_CLASS] class inside given [element]. * Fetch and render visions for all elements with [OutputTagConsumer.OUTPUT_CLASS] class inside given [element].
*/ */
public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates: Boolean = true) { public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates: Boolean = true) {
val elements = element.getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS) val elements = element.getElementsByClassName(OutputTagConsumer.OUTPUT_CLASS)
console.info("Finished search for outputs. Found ${elements.length} items") console.info("Finished search for outputs. Found ${elements.length} items")
elements.asList().forEach { child -> elements.asList().forEach { child ->
fetchAndRenderVision(child, requestUpdates) fetchAndRenderVision(child, requestUpdates)
@ -123,7 +128,7 @@ public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates:
} }
/** /**
* Fetch visions from the server for all elements with [HtmlOutputScope.OUTPUT_CLASS] class in the document body * Fetch visions from the server for all elements with [OutputTagConsumer.OUTPUT_CLASS] class in the document body
*/ */
public fun VisionClient.fetchAndRenderAllVisions(requestUpdates: Boolean = true) { public fun VisionClient.fetchAndRenderAllVisions(requestUpdates: Boolean = true) {
val element = document.body ?: error("Document does not have a body") val element = document.body ?: error("Document does not have a body")

View File

@ -1,13 +1,14 @@
package hep.dataforge.vision.client package hep.dataforge.vision.client
import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
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 import hep.dataforge.vision.Vision
import hep.dataforge.vision.html.BindingHtmlOutputScope import hep.dataforge.vision.html.BindingOutputTagConsumer
import hep.dataforge.vision.html.HtmlOutputScope
import hep.dataforge.vision.html.HtmlVisionFragment import hep.dataforge.vision.html.HtmlVisionFragment
import hep.dataforge.vision.html.OutputTagConsumer
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
import org.w3c.dom.* import org.w3c.dom.*
@ -25,7 +26,7 @@ public interface ElementVisionRenderer {
/** /**
* Display the [vision] inside a given [element] replacing its current content * Display the [vision] inside a given [element] replacing its current content
*/ */
public fun render(element: Element, vision: Vision): Unit public fun render(element: Element, vision: Vision, meta: Meta = Meta.EMPTY): Unit
public companion object { public companion object {
public const val TYPE: String = "elementVisionRenderer" public const val TYPE: String = "elementVisionRenderer"
@ -44,11 +45,11 @@ public fun Map<String, Vision>.bind(rendererFactory: (Vision) -> ElementVisionRe
@DFExperimental @DFExperimental
public fun Element.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer) { public fun Element.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer) {
val elements = getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS) val elements = getElementsByClassName(OutputTagConsumer.OUTPUT_CLASS)
elements.asList().forEach { element -> elements.asList().forEach { element ->
val name = element.attributes[HtmlOutputScope.OUTPUT_NAME_ATTRIBUTE]?.value val name = element.attributes[OutputTagConsumer.OUTPUT_NAME_ATTRIBUTE]?.value
if (name == null) { if (name == null) {
console.error("Attribute ${HtmlOutputScope.OUTPUT_NAME_ATTRIBUTE} not defined in the output element") console.error("Attribute ${OutputTagConsumer.OUTPUT_NAME_ATTRIBUTE} not defined in the output element")
return@forEach return@forEach
} }
val vision = visionProvider(name.toName()) val vision = visionProvider(name.toName())
@ -65,7 +66,7 @@ public fun Document.renderAllVisions(visionProvider: (Name) -> Vision, rendererF
public fun HtmlVisionFragment<Vision>.renderInDocument( public fun HtmlVisionFragment<Vision>.renderInDocument(
root: TagConsumer<HTMLElement>, root: TagConsumer<HTMLElement>,
renderer: ElementVisionRenderer, renderer: ElementVisionRenderer,
): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(content).let { scope -> ): HTMLElement = BindingOutputTagConsumer<HTMLElement, Vision>(root).apply(content).let { scope ->
scope.finalize().apply { scope.finalize().apply {
scope.bindings.forEach { (name, vision) -> scope.bindings.forEach { (name, vision) ->
val id = scope.resolveId(name) val id = scope.resolveId(name)

View File

@ -346,7 +346,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
final.prototypes { final.prototypes {
proto.children.forEach { (token, item) -> proto.children.forEach { (token, item) ->
item.parent = null item.parent = null
set(token.asName(), item) set(token.asName(), item as? Solid)
} }
} }
styleCache.forEach { styleCache.forEach {

View File

@ -29,10 +29,14 @@ import io.ktor.routing.application
import io.ktor.routing.get import io.ktor.routing.get
import io.ktor.routing.route import io.ktor.routing.route
import io.ktor.routing.routing import io.ktor.routing.routing
import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.util.KtorExperimentalAPI
import io.ktor.util.error import io.ktor.util.error
import io.ktor.websocket.WebSockets import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket import io.ktor.websocket.webSocket
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.html.* import kotlinx.html.*
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
@ -47,7 +51,7 @@ public class VisionServer internal constructor(
private val visionManager: VisionManager, private val visionManager: VisionManager,
private val application: Application, private val application: Application,
private val rootRoute: String, private val rootRoute: String,
) : Configurable { ) : Configurable, CoroutineScope by application {
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)
@ -78,23 +82,8 @@ public class VisionServer internal constructor(
title(title) title(title)
} }
body { body {
// attributes[OUTPUT_ENDPOINT_ATTRIBUTE] = if (rootRoute.endsWith("/")) {
// rootRoute
// } else {
// "$rootRoute/"
// }
//Load the fragment and remember all loaded visions //Load the fragment and remember all loaded visions
visionMap = visionFragment(visionFragment) visionMap = visionFragment(visionFragment)
// //The script runs when all headers already with required libraries are already loaded
// script {
// type = "text/javascript"
//
// val normalizedRoute =
// unsafe {
// //language=JavaScript
// +"fetchAndRenderAllVisions()"
// }
// }
} }
return visionMap return visionMap
@ -171,7 +160,7 @@ public class VisionServer internal constructor(
route: String = DEFAULT_PAGE, route: String = DEFAULT_PAGE,
title: String = "VisionForge server page '$route'", title: String = "VisionForge server page '$route'",
headers: List<HtmlFragment> = emptyList(), headers: List<HtmlFragment> = emptyList(),
content: HtmlOutputScope<*, Vision>.() -> Unit, content: OutputTagConsumer<*, Vision>.() -> Unit,
) { ) {
page(buildVisionFragment(content), route, title, headers) page(buildVisionFragment(content), route, title, headers)
} }
@ -215,6 +204,15 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA
return VisionServer(visionManager, this, route) return VisionServer(visionManager, this, route)
} }
@OptIn(KtorExperimentalAPI::class)
public fun VisionManager.serve(
host: String = "localhost",
port: Int = 7777,
block: VisionServer.()->Unit
): ApplicationEngine = context.embeddedServer(CIO, port, host) {
visionModule(context).apply(block)
}.start()
public fun ApplicationEngine.show() { public fun ApplicationEngine.show() {
val connector = environment.connectors.first() val connector = environment.connectors.first()
val uri = URI("http", null, connector.host, connector.port, null, null, null) val uri = URI("http", null, connector.host, connector.port, null, null, null)

View File

@ -65,9 +65,9 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
serializersModule = serializersModuleForSolids serializersModule = serializersModuleForSolids
} }
internal fun encodeToString(solid: Solid): String = jsonForSolids.encodeToString(PolymorphicSerializer(Vision::class), solid) public fun encodeToString(solid: Solid): String = jsonForSolids.encodeToString(PolymorphicSerializer(Vision::class), solid)
internal fun decodeFromString(str: String): Vision = jsonForSolids.decodeFromString(PolymorphicSerializer(Vision::class), str).also { fun decodeFromString(str: String): Solid = jsonForSolids.decodeFromString(PolymorphicSerializer(Solid::class), str).also {
if(it is VisionGroup){ if(it is VisionGroup){
it.attachChildren() it.attachChildren()
} }

View File

@ -133,8 +133,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
} }
override fun render(element: Element, vision: Vision) { override fun render(element: Element, vision: Vision, meta: Meta) {
createCanvas(element).render(vision as? Solid ?: error("Only solids are rendered")) createCanvas(element, Canvas3DOptions.read(meta)).render(vision as? Solid ?: error("Only solids are rendered"))
} }
public companion object : PluginFactory<ThreePlugin> { public companion object : PluginFactory<ThreePlugin> {