Working Server server provider.
This commit is contained in:
parent
9b42d4f186
commit
a85cd828e6
@ -1,20 +1,51 @@
|
||||
plugins {
|
||||
id("ru.mipt.npm.mpp")
|
||||
application
|
||||
}
|
||||
|
||||
val kvisionVersion: String = "3.16.2"
|
||||
|
||||
group = "ru.mipt.npm"
|
||||
|
||||
//val kvisionVersion: String = "3.16.2"
|
||||
|
||||
kscience{
|
||||
useSerialization{
|
||||
json()
|
||||
}
|
||||
application()
|
||||
}
|
||||
|
||||
val ktorVersion: String by rootProject.extra
|
||||
|
||||
kotlin {
|
||||
afterEvaluate {
|
||||
val jsBrowserDistribution by tasks.getting
|
||||
|
||||
jvm {
|
||||
withJava()
|
||||
compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME]?.apply {
|
||||
tasks.getByName<ProcessResources>(processResourcesTaskName) {
|
||||
dependsOn(jsBrowserDistribution)
|
||||
afterEvaluate {
|
||||
from(jsBrowserDistribution)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-solid"))
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-server"))
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-threejs"))
|
||||
@ -22,3 +53,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("ru.mipt.npm.sat.SatServerKt")
|
||||
}
|
||||
|
@ -1,34 +1,40 @@
|
||||
package ru.mipt.npm.sat
|
||||
|
||||
import hep.dataforge.Application
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.invoke
|
||||
import hep.dataforge.startApplication
|
||||
import hep.dataforge.vision.client.VisionClient
|
||||
import hep.dataforge.vision.client.fetchAndRenderAllVisions
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import hep.dataforge.vision.solid.three.render
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.HTMLElement
|
||||
import kotlinx.browser.window
|
||||
|
||||
private class SatDemoApp : Application {
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
val element = document.getElementById("canvas") as? HTMLElement
|
||||
?: error("Element with id 'canvas' not found on page")
|
||||
val three = Global.plugins.fetch(ThreePlugin)
|
||||
val sat = visionOfSatellite(
|
||||
ySegments = 3,
|
||||
)
|
||||
three.render(element, sat){
|
||||
minSize = 500
|
||||
axes{
|
||||
size = 500.0
|
||||
visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//private class SatDemoApp : Application {
|
||||
//
|
||||
// override fun start(state: Map<String, Any>) {
|
||||
// val element = document.getElementById("canvas") as? HTMLElement
|
||||
// ?: error("Element with id 'canvas' not found on page")
|
||||
// val three = Global.plugins.fetch(ThreePlugin)
|
||||
//
|
||||
// val sat = visionOfSatellite(
|
||||
// ySegments = 3,
|
||||
// )
|
||||
// three.render(element, sat){
|
||||
// minSize = 500
|
||||
// axes{
|
||||
// size = 500.0
|
||||
// visible = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//fun main() {
|
||||
// startApplication(::SatDemoApp)
|
||||
//}
|
||||
|
||||
fun main() {
|
||||
startApplication(::SatDemoApp)
|
||||
//Loading three-js renderer
|
||||
Global.plugins.load(ThreePlugin)
|
||||
window.onload = {
|
||||
Global.plugins.fetch(VisionClient).fetchAndRenderAllVisions()
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package ru.mipt.npm.sat
|
||||
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vision.server.visionModule
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import io.ktor.server.cio.CIO
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.html.script
|
||||
|
||||
@OptIn(KtorExperimentalAPI::class)
|
||||
fun main() {
|
||||
val sat = visionOfSatellite(
|
||||
ySegments = 3,
|
||||
)
|
||||
|
||||
val context = Global.context("SAT"){
|
||||
plugin(SolidManager)
|
||||
}
|
||||
|
||||
embeddedServer(CIO, 8080, host = "localhost"){
|
||||
visionModule(context).apply {
|
||||
header {
|
||||
script {
|
||||
src = "sat-demo.js"
|
||||
}
|
||||
}
|
||||
page {
|
||||
vision("main".asName(), sat)
|
||||
}
|
||||
}
|
||||
}.start(wait = true)
|
||||
}
|
@ -12,16 +12,30 @@ repositories{
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js(IR) {
|
||||
browser {}
|
||||
browser {
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":visionforge-solid"))
|
||||
api(project(":visionforge-gdml"))
|
||||
api(project(":ui:bootstrap"))
|
||||
implementation(project(":visionforge-solid"))
|
||||
implementation(project(":visionforge-gdml"))
|
||||
|
||||
}
|
||||
}
|
||||
val jsMain by getting{
|
||||
dependencies {
|
||||
implementation(project(":ui:bootstrap"))
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting{
|
||||
dependencies {
|
||||
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,22 @@
|
||||
//package hep.dataforge.vision.solid
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import com.github.ricky12awesome.jss.encodeToSchema
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
fun main() {
|
||||
val schema = Json {
|
||||
serializersModule = SolidManager.serializersModuleForSolids
|
||||
prettyPrintIndent = " "
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
coerceInputValues = true
|
||||
encodeDefaults = true
|
||||
}.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false)
|
||||
println(schema)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//import hep.dataforge.meta.JSON_PRETTY
|
||||
//import kotlinx.serialization.*
|
@ -54,6 +54,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
|
||||
private val defaultSerialModule: SerializersModule = SerializersModule {
|
||||
polymorphic(Vision::class) {
|
||||
default { VisionBase.serializer() }
|
||||
subclass(VisionBase.serializer())
|
||||
subclass(VisionGroupBase.serializer())
|
||||
}
|
||||
|
@ -63,5 +63,7 @@ public abstract class HtmlOutputScope<R, V : Vision>(
|
||||
public companion object {
|
||||
public const val OUTPUT_CLASS: String = "visionforge-output"
|
||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||
public const val DEFAULT_ENDPOINT: String = "."
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package hep.dataforge.vision.client
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.HtmlOutputScope
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.url.URL
|
||||
|
||||
@JsExport
|
||||
public class ClientVisionManager {
|
||||
private val visionForgeContext: Context = Global.context("client") {
|
||||
plugin(VisionManager)
|
||||
}
|
||||
|
||||
private val visionManager: VisionManager = visionForgeContext.plugins.fetch(VisionManager)
|
||||
|
||||
/**
|
||||
* Up-going tree traversal in search for endpoint attribute
|
||||
*/
|
||||
private fun resolveEndpoint(element: Element?): String {
|
||||
if(element == null) return DEFAULT_ENDPOINT
|
||||
val attribute = element.attributes[OUTPUT_ENDPOINT_ATTRIBUTE]
|
||||
return attribute?.value ?: resolveEndpoint(element.parentElement)
|
||||
}
|
||||
|
||||
public fun renderVision(element: Element){
|
||||
if(!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element")
|
||||
val endpoint = URL(resolveEndpoint(element))
|
||||
window.fetch("$endpoint/vision").then {response->
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||
public const val DEFAULT_ENDPOINT: String = ".."
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,124 @@
|
||||
package hep.dataforge.vision.client
|
||||
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.HtmlOutputScope
|
||||
import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
||||
import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.WebSocket
|
||||
import org.w3c.dom.asList
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.url.URL
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class VisionClient : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
private val visionManager: VisionManager by require(VisionManager)
|
||||
|
||||
/**
|
||||
* Up-going tree traversal in search for endpoint attribute
|
||||
*/
|
||||
private fun resolveEndpoint(element: Element?): String {
|
||||
if (element == null) return window.location.href
|
||||
val attribute = element.attributes[OUTPUT_ENDPOINT_ATTRIBUTE]
|
||||
return attribute?.value ?: resolveEndpoint(element.parentElement)
|
||||
}
|
||||
|
||||
private fun resolveName(element: Element): String? {
|
||||
val attribute = element.attributes[OUTPUT_NAME_ATTRIBUTE]
|
||||
return attribute?.value
|
||||
}
|
||||
|
||||
private fun getRenderers() = context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values
|
||||
|
||||
public fun findRendererFor(vision: Vision): ElementVisionRenderer? = getRenderers().maxByOrNull { it.rateVision(vision) }
|
||||
|
||||
/**
|
||||
* Fetch from server and render a vision, described in a given with [HtmlOutputScope.OUTPUT_CLASS] class.
|
||||
*/
|
||||
public fun fetchAndRenderVision(element: Element, requestUpdates: Boolean = true) {
|
||||
val name = resolveName(element) ?: error("The element is not a vision output")
|
||||
console.info("Found DF output with name $name")
|
||||
if (!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element")
|
||||
val endpoint = resolveEndpoint(element)
|
||||
console.info("Vision server is resolved to $endpoint")
|
||||
val fetchUrl = URL(endpoint).apply {
|
||||
searchParams.append("name", name)
|
||||
pathname += "/vision"
|
||||
}
|
||||
window.fetch(fetchUrl).then { response ->
|
||||
if (response.ok) {
|
||||
response.text().then { text ->
|
||||
val vision = visionManager.jsonFormat.decodeFromString(Vision.serializer(), text)
|
||||
|
||||
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
||||
renderer.render(element, vision)
|
||||
if (requestUpdates) {
|
||||
val wsUrl = URL(endpoint).apply {
|
||||
pathname += "/ws"
|
||||
protocol = "ws"
|
||||
searchParams.append("name", name)
|
||||
}
|
||||
val ws = WebSocket(wsUrl.toString()).apply {
|
||||
onmessage = { messageEvent ->
|
||||
val stringData: String? = messageEvent.data as? String
|
||||
if (stringData != null) {
|
||||
val update = visionManager.jsonFormat.decodeFromString(Vision.serializer(), text)
|
||||
vision.update(update)
|
||||
} 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 {
|
||||
console.error("Failed to fetch initial vision state from $endpoint")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<VisionClient> {
|
||||
|
||||
override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient()
|
||||
|
||||
override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override val type: KClass<out VisionClient> = VisionClient::class
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and render visions for all elements with [HtmlOutputScope.OUTPUT_CLASS] class inside given [element].
|
||||
*/
|
||||
public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates: Boolean = true) {
|
||||
val elements = element.getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS)
|
||||
console.info("Finished search for outputs. Found ${elements.length} items")
|
||||
elements.asList().forEach { child ->
|
||||
fetchAndRenderVision(child, requestUpdates)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch visions from the server for all elements with [HtmlOutputScope.OUTPUT_CLASS] class in the document body
|
||||
*/
|
||||
public fun VisionClient.fetchAndRenderAllVisions(requestUpdates: Boolean = true){
|
||||
val element = document.body ?: error("Document does not have a body")
|
||||
fetchVisionsInChildren(element, requestUpdates)
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package hep.dataforge.vision.client
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.html.BindingHtmlOutputScope
|
||||
import hep.dataforge.vision.html.HtmlOutputScope
|
||||
@ -10,18 +12,38 @@ import kotlinx.browser.document
|
||||
import kotlinx.html.TagConsumer
|
||||
import org.w3c.dom.*
|
||||
|
||||
public interface ElementVisionRenderer<in V : Vision> {
|
||||
public fun render(element: Element, vision: V): Unit
|
||||
}
|
||||
@Type(ElementVisionRenderer.TYPE)
|
||||
public interface ElementVisionRenderer {
|
||||
|
||||
public fun <V : Vision> Map<String, V>.bind(renderer: ElementVisionRenderer<V>) {
|
||||
forEach { (id, vision) ->
|
||||
val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
||||
renderer.render(element, vision)
|
||||
/**
|
||||
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
||||
* can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify
|
||||
* higher value in order to "steal" rendering job
|
||||
*/
|
||||
public fun rateVision(vision: Vision): Int
|
||||
|
||||
/**
|
||||
* Display the [vision] inside a given [element] replacing its current content
|
||||
*/
|
||||
public fun render(element: Element, vision: Vision): Unit
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "elementVisionRenderer"
|
||||
public const val ZERO_RATING: Int = 0
|
||||
public const val DEFAULT_RATING: Int = 10
|
||||
}
|
||||
}
|
||||
|
||||
public fun <V : Vision> Element.renderVisions(renderer: ElementVisionRenderer<V>, visionProvider: (Name) -> V?) {
|
||||
@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(HtmlOutputScope.OUTPUT_CLASS)
|
||||
elements.asList().forEach { element ->
|
||||
val name = element.attributes[HtmlOutputScope.OUTPUT_NAME_ATTRIBUTE]?.value
|
||||
@ -30,21 +52,19 @@ public fun <V : Vision> Element.renderVisions(renderer: ElementVisionRenderer<V>
|
||||
return@forEach
|
||||
}
|
||||
val vision = visionProvider(name.toName())
|
||||
if (vision == null) {
|
||||
console.error("Vision with name $name is not resolved")
|
||||
return@forEach
|
||||
}
|
||||
renderer.render(element, vision)
|
||||
rendererFactory(vision).render(element, vision)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <V : Vision> Document.renderVisions(renderer: ElementVisionRenderer<V>, visionProvider: (Name) -> V?): Unit {
|
||||
documentElement?.renderVisions(renderer, visionProvider)
|
||||
@DFExperimental
|
||||
public fun Document.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer): Unit {
|
||||
documentElement?.renderAllVisions(visionProvider,rendererFactory)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun HtmlVisionFragment<Vision>.renderInDocument(
|
||||
root: TagConsumer<HTMLElement>,
|
||||
renderer: ElementVisionRenderer<Vision>,
|
||||
renderer: ElementVisionRenderer,
|
||||
): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(content).let { scope ->
|
||||
scope.finalize().apply {
|
||||
scope.bindings.forEach { (name, vision) ->
|
||||
|
@ -14,6 +14,7 @@ import hep.dataforge.vision.html.*
|
||||
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
import io.ktor.application.*
|
||||
import io.ktor.features.CORS
|
||||
import io.ktor.features.CallLogging
|
||||
import io.ktor.html.respondHtml
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@ -23,7 +24,10 @@ import io.ktor.http.content.static
|
||||
import io.ktor.http.withCharset
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.routing.application
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.route
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import io.ktor.websocket.WebSockets
|
||||
import io.ktor.websocket.webSocket
|
||||
@ -34,9 +38,12 @@ import java.awt.Desktop
|
||||
import java.net.URI
|
||||
import kotlin.time.milliseconds
|
||||
|
||||
/**
|
||||
* A ktor plugin container with given [routing]
|
||||
*/
|
||||
public class VisionServer internal constructor(
|
||||
private val visionManager: VisionManager,
|
||||
private val routing: Routing,
|
||||
private val application: Application,
|
||||
private val rootRoute: String,
|
||||
) : Configurable {
|
||||
override val config: Config = Config()
|
||||
@ -57,7 +64,7 @@ public class VisionServer internal constructor(
|
||||
title: String,
|
||||
headers: List<HtmlFragment>,
|
||||
): Map<Name, Vision> {
|
||||
lateinit var result: Map<Name, Vision>
|
||||
lateinit var visionMap: Map<Name, Vision>
|
||||
|
||||
head {
|
||||
meta {
|
||||
@ -69,21 +76,26 @@ public class VisionServer internal constructor(
|
||||
title(title)
|
||||
}
|
||||
body {
|
||||
result = visionFragment(visionFragment)
|
||||
script {
|
||||
type = "text/javascript"
|
||||
|
||||
val normalizedRoute = if (rootRoute.endsWith("/")) {
|
||||
rootRoute
|
||||
} else {
|
||||
"$rootRoute/"
|
||||
}
|
||||
|
||||
src = TODO()//"${normalizedRoute}js/plotlyConnect.js"
|
||||
}
|
||||
// attributes[OUTPUT_ENDPOINT_ATTRIBUTE] = if (rootRoute.endsWith("/")) {
|
||||
// rootRoute
|
||||
// } else {
|
||||
// "$rootRoute/"
|
||||
// }
|
||||
//Load the fragment and remember all loaded visions
|
||||
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 result
|
||||
return visionMap
|
||||
}
|
||||
|
||||
public fun page(
|
||||
@ -102,56 +114,50 @@ public class VisionServer internal constructor(
|
||||
null
|
||||
}
|
||||
|
||||
routing.createRouteFromPath(rootRoute).apply {
|
||||
route(route) {
|
||||
//Update websocket
|
||||
webSocket("ws") {
|
||||
val name: String = call.request.queryParameters["name"]
|
||||
?: error("Vision name is not defined in parameters")
|
||||
application.routing {
|
||||
route(rootRoute) {
|
||||
route(route) {
|
||||
//Update websocket
|
||||
webSocket("ws") {
|
||||
val name: String = call.request.queryParameters["name"]
|
||||
?: error("Vision name is not defined in parameters")
|
||||
|
||||
application.log.debug("Opened server socket for $name")
|
||||
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
|
||||
try {
|
||||
vision.flowChanges(this, updateInterval.milliseconds).collect { update ->
|
||||
val json = visionManager.encodeToString(update)
|
||||
outgoing.send(Frame.Text(json))
|
||||
application.log.debug("Opened server socket for $name")
|
||||
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
|
||||
try {
|
||||
vision.flowChanges(this, updateInterval.milliseconds).collect { update ->
|
||||
val json = visionManager.encodeToString(update)
|
||||
outgoing.send(Frame.Text(json))
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
application.log.debug("Closed server socket for $name")
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
application.log.debug("Closed server socket for $name")
|
||||
}
|
||||
}
|
||||
//Plots in their json representation
|
||||
get("vision") {
|
||||
val name: String = call.request.queryParameters["name"]
|
||||
?: error("Vision name is not defined in parameters")
|
||||
//Plots in their json representation
|
||||
get("vision") {
|
||||
val name: String = call.request.queryParameters["name"]
|
||||
?: error("Vision name is not defined in parameters")
|
||||
|
||||
val vision: Vision? = visions[name.toName()]
|
||||
if (vision == null) {
|
||||
call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found")
|
||||
} else {
|
||||
call.respondText(
|
||||
visionManager.encodeToString(vision),
|
||||
contentType = ContentType.Application.Json,
|
||||
status = HttpStatusCode.OK
|
||||
)
|
||||
}
|
||||
}
|
||||
//filled pages
|
||||
get {
|
||||
// val origin = call.request.origin
|
||||
// val url = URLBuilder().apply {
|
||||
// protocol = URLProtocol.createOrDefault(origin.scheme)
|
||||
// //workaround for https://github.com/ktorio/ktor/issues/1663
|
||||
// host = if (origin.host.startsWith("0:")) "[${origin.host}]" else origin.host
|
||||
// port = origin.port
|
||||
// encodedPath = origin.uri
|
||||
// }.build()
|
||||
if (cachedHtml == null) {
|
||||
call.respondHtml {
|
||||
visions.putAll(buildPage(visionFragment, title, headers))
|
||||
val vision: Vision? = visions[name.toName()]
|
||||
if (vision == null) {
|
||||
call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found")
|
||||
} else {
|
||||
call.respondText(
|
||||
visionManager.encodeToString(vision),
|
||||
contentType = ContentType.Application.Json,
|
||||
status = HttpStatusCode.OK
|
||||
)
|
||||
}
|
||||
}
|
||||
//filled pages
|
||||
get {
|
||||
if (cachedHtml == null) {
|
||||
call.respondHtml {
|
||||
visions.putAll(buildPage(visionFragment, title, headers))
|
||||
}
|
||||
} else {
|
||||
call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
|
||||
}
|
||||
} else {
|
||||
call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,7 +166,7 @@ public class VisionServer internal constructor(
|
||||
|
||||
public fun page(
|
||||
route: String = DEFAULT_PAGE,
|
||||
title: String = "Plotly server page '$route'",
|
||||
title: String = "VisionForge server page '$route'",
|
||||
headers: List<HtmlFragment> = emptyList(),
|
||||
content: HtmlOutputScope<*, Vision>.() -> Unit,
|
||||
) {
|
||||
@ -170,7 +176,6 @@ public class VisionServer internal constructor(
|
||||
|
||||
public companion object {
|
||||
public const val DEFAULT_PAGE: String = "/"
|
||||
public val UPDATE_MODE_KEY: Name = "update.mode".toName()
|
||||
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
|
||||
}
|
||||
}
|
||||
@ -190,8 +195,12 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA
|
||||
}
|
||||
}
|
||||
|
||||
if(featureOrNull(CallLogging) == null){
|
||||
install(CallLogging)
|
||||
}
|
||||
|
||||
val routing = routing {
|
||||
|
||||
routing {
|
||||
route(route) {
|
||||
static {
|
||||
resources()
|
||||
@ -201,7 +210,7 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA
|
||||
|
||||
val visionManager = context.plugins.fetch(VisionManager)
|
||||
|
||||
return VisionServer(visionManager, routing, route)
|
||||
return VisionServer(visionManager, this, route)
|
||||
}
|
||||
|
||||
public fun ApplicationEngine.show() {
|
||||
|
@ -12,7 +12,7 @@ public class Box(
|
||||
public val xSize: Float,
|
||||
public val ySize: Float,
|
||||
public val zSize: Float
|
||||
) : BasicSolid(), GeometrySolid {
|
||||
) : SolidBase(), GeometrySolid {
|
||||
|
||||
//TODO add helper for color configuration
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
|
@ -19,7 +19,7 @@ public class Composite(
|
||||
public val compositeType: CompositeType,
|
||||
public val first: Solid,
|
||||
public val second: Solid
|
||||
) : BasicSolid(), Solid, VisionGroup {
|
||||
) : SolidBase(), Solid, VisionGroup {
|
||||
|
||||
init {
|
||||
first.parent = this
|
||||
|
@ -18,7 +18,7 @@ public class ConeSegment(
|
||||
public var upperRadius: Float,
|
||||
public var startAngle: Float = 0f,
|
||||
public var angle: Float = PI2
|
||||
) : BasicSolid(), GeometrySolid {
|
||||
) : SolidBase(), GeometrySolid {
|
||||
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
val segments = detail ?: 8
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("solid.convex")
|
||||
public class Convex(public val points: List<Point3D>) : BasicSolid(), Solid
|
||||
public class Convex(public val points: List<Point3D>) : SolidBase(), Solid
|
||||
|
||||
public inline fun VisionContainerBuilder<Solid>.convex(name: String = "", action: ConvexBuilder.() -> Unit = {}): Convex =
|
||||
ConvexBuilder().apply(action).build().also { set(name, it) }
|
||||
|
@ -39,7 +39,7 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo
|
||||
public class Extruded(
|
||||
public var shape: List<Point2D> = ArrayList(),
|
||||
public var layers: MutableList<Layer> = ArrayList()
|
||||
) : BasicSolid(), GeometrySolid {
|
||||
) : SolidBase(), GeometrySolid {
|
||||
|
||||
public fun shape(block: Shape2DBuilder.() -> Unit) {
|
||||
this.shape = Shape2DBuilder().apply(block).build()
|
||||
|
@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("solid.line")
|
||||
public class PolyLine(public var points: List<Point3D>) : BasicSolid(), Solid {
|
||||
public class PolyLine(public var points: List<Point3D>) : SolidBase(), Solid {
|
||||
|
||||
//var lineType by string()
|
||||
public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
||||
|
@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("solid")
|
||||
public open class BasicSolid : VisionBase(), Solid {
|
||||
public open class SolidBase : VisionBase(), Solid {
|
||||
override val descriptor: NodeDescriptor get() = Solid.descriptor
|
||||
|
||||
override var position: Point3D? = null
|
@ -11,7 +11,7 @@ public class SolidLabel(
|
||||
public var text: String,
|
||||
public var fontSize: Double,
|
||||
public var fontFamily: String,
|
||||
) : BasicSolid(), Solid
|
||||
) : SolidBase(), Solid
|
||||
|
||||
public fun VisionContainerBuilder<Solid>.label(
|
||||
text: String,
|
||||
|
@ -7,10 +7,7 @@ import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.VisionGroupBase
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
@ -42,6 +39,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
subclass(Composite.serializer())
|
||||
subclass(Tube.serializer())
|
||||
subclass(Box.serializer())
|
||||
subclass(ConeSegment.serializer())
|
||||
subclass(Convex.serializer())
|
||||
subclass(Extruded.serializer())
|
||||
subclass(PolyLine.serializer())
|
||||
@ -51,11 +49,13 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
|
||||
public val serializersModuleForSolids: SerializersModule = SerializersModule {
|
||||
polymorphic(Vision::class) {
|
||||
subclass(VisionBase.serializer())
|
||||
subclass(VisionGroupBase.serializer())
|
||||
solids()
|
||||
}
|
||||
|
||||
polymorphic(Solid::class) {
|
||||
default { SolidBase.serializer() }
|
||||
solids()
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.collections.set
|
||||
|
||||
public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
||||
public abstract class AbstractReference : SolidBase(), VisionGroup {
|
||||
public abstract val prototype: Solid
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||
|
@ -16,7 +16,7 @@ public class Sphere(
|
||||
public var phi: Float = PI2,
|
||||
public var thetaStart: Float = 0f,
|
||||
public var theta: Float = PI.toFloat(),
|
||||
) : BasicSolid(), GeometrySolid {
|
||||
) : SolidBase(), GeometrySolid {
|
||||
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D {
|
||||
|
@ -19,7 +19,7 @@ public class Tube(
|
||||
public var innerRadius: Float = 0f,
|
||||
public var startAngle: Float = 0f,
|
||||
public var angle: Float = PI2,
|
||||
) : BasicSolid(), GeometrySolid {
|
||||
) : SolidBase(), GeometrySolid {
|
||||
|
||||
init {
|
||||
require(radius > 0)
|
||||
|
@ -15,7 +15,7 @@ import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
import info.laht.threekt.objects.Group as ThreeGroup
|
||||
|
||||
public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer<Solid> {
|
||||
public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
public val solidManager: SolidManager by require(SolidManager)
|
||||
@ -122,8 +122,19 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer<Solid> {
|
||||
attach(element)
|
||||
}
|
||||
|
||||
override fun render(element: Element, vision: Solid) {
|
||||
createCanvas(element).render(vision)
|
||||
override fun content(target: String): Map<Name, Any> {
|
||||
return when (target) {
|
||||
ElementVisionRenderer.TYPE -> mapOf("three".asName() to this)
|
||||
else -> super.content(target)
|
||||
}
|
||||
}
|
||||
|
||||
override fun rateVision(vision: Vision): Int {
|
||||
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
|
||||
}
|
||||
|
||||
override fun render(element: Element, vision: Vision) {
|
||||
createCanvas(element).render(vision as? Solid ?: error("Only solids are rendered"))
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<ThreePlugin> {
|
||||
|
@ -1,11 +1,11 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
|
||||
import hep.dataforge.vision.solid.BasicSolid
|
||||
import hep.dataforge.vision.solid.SolidBase
|
||||
import info.laht.threekt.core.Object3D
|
||||
|
||||
/**
|
||||
* A custom visual object that has its own Three.js renderer
|
||||
*/
|
||||
public abstract class ThreeVision : BasicSolid() {
|
||||
public abstract class ThreeVision : SolidBase() {
|
||||
public abstract fun render(): Object3D
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user