[orx-compute-graph] Add compute graph code
This commit is contained in:
@@ -20,6 +20,8 @@ def multiplatformModules = [
|
|||||||
"orx-camera",
|
"orx-camera",
|
||||||
"orx-color",
|
"orx-color",
|
||||||
"orx-compositor",
|
"orx-compositor",
|
||||||
|
"orx-compute-graph",
|
||||||
|
"orx-compute-graph-nodes",
|
||||||
"orx-easing",
|
"orx-easing",
|
||||||
"orx-fx",
|
"orx-fx",
|
||||||
"orx-gradient-descent",
|
"orx-gradient-descent",
|
||||||
@@ -41,6 +43,7 @@ project.ext {
|
|||||||
kotlinApiVersion = '1.6'
|
kotlinApiVersion = '1.6'
|
||||||
kotlinLanguageVersion = '1.6'
|
kotlinLanguageVersion = '1.6'
|
||||||
kotlinVersion = '1.6.10'
|
kotlinVersion = '1.6.10'
|
||||||
|
kotlinxCoroutinesVersion = '1.6.0'
|
||||||
kotlinLoggingVersion = '2.1.15'
|
kotlinLoggingVersion = '2.1.15'
|
||||||
kotlinxSerializationVersion = '1.3.1'
|
kotlinxSerializationVersion = '1.3.1'
|
||||||
spekVersion = '2.0.15'
|
spekVersion = '2.0.15'
|
||||||
@@ -323,10 +326,8 @@ configure(allprojects.findAll { !doNotPublish.contains(it.name) && !multiplatfor
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Note: kotlin-logging and kotlinx-coroutines-core are loaded
|
|
||||||
// too early and their versions cannot be parametrized
|
|
||||||
implementation 'io.github.microutils:kotlin-logging:2.1.10'
|
implementation 'io.github.microutils:kotlin-logging:2.1.10'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC3'
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
implementation "org.openrndr:openrndr-application:$openrndrVersion"
|
implementation "org.openrndr:openrndr-application:$openrndrVersion"
|
||||||
|
|||||||
22
orx-compute-graph-nodes/README.md
Normal file
22
orx-compute-graph-nodes/README.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# orx-compute-graph-nodes
|
||||||
|
|
||||||
|
A collection of nodes that can be used with orx-computer-graph.
|
||||||
|
|
||||||
|
## List of nodes
|
||||||
|
|
||||||
|
### Multi-platform
|
||||||
|
|
||||||
|
Name | Description | Inputs | Outputs
|
||||||
|
----------------|-----------------------|--------|---------
|
||||||
|
`filterNode` | Wrap around a `Filter`| | `image`
|
||||||
|
`fitImageNode` | Fit image to window bounds | `image` | `image`
|
||||||
|
|
||||||
|
### JVM only
|
||||||
|
|
||||||
|
Name | Description | Inputs | Outputs
|
||||||
|
----------------|-------------------|--------|---------
|
||||||
|
`drawCacheNode` | Cache drawing in an internal color buffer, commonly used as the final stage node | | `image`
|
||||||
|
`dropImageNode` | Listen for window file drop events | | `image`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
97
orx-compute-graph-nodes/build.gradle.kts
Normal file
97
orx-compute-graph-nodes/build.gradle.kts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import EmbedShadersTask
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
kotlin("plugin.serialization")
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinxSerializationVersion: String by rootProject.extra
|
||||||
|
val kotestVersion: String by rootProject.extra
|
||||||
|
val junitJupiterVersion: String by rootProject.extra
|
||||||
|
val jvmTarget: String by rootProject.extra
|
||||||
|
val kotlinApiVersion: String by rootProject.extra
|
||||||
|
val kotlinVersion: String by rootProject.extra
|
||||||
|
val kotlinLoggingVersion: String by rootProject.extra
|
||||||
|
val kluentVersion: String by rootProject.extra
|
||||||
|
val openrndrVersion: String by rootProject.extra
|
||||||
|
val openrndrOS: String by rootProject.extra
|
||||||
|
val spekVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations {
|
||||||
|
val demo by creating {
|
||||||
|
defaultSourceSet {
|
||||||
|
kotlin.srcDir("src/demo")
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-camera"))
|
||||||
|
implementation("org.openrndr:openrndr-application:$openrndrVersion")
|
||||||
|
implementation("org.openrndr:openrndr-extensions:$openrndrVersion")
|
||||||
|
runtimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion")
|
||||||
|
runtimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion")
|
||||||
|
implementation(compilations["main"]!!.output.allOutputs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions.jvmTarget = jvmTarget
|
||||||
|
kotlinOptions.apiVersion = kotlinApiVersion
|
||||||
|
}
|
||||||
|
testRuns["test"].executionTask.configure {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js(IR) {
|
||||||
|
browser()
|
||||||
|
nodejs()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-parameters"))
|
||||||
|
implementation(project(":orx-shader-phrases"))
|
||||||
|
implementation(project(":orx-compute-graph"))
|
||||||
|
implementation(project(":orx-image-fit"))
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinxSerializationVersion")
|
||||||
|
implementation("org.openrndr:openrndr-application:$openrndrVersion")
|
||||||
|
implementation("org.openrndr:openrndr-draw:$openrndrVersion")
|
||||||
|
implementation("org.openrndr:openrndr-filter:$openrndrVersion")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
|
||||||
|
implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val commonTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-common"))
|
||||||
|
implementation(kotlin("test-annotations-common"))
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
|
||||||
|
implementation("io.kotest:kotest-assertions-core:$kotestVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val jvmTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-common"))
|
||||||
|
implementation(kotlin("test-annotations-common"))
|
||||||
|
implementation(kotlin("test-junit5"))
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
|
||||||
|
runtimeOnly("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion")
|
||||||
|
runtimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion")
|
||||||
|
implementation("org.spekframework.spek2:spek-dsl-jvm:$spekVersion")
|
||||||
|
implementation("org.amshove.kluent:kluent:$kluentVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val jsTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-js"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
orx-compute-graph-nodes/src/commonMain/kotlin/FilterNode.kt
Normal file
27
orx-compute-graph-nodes/src/commonMain/kotlin/FilterNode.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package org.openrndr.extra.computegraph.nodes
|
||||||
|
|
||||||
|
import org.openrndr.draw.ColorBuffer
|
||||||
|
import org.openrndr.draw.Filter
|
||||||
|
import org.openrndr.draw.createEquivalent
|
||||||
|
import org.openrndr.extra.computegraph.ComputeGraph
|
||||||
|
import org.openrndr.extra.computegraph.ComputeNode
|
||||||
|
import org.openrndr.extra.computegraph.withKey
|
||||||
|
|
||||||
|
fun <T : Filter> ComputeGraph.filterNode(
|
||||||
|
filter: T, input: ComputeNode, inputKey: String = "image", outputKey: String = "image",
|
||||||
|
config: ComputeNode.(f: Filter) -> Unit
|
||||||
|
): ComputeNode {
|
||||||
|
return node {
|
||||||
|
name = "filter-${filter::class.simpleName}"
|
||||||
|
inputs = filter.parameters
|
||||||
|
config(filter)
|
||||||
|
val inputImage by input.outputs.withKey<ColorBuffer>(inputKey)
|
||||||
|
var outputImage by outputs.withKey<ColorBuffer>(outputKey)
|
||||||
|
outputImage = inputImage.createEquivalent()
|
||||||
|
compute {
|
||||||
|
filter.apply(inputImage, outputImage)
|
||||||
|
}
|
||||||
|
dependOn(input)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.openrndr.extra.computegraph.nodes
|
||||||
|
|
||||||
|
import org.openrndr.Program
|
||||||
|
import org.openrndr.draw.ColorBuffer
|
||||||
|
import org.openrndr.draw.isolatedWithTarget
|
||||||
|
import org.openrndr.draw.renderTarget
|
||||||
|
import org.openrndr.extra.computegraph.ComputeGraph
|
||||||
|
import org.openrndr.extra.computegraph.ComputeNode
|
||||||
|
import org.openrndr.extra.computegraph.withKey
|
||||||
|
import org.openrndr.extras.imageFit.imageFit
|
||||||
|
|
||||||
|
fun ComputeGraph.fitImageNode(program: Program, input: ComputeNode) : ComputeNode {
|
||||||
|
return node {
|
||||||
|
name = "fit-image"
|
||||||
|
val rt = renderTarget(program.width, program.height) {
|
||||||
|
colorBuffer()
|
||||||
|
}
|
||||||
|
val inputImage: ColorBuffer by input.outputs.withKey("image")
|
||||||
|
var outputImage:ColorBuffer by outputs.withKey("image")
|
||||||
|
outputImage = rt.colorBuffer(0)
|
||||||
|
compute {
|
||||||
|
program.drawer.isolatedWithTarget(rt) {
|
||||||
|
ortho(rt)
|
||||||
|
imageFit(inputImage, bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependOn(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
99
orx-compute-graph-nodes/src/jvmMain/kotlin/DrawCacheNode.kt
Normal file
99
orx-compute-graph-nodes/src/jvmMain/kotlin/DrawCacheNode.kt
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package org.openrndr.extra.computegraph.nodes
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.openrndr.KEY_SPACEBAR
|
||||||
|
import org.openrndr.Program
|
||||||
|
import org.openrndr.RequestAssetsEvent
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.draw.*
|
||||||
|
import org.openrndr.extra.computegraph.ComputeGraph
|
||||||
|
import org.openrndr.extra.computegraph.ComputeNode
|
||||||
|
import org.openrndr.extra.computegraph.withKey
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
val logger = KotlinLogging.logger { }
|
||||||
|
|
||||||
|
private data class RenderTargetDescription(val width: Int, val height: Int, val contentScale: Double)
|
||||||
|
|
||||||
|
private fun RenderTarget.description() = RenderTargetDescription(width, height, contentScale)
|
||||||
|
|
||||||
|
|
||||||
|
fun ComputeGraph.drawCacheNode(
|
||||||
|
program: Program,
|
||||||
|
inputNodes: List<ComputeNode>,
|
||||||
|
draw: Program.(node: ComputeNode) -> Unit
|
||||||
|
): ComputeNode {
|
||||||
|
return node {
|
||||||
|
var producingAssets: Boolean by inputs
|
||||||
|
producingAssets = false
|
||||||
|
|
||||||
|
program.keyboard.keyDown.listen {
|
||||||
|
if (!it.propagationCancelled) {
|
||||||
|
logger.info { "requesting assets" }
|
||||||
|
if (it.key == KEY_SPACEBAR) {
|
||||||
|
program.requestAssets.trigger(RequestAssetsEvent(this, program))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var screenshotTarget = ""
|
||||||
|
program.produceAssets.listen {
|
||||||
|
producingAssets = true
|
||||||
|
screenshotTarget = "screenshots/${it.assetMetadata.assetBaseName}.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "draw-cache"
|
||||||
|
var rt = renderTarget(program.width, program.height) {
|
||||||
|
colorBuffer()
|
||||||
|
depthBuffer()
|
||||||
|
}
|
||||||
|
var outputImage: ColorBuffer by outputs.withKey("image")
|
||||||
|
outputImage = rt.colorBuffer(0)
|
||||||
|
|
||||||
|
var description: RenderTargetDescription by inputs
|
||||||
|
description = RenderTarget.active.description()
|
||||||
|
|
||||||
|
update {
|
||||||
|
description = RenderTarget.active.description()
|
||||||
|
}
|
||||||
|
val defaultContentScale = program.window.contentScale
|
||||||
|
|
||||||
|
compute {
|
||||||
|
rt.colorBuffer(0).destroy()
|
||||||
|
rt.depthBuffer?.destroy()
|
||||||
|
rt.detachColorAttachments()
|
||||||
|
rt.detachDepthBuffer()
|
||||||
|
rt.destroy()
|
||||||
|
rt = renderTarget(
|
||||||
|
program.width,
|
||||||
|
program.height,
|
||||||
|
contentScale = if (producingAssets) 6.0 else defaultContentScale
|
||||||
|
) {
|
||||||
|
colorBuffer()
|
||||||
|
depthBuffer()
|
||||||
|
}
|
||||||
|
program.drawer.isolatedWithTarget(rt) {
|
||||||
|
clear(ColorRGBa.WHITE)
|
||||||
|
|
||||||
|
draw(program, this@node)
|
||||||
|
}
|
||||||
|
outputImage = rt.colorBuffer(0)
|
||||||
|
println(outputImage)
|
||||||
|
|
||||||
|
if (producingAssets) {
|
||||||
|
logger.info { "saving draw cache to file" }
|
||||||
|
val directory = File("screenshots")
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
outputImage.saveToFile(File(screenshotTarget), async = false)
|
||||||
|
producingAssets = false
|
||||||
|
screenshotTarget = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (input in inputNodes) {
|
||||||
|
dependOn(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
orx-compute-graph-nodes/src/jvmMain/kotlin/DropImageNode.kt
Normal file
28
orx-compute-graph-nodes/src/jvmMain/kotlin/DropImageNode.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package org.openrndr.extra.computegraph.nodes
|
||||||
|
|
||||||
|
import org.openrndr.Program
|
||||||
|
import org.openrndr.draw.ColorBuffer
|
||||||
|
import org.openrndr.draw.colorBuffer
|
||||||
|
import org.openrndr.draw.loadImage
|
||||||
|
import org.openrndr.extra.computegraph.ComputeGraph
|
||||||
|
import org.openrndr.extra.computegraph.ComputeNode
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun ComputeGraph.dropImageNode(program: Program): ComputeNode {
|
||||||
|
return node {
|
||||||
|
name = "drop-image"
|
||||||
|
var file: File by inputs
|
||||||
|
file = File("data/images/cheeta.jpg")
|
||||||
|
program.window.drop.listen {
|
||||||
|
file = File(it.files.first())
|
||||||
|
}
|
||||||
|
var image: ColorBuffer by outputs
|
||||||
|
|
||||||
|
fun loadFileOrEmpty() = if (file.exists()) loadImage(file) else colorBuffer(256, 256)
|
||||||
|
image = loadFileOrEmpty()
|
||||||
|
compute {
|
||||||
|
image.destroy()
|
||||||
|
image = loadFileOrEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
orx-compute-graph/README.md
Normal file
55
orx-compute-graph/README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# orx-compute-graph
|
||||||
|
|
||||||
|
A graph for computation.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
In development. Things may change without prior notice.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
fun main() = application {
|
||||||
|
program {
|
||||||
|
val linkNode: ComputeNode
|
||||||
|
val cg = computeGraph {
|
||||||
|
val randomNode = node {
|
||||||
|
var seed: Int by inputs
|
||||||
|
seed = 0
|
||||||
|
var points: List<Vector2> by outputs
|
||||||
|
points = emptyList()
|
||||||
|
compute {
|
||||||
|
val r = Random(seed)
|
||||||
|
points = (0 until 1000).map {
|
||||||
|
val x = r.nextDouble(0.0, width.toDouble())
|
||||||
|
val y = r.nextDouble(0.0, height.toDouble())
|
||||||
|
Vector2(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
linkNode = node {
|
||||||
|
var seed: Int by inputs
|
||||||
|
seed = 0
|
||||||
|
val points: List<Vector2> by randomNode.outputs
|
||||||
|
var links: List<LineSegment> by outputs
|
||||||
|
compute {
|
||||||
|
val r = Random(seed)
|
||||||
|
val shuffled = points.shuffled(r)
|
||||||
|
links = shuffled.windowed(2, 2).map {
|
||||||
|
LineSegment(it[0], it[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
randomNode.dependOn(root)
|
||||||
|
linkNode.dependOn(randomNode)
|
||||||
|
}
|
||||||
|
cg.dispatch(dispatcher) {}
|
||||||
|
extend {
|
||||||
|
drawer.clear(ColorRGBa.WHITE)
|
||||||
|
drawer.stroke = ColorRGBa.BLACK
|
||||||
|
val links: List<LineSegment> by linkNode.outputs
|
||||||
|
drawer.lineSegments(links)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
91
orx-compute-graph/build.gradle.kts
Normal file
91
orx-compute-graph/build.gradle.kts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import EmbedShadersTask
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
kotlin("plugin.serialization")
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinxSerializationVersion: String by rootProject.extra
|
||||||
|
val kotlinxCoroutinesVersion: String by rootProject.extra
|
||||||
|
val kotestVersion: String by rootProject.extra
|
||||||
|
val junitJupiterVersion: String by rootProject.extra
|
||||||
|
val jvmTarget: String by rootProject.extra
|
||||||
|
val kotlinApiVersion: String by rootProject.extra
|
||||||
|
val kotlinVersion: String by rootProject.extra
|
||||||
|
val kotlinLoggingVersion: String by rootProject.extra
|
||||||
|
val kluentVersion: String by rootProject.extra
|
||||||
|
val openrndrVersion: String by rootProject.extra
|
||||||
|
val openrndrOS: String by rootProject.extra
|
||||||
|
val spekVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations {
|
||||||
|
val demo by creating {
|
||||||
|
defaultSourceSet {
|
||||||
|
kotlin.srcDir("src/demo")
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-camera"))
|
||||||
|
implementation("org.openrndr:openrndr-application:$openrndrVersion")
|
||||||
|
implementation("org.openrndr:openrndr-extensions:$openrndrVersion")
|
||||||
|
runtimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion")
|
||||||
|
runtimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion")
|
||||||
|
implementation(compilations["main"]!!.output.allOutputs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions.jvmTarget = jvmTarget
|
||||||
|
kotlinOptions.apiVersion = kotlinApiVersion
|
||||||
|
}
|
||||||
|
testRuns["test"].executionTask.configure {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js(IR) {
|
||||||
|
browser()
|
||||||
|
nodejs()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
api("org.openrndr:openrndr-event:$openrndrVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
|
||||||
|
implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val commonTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-common"))
|
||||||
|
implementation(kotlin("test-annotations-common"))
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
|
||||||
|
implementation("io.kotest:kotest-assertions-core:$kotestVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val jvmTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-common"))
|
||||||
|
implementation(kotlin("test-annotations-common"))
|
||||||
|
implementation(kotlin("test-junit5"))
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
|
||||||
|
runtimeOnly("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion")
|
||||||
|
runtimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion")
|
||||||
|
implementation("org.spekframework.spek2:spek-dsl-jvm:$spekVersion")
|
||||||
|
implementation("org.amshove.kluent:kluent:$kluentVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val jsTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-js"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
175
orx-compute-graph/src/commonMain/kotlin/ComputeGraph.kt
Normal file
175
orx-compute-graph/src/commonMain/kotlin/ComputeGraph.kt
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package org.openrndr.extra.computegraph
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.openrndr.events.Event
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger { }
|
||||||
|
|
||||||
|
data class ComputeEvent(val source: ComputeNode)
|
||||||
|
|
||||||
|
open class ComputeNode(val graph: ComputeGraph, var computeFunction: suspend () -> Unit = {}) {
|
||||||
|
internal var updateFunction = {}
|
||||||
|
|
||||||
|
var inputs = mutableMapOf<String, Any>()
|
||||||
|
val outputs = mutableMapOf<String, Any>()
|
||||||
|
var name = "unnamed-node-${this.hashCode()}"
|
||||||
|
private var lastInputsHash = inputs.hashCode()
|
||||||
|
var receivedComputeRequest = true
|
||||||
|
private val computeFinished = Event<Unit>("compute-finished")
|
||||||
|
|
||||||
|
fun needsRecompute(): Boolean {
|
||||||
|
return receivedComputeRequest || (inputs.hashCode() != lastInputsHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dependOn(node: ComputeNode) {
|
||||||
|
graph.nodes.add(this)
|
||||||
|
graph.inbound.getOrPut(this) { mutableSetOf() }.add(node)
|
||||||
|
graph.outbound.getOrPut(node) { mutableSetOf() }.add(node)
|
||||||
|
|
||||||
|
node.computeFinished.listen {
|
||||||
|
receivedComputeRequest = true
|
||||||
|
graph.requireCompute.add(this)
|
||||||
|
computeFinished.trigger(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an update function, this function is called unconditionally by the compute-graph processor. This update
|
||||||
|
* function can be used to change values of [inputs] to trigger compute of the node.
|
||||||
|
*/
|
||||||
|
fun update(updateFunction: () -> Unit) {
|
||||||
|
this.updateFunction = updateFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compute(computeFunction: suspend () -> Unit) {
|
||||||
|
this.computeFunction = computeFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun execute() {
|
||||||
|
receivedComputeRequest = false
|
||||||
|
lastInputsHash = inputs.hashCode()
|
||||||
|
computeFunction()
|
||||||
|
computeFinished.trigger(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ComputeNode(name='$name', receivedComputeRequest=$receivedComputeRequest)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComputeGraph {
|
||||||
|
val root = ComputeNode(this, {})
|
||||||
|
internal val requireCompute = ArrayDeque<ComputeNode>()
|
||||||
|
|
||||||
|
val nodes = mutableListOf<ComputeNode>()
|
||||||
|
val inbound = mutableMapOf<ComputeNode, MutableSet<ComputeNode>>()
|
||||||
|
val outbound = mutableMapOf<ComputeNode, MutableSet<ComputeNode>>()
|
||||||
|
|
||||||
|
var job: Job? = null
|
||||||
|
fun node(builder: ComputeNode.() -> Unit): ComputeNode {
|
||||||
|
val cn = ComputeNode(this)
|
||||||
|
cn.builder()
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
private var computeHash = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the compute graph in [context].
|
||||||
|
*
|
||||||
|
* Eventually we likely want to separate compute-graph definitions from the compute-graph processor.
|
||||||
|
*/
|
||||||
|
fun dispatch(context: CoroutineDispatcher, delayBeforeCompute: Long = 500) {
|
||||||
|
var firstRodeo = true
|
||||||
|
GlobalScope.launch(context, CoroutineStart.DEFAULT) {
|
||||||
|
while (true) {
|
||||||
|
for (node in nodes) {
|
||||||
|
node.updateFunction()
|
||||||
|
}
|
||||||
|
val testHash = nodes.map { it.inputs.hashCode() }.reduce { acc, computeNode -> acc * 31 + computeNode }
|
||||||
|
if (testHash != computeHash) {
|
||||||
|
logger.info { "canceling job $job" }
|
||||||
|
job?.cancel()
|
||||||
|
job = null
|
||||||
|
}
|
||||||
|
if (testHash != computeHash && job == null) {
|
||||||
|
computeHash = testHash
|
||||||
|
job = GlobalScope.launch(context) {
|
||||||
|
if (!firstRodeo) {
|
||||||
|
delay(delayBeforeCompute)
|
||||||
|
}
|
||||||
|
logger.info { "compute started" }
|
||||||
|
compute()
|
||||||
|
logger.info { "compute finished" }
|
||||||
|
firstRodeo = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun compute() {
|
||||||
|
for (node in nodes) {
|
||||||
|
if (node.needsRecompute()) {
|
||||||
|
if (node !in requireCompute) {
|
||||||
|
logger.info { "node '${node.name}' needs computation" }
|
||||||
|
requireCompute.add(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val processed = mutableListOf<ComputeNode>()
|
||||||
|
root.receivedComputeRequest = false
|
||||||
|
while (requireCompute.isNotEmpty()) {
|
||||||
|
val node = requireCompute.first {
|
||||||
|
val deps = (inbound[it] ?: emptyList())
|
||||||
|
if (deps.isEmpty()) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
deps.none { dep -> dep in requireCompute }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requireCompute.remove(node)
|
||||||
|
if (node !in processed) {
|
||||||
|
logger.info { "computing ${node.name}" }
|
||||||
|
node.execute()
|
||||||
|
processed.add(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
fun computeGraph(builder: ComputeGraph.() -> Unit): ComputeGraph {
|
||||||
|
contract {
|
||||||
|
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
val cg = ComputeGraph()
|
||||||
|
cg.builder()
|
||||||
|
return cg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MutableMapKeyReference<T : Any>(private val map: MutableMap<String, Any>, private val key: String) {
|
||||||
|
operator fun getValue(any: Any?, property: KProperty<*>): T {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return map[key] as T
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun setValue(any: Any?, property: KProperty<*>, value: Any) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
map[key] = value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a map delegation by [key]
|
||||||
|
*/
|
||||||
|
fun <T : Any> MutableMap<String, Any>.withKey(key: String): MutableMapKeyReference<T> {
|
||||||
|
return MutableMapKeyReference(this, key)
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ include 'openrndr-demos',
|
|||||||
'orx-jvm:orx-chataigne',
|
'orx-jvm:orx-chataigne',
|
||||||
'orx-color',
|
'orx-color',
|
||||||
'orx-compositor',
|
'orx-compositor',
|
||||||
|
'orx-compute-graph',
|
||||||
|
'orx-compute-graph-nodes',
|
||||||
'orx-jvm:orx-dnk3',
|
'orx-jvm:orx-dnk3',
|
||||||
'orx-easing',
|
'orx-easing',
|
||||||
'orx-jvm:orx-file-watcher',
|
'orx-jvm:orx-file-watcher',
|
||||||
|
|||||||
Reference in New Issue
Block a user