diff --git a/demo/sat-demo/build.gradle.kts b/demo/sat-demo/build.gradle.kts index f5b9ef31..048ebd84 100644 --- a/demo/sat-demo/build.gradle.kts +++ b/demo/sat-demo/build.gradle.kts @@ -1,64 +1,20 @@ plugins { - id("ru.mipt.npm.mpp") + id("ru.mipt.npm.jvm") application } -group = "ru.mipt.npm" - -//val kvisionVersion: String = "3.16.2" - -kscience{ - useSerialization{ +kscience { + useSerialization { json() } application() } -val ktorVersion: String by rootProject.extra +group = "ru.mipt.npm" -kotlin { - js{ - browser { - webpackTask { - this.outputFileName = "visionforge-solid.js" - } - } - } - afterEvaluate { - val jsBrowserDistribution by tasks.getting - - jvm { - withJava() - compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME]?.apply { - tasks.getByName(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")) - } - } - } +dependencies{ + implementation(project(":visionforge-threejs:visionforge-threejs-server")) } application { diff --git a/demo/sat-demo/src/commonTest/kotlin/ru/mipt/npm/sat/GeometrySerializationTest.kt b/demo/sat-demo/src/commonTest/kotlin/ru/mipt/npm/sat/GeometrySerializationTest.kt deleted file mode 100644 index 8b9e3c23..00000000 --- a/demo/sat-demo/src/commonTest/kotlin/ru/mipt/npm/sat/GeometrySerializationTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ru.mipt.npm.sat - -import hep.dataforge.context.Global -import hep.dataforge.vision.solid.SolidManager -import kotlin.test.Test -import kotlin.test.assertEquals - -class GeometrySerializationTest { - @Test - fun testSerialization(){ - val geometry = visionOfSatellite() - val manager = Global.plugins.fetch(SolidManager) - val string = manager.visionManager.encodeToString(geometry) - val reconstructed = manager.visionManager.decodeFromString(string) - assertEquals(geometry.config,reconstructed.config) - } -} \ No newline at end of file diff --git a/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt b/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt deleted file mode 100644 index c30b2ef8..00000000 --- a/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ru.mipt.npm.sat - -import hep.dataforge.context.Global -import hep.dataforge.vision.client.VisionClient -import hep.dataforge.vision.client.renderAllVisions -import hep.dataforge.vision.solid.three.ThreePlugin -import kotlinx.browser.window - -fun main() { - //Loading three-js renderer - Global.plugins.load(ThreePlugin) - //Fetch from server and render visions for all outputs - window.onload = { - Global.plugins.fetch(VisionClient).renderAllVisions() - } -} \ No newline at end of file diff --git a/demo/sat-demo/src/commonMain/kotlin/ru/mipt/npm/sat/geometry.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt similarity index 100% rename from demo/sat-demo/src/commonMain/kotlin/ru/mipt/npm/sat/geometry.kt rename to demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt diff --git a/demo/sat-demo/src/jvmMain/kotlin/ru/mipt/npm/sat/satServer.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt similarity index 57% rename from demo/sat-demo/src/jvmMain/kotlin/ru/mipt/npm/sat/satServer.kt rename to demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt index 067a06f0..e65eb314 100644 --- a/demo/sat-demo/src/jvmMain/kotlin/ru/mipt/npm/sat/satServer.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt @@ -1,14 +1,11 @@ package ru.mipt.npm.sat -import hep.dataforge.context.Global import hep.dataforge.names.toName -import hep.dataforge.vision.server.* import hep.dataforge.vision.solid.Solid -import hep.dataforge.vision.solid.SolidManager import hep.dataforge.vision.solid.color -import hep.dataforge.vision.visionManager -import io.ktor.util.KtorExperimentalAPI +import hep.dataforge.vision.three.server.* +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -16,22 +13,13 @@ import kotlinx.html.div import kotlinx.html.h1 import kotlin.random.Random -@OptIn(KtorExperimentalAPI::class) fun main() { //Create a geometry val sat = visionOfSatellite(ySegments = 3) - val context = Global.context("SAT") { - //need to install solids extension, vision manager is installed automatically - plugin(SolidManager) - } - - // fetch vision manager - val visionManager = context.visionManager - val server = visionManager.serve { //use client library - useScript("visionforge-solid.js") + useThreeJs() //use css useCss("css/styles.css") page { @@ -44,9 +32,12 @@ fun main() { server.show() - context.launch { + GlobalScope.launch { while (isActive) { - val target = "layer[${Random.nextInt(1, 11)}].segment[${Random.nextInt(3)},${Random.nextInt(3)}]".toName() + val randomLayer = Random.nextInt(1, 11) + val randomI = Random.nextInt(1, 4) + val randomJ = Random.nextInt(1, 4) + val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName() (sat[target] as? Solid)?.color("red") delay(300) (sat[target] as? Solid)?.color = "green" @@ -54,7 +45,7 @@ fun main() { } } - println("Press Enter to close server") + println("Enter 'exit' to close server") while (readLine() != "exit") { // } diff --git a/demo/sat-demo/src/jvmMain/resources/css/styles.css b/demo/sat-demo/src/main/resources/css/styles.css similarity index 100% rename from demo/sat-demo/src/jvmMain/resources/css/styles.css rename to demo/sat-demo/src/main/resources/css/styles.css diff --git a/playground/build.gradle.kts b/playground/build.gradle.kts index 428bb779..45d20f6d 100644 --- a/playground/build.gradle.kts +++ b/playground/build.gradle.kts @@ -12,7 +12,14 @@ repositories{ } kotlin { - jvm() + jvm{ + compilations.all { + kotlinOptions.jvmTarget = "11" + } + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } js(IR) { browser { } @@ -36,6 +43,7 @@ kotlin { val jvmMain by getting{ dependencies { implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") + implementation(project(":visionforge-threejs:visionforge-threejs-server")) } } } diff --git a/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/fileExport.kt b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/fileExport.kt new file mode 100644 index 00000000..3dc29df2 --- /dev/null +++ b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/fileExport.kt @@ -0,0 +1,19 @@ +package ru.mipt.npm.sat + +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 + +fun main() { + val fragment = VisionManager.fragment { + vision("canvas") { + solid { + box(100, 100, 100) + } + } + } + + fragment.makeFile() +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e21ae40c..978abd5e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,6 +40,7 @@ include( ":visionforge-solid", ":visionforge-fx", ":visionforge-threejs", + ":visionforge-threejs:visionforge-threejs-server", ":visionforge-gdml", ":visionforge-server", ":demo:spatial-showcase", diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt index 9a09e52e..c800024f 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt @@ -110,23 +110,21 @@ public fun Vision.flowChanges( manager: VisionManager, collectionDuration: Duration, ): Flow = flow { - supervisorScope { - val mutex = Mutex() + val mutex = Mutex() - var collector = VisionChangeBuilder() - collectChange(Name.EMPTY, this@flowChanges, mutex) { collector } + var collector = VisionChangeBuilder() + manager.context.collectChange(Name.EMPTY, this@flowChanges, mutex) { collector } - while (currentCoroutineContext().isActive) { - //Wait for changes to accumulate - delay(collectionDuration) - //Propagate updates only if something is changed - if (!collector.isEmpty()) { - //emit changes - mutex.withLock { - emit(collector.isolate(manager)) - //Reset the collector - collector = VisionChangeBuilder() - } + while (currentCoroutineContext().isActive) { + //Wait for changes to accumulate + delay(collectionDuration) + //Propagate updates only if something is changed + if (!collector.isEmpty()) { + //emit changes + mutex.withLock { + emit(collector.isolate(manager)) + //Reset the collector + collector = VisionChangeBuilder() } } } diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlVisionFragment.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlVisionFragment.kt index 9f3999e2..bdea12b4 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlVisionFragment.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlVisionFragment.kt @@ -1,5 +1,7 @@ package hep.dataforge.vision.html +import hep.dataforge.meta.DFExperimental +import hep.dataforge.vision.VisionManager import kotlinx.html.FlowContent import kotlinx.html.TagConsumer @@ -13,4 +15,7 @@ public fun FlowContent.fragment(fragment: HtmlFragment) { fragment(consumer) } -public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit \ No newline at end of file +public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit + +@DFExperimental +public fun VisionManager.Companion.fragment(content: HtmlVisionFragment): VisionTagConsumer<*>.() -> Unit = content \ No newline at end of file diff --git a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt index a7da3719..f8e138c5 100644 --- a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt +++ b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/headers.kt @@ -1,5 +1,7 @@ package hep.dataforge.vision +import hep.dataforge.context.Context +import hep.dataforge.meta.DFExperimental import hep.dataforge.vision.html.HtmlFragment import kotlinx.html.link import kotlinx.html.script @@ -33,6 +35,8 @@ public enum class ResourceLocation { EMBED } +internal const val DATAFORGE_ASSETS_PATH = ".dataforge/assets" + /** * Check if the asset exists in given local location and put it there if it does not @@ -62,14 +66,11 @@ internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): * A header that automatically copies relevant scripts to given path */ internal fun fileScriptHeader( - basePath: Path, - scriptPath: Path, - resource: String + path: Path, ): HtmlFragment = { - val relativePath = checkOrStoreFile(basePath, scriptPath, resource) script { type = "text/javascript" - src = relativePath.toString() + src = path.toString() } } @@ -86,7 +87,7 @@ internal fun embedScriptHeader(resource: String): HtmlFragment = { internal fun fileCssHeader( basePath: Path, cssPath: Path, - resource: String + resource: String, ): HtmlFragment = { val relativePath = checkOrStoreFile(basePath, cssPath, resource) link { @@ -95,6 +96,37 @@ internal fun fileCssHeader( } } +/** + * Make a script header, automatically copying file to appropriate location + */ +@DFExperimental +public fun Context.Companion.scriptHeader( + scriptResource: String, + basePath: Path?, + resourceLocation: ResourceLocation, +): HtmlFragment { + val targetPath = if (basePath == null) null else { + when (resourceLocation) { + ResourceLocation.LOCAL -> checkOrStoreFile( + basePath, + Path.of(DATAFORGE_ASSETS_PATH), + scriptResource + ) + ResourceLocation.SYSTEM -> checkOrStoreFile( + Path.of("."), + Path.of(System.getProperty("user.home")).resolve(DATAFORGE_ASSETS_PATH), + scriptResource + ) + ResourceLocation.EMBED -> null + } + } + return if (targetPath == null) { + embedScriptHeader(scriptResource) + } else { + fileScriptHeader(targetPath) + } +} + // ///** // * A system-wide plotly store location diff --git a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt index 328425ee..f7539a52 100644 --- a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt +++ b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/htmlExport.kt @@ -1,5 +1,6 @@ package hep.dataforge.vision +import hep.dataforge.meta.DFExperimental import hep.dataforge.vision.html.* import kotlinx.html.* import kotlinx.html.stream.createHTML @@ -8,11 +9,17 @@ import java.nio.file.Files import java.nio.file.Path - /** * Make a file with the embedded vision data */ -public fun HtmlVisionFragment.makeFile(manager: VisionManager, vararg headers: HtmlFragment, path: Path? = null, show: Boolean = true) { +@DFExperimental +public fun HtmlVisionFragment.makeFile( + manager: VisionManager, + vararg headers: HtmlFragment, + path: Path? = null, + title: String = "VisionForge page", + show: Boolean = true, +) { val actualFile = path ?: Files.createTempFile("tempPlot", ".html") Files.createDirectories(actualFile.parent) val htmlString = createHTML().apply { diff --git a/visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt b/visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt similarity index 97% rename from visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt rename to visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt index d67ec24d..680f0a37 100644 --- a/visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/hep/dataforge/vision/three/server/VisionServer.kt @@ -1,4 +1,4 @@ -package hep.dataforge.vision.server +package hep.dataforge.vision.three.server import hep.dataforge.context.Context import hep.dataforge.meta.* @@ -12,7 +12,7 @@ import hep.dataforge.vision.html.HtmlFragment import hep.dataforge.vision.html.HtmlVisionFragment import hep.dataforge.vision.html.VisionTagConsumer import hep.dataforge.vision.html.fragment -import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE +import hep.dataforge.vision.three.server.VisionServer.Companion.DEFAULT_PAGE import io.ktor.application.* import io.ktor.features.CORS import io.ktor.features.CallLogging diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt index c1926dcd..bcf309bb 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt @@ -134,7 +134,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } override fun render(element: Element, vision: Vision, meta: Meta) { - createCanvas(element, Canvas3DOptions.read(meta)).render(vision as? Solid ?: error("Only solids are rendered")) + createCanvas(element, Canvas3DOptions.read(meta)).render( + vision as? Solid ?: error("Solid expected but ${vision::class} is found") + ) } public companion object : PluginFactory { diff --git a/visionforge-threejs/visionforge-threejs-server/build.gradle.kts b/visionforge-threejs/visionforge-threejs-server/build.gradle.kts new file mode 100644 index 00000000..2531a5e2 --- /dev/null +++ b/visionforge-threejs/visionforge-threejs-server/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + id("ru.mipt.npm.mpp") + } + + +kscience{ + useSerialization{ + json() + } + } + +val ktorVersion: String by rootProject.extra + +kotlin { + js{ + browser { + webpackTask { + this.outputFileName = "js/visionforge-three.js" + } + } + binaries.executable() + } + + afterEvaluate { + val jsBrowserDistribution by tasks.getting + + tasks.getByName("jvmProcessResources") { + dependsOn(jsBrowserDistribution) + afterEvaluate { + from(jsBrowserDistribution) + } + } + } + + sourceSets { + commonMain { + dependencies { + api(project(":visionforge-solid")) + } + } + jvmMain { + dependencies { + api(project(":visionforge-server")) + } + } + jsMain { + dependencies { + api(project(":visionforge-threejs")) + } + } + } +} \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/src/commonMain/kotlin/hep/dataforge/vision/three/server/visionContext.kt b/visionforge-threejs/visionforge-threejs-server/src/commonMain/kotlin/hep/dataforge/vision/three/server/visionContext.kt new file mode 100644 index 00000000..c568d74a --- /dev/null +++ b/visionforge-threejs/visionforge-threejs-server/src/commonMain/kotlin/hep/dataforge/vision/three/server/visionContext.kt @@ -0,0 +1,8 @@ +package hep.dataforge.vision.three.server + +import hep.dataforge.context.Context +import hep.dataforge.vision.VisionManager + +public expect val visionContext: Context + +public val visionManager: VisionManager get() = visionContext.plugins.fetch(VisionManager) \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/hep/dataforge/vision/three/server/jsMain.kt b/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/hep/dataforge/vision/three/server/jsMain.kt new file mode 100644 index 00000000..f9505680 --- /dev/null +++ b/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/hep/dataforge/vision/three/server/jsMain.kt @@ -0,0 +1,48 @@ +package hep.dataforge.vision.three.server + +import hep.dataforge.context.Context +import hep.dataforge.context.Global +import hep.dataforge.vision.client.VisionClient +import hep.dataforge.vision.client.renderAllVisions +import hep.dataforge.vision.solid.three.ThreePlugin +import kotlinx.browser.window + +//FIXME check plugin loading in JS +//public actual val visionContext: Context = Global.context("vision-client") { +// //Loading three-js renderer +// plugin(ThreePlugin) +//} + +public actual val visionContext: Context = Global.context("vision-client").apply { + //Loading three-js renderer + plugins.fetch(ThreePlugin) +} + +public val clientManager: VisionClient get() = visionContext.plugins.fetch(VisionClient) + + +///** +// * Render all visions in the document using registered renderers +// */ +//@JsExport +//public fun renderVisions() { +// //Fetch from server and render visions for all outputs +// window.onload = { +// clientManager.renderAllVisions() +// } +//} +// +///** +// * Render all visions in a given element, using registered renderers +// */ +//@JsExport +//public fun renderAllVisionsAt(element: Element) { +// clientManager.renderAllVisionsAt(element) +//} + +public fun main() { + //Fetch from server and render visions for all outputs + window.onload = { + clientManager.renderAllVisions() + } +} \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt b/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt new file mode 100644 index 00000000..fa7a0488 --- /dev/null +++ b/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/hep/dataforge/vision/three/server/serverExtensions.kt @@ -0,0 +1,43 @@ +package hep.dataforge.vision.three.server + +import hep.dataforge.context.Context +import hep.dataforge.context.Global +import hep.dataforge.meta.DFExperimental +import hep.dataforge.vision.ResourceLocation +import hep.dataforge.vision.html.HtmlVisionFragment +import hep.dataforge.vision.html.VisionOutput +import hep.dataforge.vision.makeFile +import hep.dataforge.vision.scriptHeader +import hep.dataforge.vision.solid.SolidGroup +import hep.dataforge.vision.solid.SolidManager +import java.nio.file.Path + +public actual val visionContext: Context = Global.context("vision-server") { + //Loading solid manager for the backend (it does not know about three + plugin(SolidManager) +} + +public fun VisionServer.useThreeJs(): Unit { + useScript("js/visionforge-three.js") +// header { +// script { +// unsafe { +// +"renderThreeVisions()" +// } +// } +// } +} + +@DFExperimental +public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block) + +@OptIn(DFExperimental::class) +public fun HtmlVisionFragment.makeFile( + path: Path? = null, + title: String = "VisionForge page", + resourceLocation: ResourceLocation = ResourceLocation.SYSTEM, + show: Boolean = true, +) { + val scriptHeader = Context.scriptHeader("/js/visionforge-three.js", path, resourceLocation) + makeFile(visionManager, path = path, show = show, title = title, headers = arrayOf(scriptHeader)) +} \ No newline at end of file