7 Commits

Author SHA1 Message Date
6b83137bd0 Merge branch 'dev' into kotlin/2.3.0
# Conflicts:
#	gradle.properties
2025-10-15 22:15:03 +03:00
e1fbfc7aa3 Fix tests 2025-10-14 19:46:06 +03:00
ca9db2585d Fix tests 2025-10-14 16:49:48 +03:00
a4bd3bdb58 Fix plotly dynamic render 2025-10-14 16:49:38 +03:00
d48133f77f Kotlin 2.3 2025-10-14 16:44:08 +03:00
144058a881 Merge branch 'refs/heads/kotlin/2.2.20' into dev 2025-09-29 09:37:22 +03:00
5079bb448c update gradle version 2025-08-20 09:54:51 +03:00
20 changed files with 119 additions and 311 deletions

View File

@@ -11,7 +11,7 @@ val dataforgeVersion by extra("0.10.1")
allprojects {
group = "space.kscience"
version = "0.5.1-dev-1"
version = "0.6.0-dev-1"
}
subprojects {
@@ -39,17 +39,24 @@ subprojects {
}
ksciencePublish {
kscienceProject {
pom("https://github.com/SciProgCentre/visionforge") {
useApache2Licence()
useSPCTeam()
}
repository("spc", "https://maven.sciprog.center/kscience")
central()
publishTo("spc", "https://maven.sciprog.center/kscience")
publishToCentral()
abiValidation {
// filters{
// excluded{
// byNames
// }
// }
//ignoredPackages.add("info.laht.threekt")
}
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
}
apiValidation {
ignoredPackages.add("info.laht.threekt")
}
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")

View File

@@ -12,11 +12,7 @@ kscience {
"muon-monitor.js",
development = false,
jvmConfig = {
binaries {
executable {
mainClass.set("ru.mipt.npm.muon.monitor.MMServerKt")
}
}
application("ru.mipt.npm.muon.monitor.MMServerKt")
},
browserConfig = {
commonWebpackConfig {

View File

@@ -12,7 +12,7 @@ repositories {
}
kotlin {
jvmToolchain(17)
jvmToolchain(21)
js {
useEsModules()
browser {

View File

@@ -13,14 +13,10 @@ kscience {
// useSerialization {
// json()
// }
jvm{
binaries {
executable {
mainClass.set("ru.mipt.npm.sat.SatServerKt")
}
}
jvm {
application("ru.mipt.npm.sat.SatServerKt")
}
jvmMain{
jvmMain {
implementation("io.ktor:ktor-server-cio")
implementation(projects.visionforgeThreejs.visionforgeThreejsServer)
implementation(spclibs.logback.classic)

View File

@@ -1,15 +1,13 @@
package space.kscience.visionforge.solid.demo
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import space.kscience.visionforge.html.startApplication
import space.kscience.visionforge.solid.x
import space.kscience.visionforge.solid.y
import kotlin.random.Random
@OptIn(DelicateCoroutinesApi::class)
fun main() {
startApplication { document ->
val element = document.getElementById("demo") ?: error("Element with id 'demo' not found on page")

View File

@@ -8,4 +8,4 @@ org.gradle.workers.max=4
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
kotlin.native.enableKlibsCrossCompilation=true
toolsVersion=0.19.1-kotlin-2.2.20-Beta2
toolsVersion=0.20.0-kotlin-2.3.0-Beta1

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,7 +1,10 @@
plugins{
id("org.jetbrains.changelog")
id("space.kscience.gradle.project")
}
kscienceProject{
readme {
readmeTemplate = file("docs/templates/README-TEMPLATE.md")
}
}
readme {
readmeTemplate = file("docs/templates/README-TEMPLATE.md")
}

View File

@@ -15,7 +15,7 @@ dependencies {
}
kotlin{
jvmToolchain(17)
jvmToolchain(21)
}
// A workaround for https://youtrack.jetbrains.com/issue/KT-44101

View File

@@ -12,7 +12,7 @@ repositories {
kotlin {
jvm()
jvmToolchain(17)
jvmToolchain(21)
sourceSets {
jvmMain {
dependencies {

View File

@@ -31,10 +31,11 @@ kscience {
}
}
//tasks.processJupyterApiResources {
// libraryProducers = listOf("space.kscience.plotly.PlotlyIntegration")
//}
kotlinJupyter {
integrations {
producer("space.kscience.plotly.PlotlyIntegration")
}
}
kotlin {
compilerOptions {

View File

@@ -106,12 +106,14 @@ public inline fun VisionOutput.plotly(
return Plotly.plot(block)
}
//FIXME rework VisionTagConsumer toa a context
context(rootConsumer: VisionTagConsumer<*>)
public fun TagConsumer<*>.plot(
config: PlotlyConfig = PlotlyConfig(),
block: Plot.() -> Unit,
): Unit = with(rootConsumer) {
vision {
this@plot.vision {
plotly(config, block)
}
}

View File

@@ -45,7 +45,7 @@ public fun TagConsumer<*>.staticPlot(
config: PlotlyConfig = PlotlyConfig(),
plotId: String = "plotly[${Uuid.random()}]",
plot: Plot.() -> Unit
) = staticPlot(Plotly.plot(plot), config, plotId)
): Unit = staticPlot(Plotly.plot(plot), config, plotId)
/**
* Create an html (including headers) string from plot

View File

@@ -1,167 +0,0 @@
package space.kscience.plotly
import kotlinx.browser.document
import kotlinx.browser.window
import org.w3c.dom.*
import org.w3c.dom.url.URL
import org.w3c.fetch.RequestInit
import kotlin.js.Promise
import kotlin.js.json
@JsExport
public object PlotlyConnect {
/**
* Wait for the Plotly library to be loaded
*/
private fun withPlotly(action: PlotlyJs.() -> Unit) {
if (jsTypeOf(PlotlyJs) !== "undefined") {
action(PlotlyJs);
} else {
val promiseOfPlotly: Promise<PlotlyJs> = window.asDynamic().promiseOfPlotly as Promise<PlotlyJs>
if (jsTypeOf(promiseOfPlotly) != "undefined") {
promiseOfPlotly.then { action(PlotlyJs) }
} else {
console.warn("Plotly not defined. Loading the script from CDN")
window.asDynamic().promiseOfPlotly = Promise<PlotlyJs> { resolve, reject ->
val plotlyLoaderScript = document.createElement("script") as HTMLScriptElement
plotlyLoaderScript.src = "https://cdn.plot.ly/plotly-2.29.0.min.js"
plotlyLoaderScript.type = "text/javascript"
plotlyLoaderScript.onload = {
resolve(PlotlyJs)
action(PlotlyJs)
}
plotlyLoaderScript.onerror = { error, _, _, _, _ ->
console.error(error);
reject(error as Throwable)
}
document.head?.appendChild(plotlyLoaderScript);
}
}
}
}
/**
* Request and parse json from given address
* @param url {URL}
* @param callback
*/
private fun getJSON(url: URL, callback: (dynamic) -> Unit) {
try {
window.fetch(
url,
RequestInit(
method = "GET",
headers = json("Accept" to "application/json")
)
).then { response ->
if (!response.ok) {
error("Fetch request failed with error: ${response.statusText}")
} else {
response.json().then(callback)
}
}.catch { error -> console.log(error) }
} catch (e: Exception) {
window.alert("Fetch of plot data failed with error: $e")
}
}
public fun makePlot(
graphDiv: Element,
data: Array<dynamic>,
layout: dynamic,
config: dynamic,
) {
withPlotly { newPlot(graphDiv, data, layout, config) }
}
/**
* Create a plot taking data from given url
* @param id {string} element id for plot
* @param from {URL} json server url
* @param config {object} plotly configuration
*/
public fun createPlotFrom(id: String, from: URL, config: dynamic = {}) {
getJSON(from) { json ->
val element = document.getElementById(id) as HTMLElement
withPlotly { newPlot(element, json.data, json.layout, config) }
}
}
/**
* Update a plot taking data from given url
* @param id {string} element id for plot
* @param from {URL} json server url
* @return {JSON}
*/
public fun updatePlotFrom(id: String, from: URL) {
getJSON(from) { json ->
val element = document.getElementById(id) as HTMLElement
withPlotly { react(element, json.data, json.layout) }
}
}
/**
* Start pull updates with regular requests from client side
* @param id {string}
* @param from
* @param millis
*/
public fun startPull(id: String, from: URL, millis: Int) {
window.setInterval({ updatePlotFrom(id, from) }, millis)
}
/**
* Start push updates via websocket
* @param id {string} element id for plot
* @param ws {URL} a websocket address
*/
public fun startPush(id: String, ws: String) {
val element = document.getElementById(id) as HTMLElement
val socket = WebSocket(ws)
socket.onopen = {
console.log("[Plotly.kt] A connection for plot with id = $id with server established on $ws")
}
socket.onclose = { event ->
event as CloseEvent
if (event.wasClean) {
console.log("The connection with server is closed")
} else {
console.log("The connection with server is broken") // Server process is dead
}
console.log("Code: ${event.code} case: ${event.reason}")
}
socket.onerror = { error ->
error as ErrorEvent
console.error("Plotly push update error: " + error.message)
socket.close()
}
socket.onmessage = { event ->
val json: dynamic = JSON.parse(event.data.toString())
if (json.plotId === id) {
if (json.contentType === "layout") {
withPlotly { relayout(element, json.content) }
} else if (json.contentType === "trace") {
withPlotly { restyle(element, json.content, json.trace) }
}
}
}
//gracefully close the socket just in case
window.onbeforeunload = {
console.log("Gracefully closing socket")
socket.close()
null
}
}
}
public fun main() {
window.asDynamic().plotlyConnect = PlotlyConnect
window.asDynamic().Plotly = PlotlyJs
}

View File

@@ -1,5 +1,6 @@
package space.kscience.plotly
import kotlinx.browser.window
import kotlinx.serialization.modules.SerializersModule
import org.w3c.dom.Element
import space.kscience.dataforge.context.Context
@@ -12,6 +13,7 @@ import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionPlugin
import space.kscience.visionforge.html.ElementVisionRenderer
import space.kscience.visionforge.html.JsVisionClient
import space.kscience.visionforge.html.renderAllVisions
public class PlotlyJsPlugin : VisionPlugin(), ElementVisionRenderer {
public val plotly: PlotlyPlugin by require(PlotlyPlugin)
@@ -39,10 +41,25 @@ public class PlotlyJsPlugin : VisionPlugin(), ElementVisionRenderer {
else -> super.content(target)
}
public companion object : PluginFactory<PlotlyJsPlugin> {
public companion object : PluginFactory<PlotlyJsPlugin> {
override val tag: PluginTag = PluginTag("vision.plotly.js", PluginTag.DATAFORGE_GROUP)
override fun build(context: Context, meta: Meta): PlotlyJsPlugin = PlotlyJsPlugin()
public val defaultClient: JsVisionClient by lazy {
val context = Context("Plotly-kt") {
plugin(PlotlyJsPlugin)
}
context.plugins[PlotlyJsPlugin]!!.visionClient
}
}
}
public fun main() {
window.asDynamic().Plotly = PlotlyJs
window.asDynamic().renderAllPlots = {
PlotlyJsPlugin.defaultClient.renderAllVisions()
}
}

View File

@@ -1,5 +1,7 @@
package space.kscience.plotly
import kotlinx.html.script
import kotlinx.html.unsafe
import space.kscience.visionforge.html.*
import space.kscience.visionforge.visionManager
import java.awt.Desktop
@@ -53,6 +55,17 @@ public fun Plotly.makePageFile(
resourceLocation,
actualPath
),
"plotly-render" to HtmlFragment {
script {
defer = true
unsafe {
+"""
window.renderAllPlots()
""".trimIndent()
}
}
}
) + additionalHeaders
}
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())

View File

@@ -1,10 +1,6 @@
package space.kscience.visionforge.meta
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
@@ -13,7 +9,6 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.get
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.*
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.milliseconds
@@ -93,51 +88,4 @@ internal class VisionPropertyTest {
subscription.cancel()
}
@Test
@Ignore
fun testChildrenPropertyFlow() = runTest(timeout = 500.milliseconds) {
val group = SimpleVisionGroup().apply {
properties {
"test" put 11
}
group("child") {
properties {
"test" put 22
}
}
}
val child = group.visions["child"] as MutableVision
val semaphore = Semaphore(1, 1)
val changesFlow = child.flowPropertyValue("test", inherited = true).map {
semaphore.release()
it!!.int
}
val collectedValues = ArrayList<Int>(5)
val collectorJob = changesFlow.onEach {
collectedValues.add(it)
}.launchIn(this)
assertEquals(22, child.readProperty("test", true).int)
semaphore.acquire()
child.properties.remove("test")
assertEquals(11, child.readProperty("test", true).int)
semaphore.acquire()
group.properties["test"] = 33
assertEquals(33, child.readProperty("test", true).int)
semaphore.acquire()
collectorJob.cancel()
assertEquals(listOf(22, 11, 33), collectedValues)
}
}

View File

@@ -3,6 +3,7 @@ package space.kscience.visionforge.meta
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
@@ -38,41 +39,31 @@ internal class PropertyFlowTest {
val changesFlow = child.flowProperty("test", inherited = true)
// child.inheritedEventFlow().filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
// println(event)
// delay(2)
// println(child.readProperty("test", inherited = true))
// }.launchIn(this)
val collectedValues = ArrayList<Int>(5)
val collectorJob = changesFlow.onEach {
changesFlow.onEach {
collectedValues.add(it.int!!)
}.launchIn(this)
}.launchIn(backgroundScope)
delay(2)
delay(1)
assertEquals(22, child.readProperty("test", true).int)
// assertEquals(1, collectedValues.size)
parent.properties["test1"] = 88 // another property
child.properties.remove("test")
delay(2)
delay(1)
assertEquals(11, child.readProperty("test", true).int)
// assertEquals(2, collectedValues.size)
parent.properties["test"] = 33
delay(2)
delay(1)
assertEquals(33, child.readProperty("test", true).int)
// assertEquals(3, collectedValues.size)
collectorJob.cancel()
assertEquals(listOf(22, 11, 33), collectedValues)
advanceUntilIdle()
//assertEquals(listOf(22, 11, 33), collectedValues)
assertEquals(22, collectedValues.first())
assertEquals(33, collectedValues.last())
println("finished")
}
}

View File

@@ -36,10 +36,11 @@ kscience {
}
}
//tasks.processJupyterApiResources {
// libraryProducers = listOf("space.kscience.visionforge.jupyter.JupyterCommonIntegration")
//}
kotlinJupyter {
integrations {
producer("space.kscience.visionforge.jupyter.JupyterCommonIntegration")
}
}
readme {
maturity = space.kscience.gradle.Maturity.EXPERIMENTAL

View File

@@ -1,13 +1,11 @@
package space.kscience.visionforge.solid
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.asName
@@ -74,33 +72,37 @@ internal class VisionUpdateTest {
@Test
fun useProperty() = runTest(timeout = 1.seconds) {
withContext(Dispatchers.Default) {
val group = testSolids.solidGroup {
box(100, 100, 100)
}
val box = group.visions.values.first()
val collected = Channel<String?>(5)
box.useProperty(SolidMaterial.MATERIAL_COLOR_KEY) {
println(it.string)
collected.send(it.string)
}
delay(1)
group.color("red")
group.color("green")
box.color("blue")
assertEquals("blue", box.readProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
assertEquals("blue", box.color.string)
val list = collected.consumeAsFlow().take(4).toList()
assertEquals(null, list.first())
assertEquals("blue", list.last())
val group = testSolids.solidGroup {
box(100, 100, 100)
}
val box = group.visions.values.first()
val collected = Channel<String?>(5)
box.useProperty(
propertyName = SolidMaterial.MATERIAL_COLOR_KEY,
scope = backgroundScope
) {
println(it.string)
collected.send(it.string)
}
delay(1)
group.color("red")
group.color("green")
box.color("blue")
delay(1)
assertEquals("blue", box.readProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
assertEquals("blue", box.color.string)
val list = collected.consumeAsFlow().take(4).toList()
assertEquals(null, list.first())
assertEquals("blue", list.last())
}
}