Skip to content

Commit

Permalink
Bidirectional IPC packets, support view tree operations for external …
Browse files Browse the repository at this point in the history
…debugger, support for offscreen GameWindow and rendering and initial incremental compilation without Gradle for faster, more-stable hot reloading (#2249)
  • Loading branch information
soywiz authored Jun 24, 2024
1 parent 0ca8644 commit d3b01e7
Show file tree
Hide file tree
Showing 38 changed files with 1,907 additions and 428 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/DEPLOY.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ env:

jobs:
publish:
runs-on: macos-11
runs-on: macos-latest
steps:
- { name: Checkout, uses: actions/checkout@v3 }
- { name: Use Node.js 20.x, uses: actions/setup-node@v3, with: { node-version: 20.x } }
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/TEST.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ jobs:
fail-fast: false # Once working, comment this
matrix:
include:
- { outputKey: testIos, os: macos-11, testTask: iosX64Test, precompileTask: compileTestKotlinIosX64, enableKotlinNative: true }
- { outputKey: testIos, os: macos-latest, testTask: iosX64Test, precompileTask: compileTestKotlinIosX64, enableKotlinNative: true }
- { outputKey: testJs, os: ubuntu-latest, testTask: "wasmJsBrowserTest", buildTasks: "jsBrowserTest", precompileTask: "wasmJsTestClasses jsTestClasses" }
- { outputKey: testAndroid, os: ubuntu-latest, enableAndroid: true, precompileTask: "compileDebugAndroidTestSources" }
- { outputKey: testJvmMacos, os: macos-11, testTask: jvmTest, precompileTask: "compileTestKotlinJvm compileTestKotlin" }
- { outputKey: testJvmMacos, os: macos-latest, testTask: jvmTest, precompileTask: "compileTestKotlinJvm compileTestKotlin" }
- { outputKey: testJvmLinux, os: ubuntu-latest, testTask: jvmTest, precompileTask: "compileTestKotlinJvm compileTestKotlin", enableKotlinNative: true, enableSandbox: true, e2e: true }
- { outputKey: testJvmWindows, os: windows-latest, testTask: jvmTest, precompileTask: "compileTestKotlinJvm compileTestKotlin" }
#if: ${{ needs.changes.outputs[matrix.outputKey] == 'true' }}
Expand Down
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import korlibs.root.*
import org.jetbrains.kotlin.backend.common.serialization.*
import org.jetbrains.kotlin.gradle.tasks.*

plugins {
//id "com.dorongold.task-tree" version "2.1.1"
Expand Down Expand Up @@ -91,3 +93,10 @@ tasks {
)
}
}

afterEvaluate {
println("-----------")
println(tasks.findByPath(":korge:jvmMainClasses")!!::class)
println(tasks.findByPath(":korge:compileKotlinJvm")!!::class)
//org.jetbrains.kotlin.gradle.tasks.KotlinCompile
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ const val ANDROID_DEFAULT_MIN_SDK = 21 // Android 5.0
const val ANDROID_DEFAULT_COMPILE_SDK = 33
const val ANDROID_DEFAULT_TARGET_SDK = 33

val GRADLE_JAVA_VERSION_STR = "11"
//val GRADLE_JAVA_VERSION_STR = "11"
val GRADLE_JAVA_VERSION_STR = "17"

val ANDROID_JAVA_VERSION = JavaVersion.VERSION_1_8
//val ANDROID_JAVA_VERSION = JavaVersion.VERSION_11
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ open class KorgeJavaExecWithAutoreload : KorgeJavaExec() {
)

environment("KORGE_AUTORELOAD", "true")

environment("KORGE_IPC", project.findProperty("korge.ipc")?.toString())
environment("KORGE_HEADLESS", project.findProperty("korge.headless")?.toString())
}
}
}
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ fun Project.mustAutoconfigureKMM(): Boolean =
project.name != "korge-kotlin-plugin" &&
project.name != "korge-reload-agent" &&
project.name != "korge-ipc" &&
project.name != "korge-kotlin-compiler" &&
project.name != "korge-benchmarks" &&
project.hasBuildGradle()

Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ korlibs-inject = { module = "com.soywiz:korlibs-inject", version.ref = "korlibs"
korlibs-template = { module = "com.soywiz:korlibs-template", version.ref = "korlibs" }
korlibs-time = { module = "com.soywiz:korlibs-time", version.ref = "korlibs" }
korlibs-serialization = { module = "com.soywiz:korlibs-serialization", version.ref = "korlibs" }
korlibs-datastructure = { module = "com.soywiz:korlibs-datastructure", version.ref = "korlibs" }
korlibs-datastructure-core = { module = "com.soywiz:korlibs-datastructure-core", version.ref = "korlibs" }
korlibs-memory = { module = "com.soywiz:korlibs-memory", version.ref = "korlibs" }
korlibs-io-stream = { module = "com.soywiz:korlibs-io-stream", version.ref = "korlibs" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
junit = { module = "junit:junit", version.ref = "junit" }
Expand Down
3 changes: 2 additions & 1 deletion korge-core/src/korlibs/event/Events.kt
Original file line number Diff line number Diff line change
Expand Up @@ -465,14 +465,15 @@ data class ChangeEvent(var oldValue: Any? = null, var newValue: Any? = null) : T
}
}

data class ReshapeEvent(var x: Int = 0, var y: Int = 0, var width: Int = 0, var height: Int = 0) : TypedEvent<ReshapeEvent>(ReshapeEvent) {
data class ReshapeEvent(var x: Int = 0, var y: Int = 0, var width: Int = 0, var height: Int = 0, var setPos: Boolean = true) : TypedEvent<ReshapeEvent>(ReshapeEvent) {
companion object : EventType<ReshapeEvent>

fun copyFrom(other: ReshapeEvent) {
this.x = other.x
this.y = other.y
this.width = other.width
this.height = other.height
this.setPos = other.setPos
}
}

Expand Down
66 changes: 64 additions & 2 deletions korge-core/src/korlibs/kgl/KmlGlContext.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,71 @@
package korlibs.kgl

import korlibs.io.lang.*

expect fun KmlGlContextDefault(window: Any? = null, parent: KmlGlContext? = null): KmlGlContext

class OffscreenKmlGlContext(
val colorRenderbuffer: Int,
val depthRenderbuffer: Int,
val framebuffer: Int,
val ctx: KmlGlContext,
var width: Int = 0,
var height: Int = 0,
) {
val gl get() = ctx.gl

fun setSize(width: Int, height: Int) {
this.width = width
this.height = height

val GL_RGBA8 = 0x8058

gl.bindRenderbuffer(KmlGl.RENDERBUFFER, colorRenderbuffer)
gl.renderbufferStorage(KmlGl.RENDERBUFFER, GL_RGBA8, width, height)
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, 0)

// Build the texture that will serve as the depth attachment for the framebuffer.
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, depthRenderbuffer)
gl.renderbufferStorage(KmlGl.RENDERBUFFER, KmlGl.DEPTH_COMPONENT, width, height)
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, 0)
}

fun doClear() {
gl.bindFramebuffer(KmlGl.FRAMEBUFFER, framebuffer)
gl.clear(KmlGl.COLOR_BUFFER_BIT or KmlGl.DEPTH_BUFFER_BIT)
}
}

fun OffsetKmlGlContext(fboWidth: Int, fboHeight: Int, doUnset: Boolean = true): KmlGlContext {
return NewOffsetKmlGlContext(fboWidth, fboHeight, doUnset).ctx
}

fun NewOffsetKmlGlContext(fboWidth: Int, fboHeight: Int, doUnset: Boolean = true): OffscreenKmlGlContext {
val ctx = KmlGlContextDefault()
ctx.set()

val gl = ctx.gl

// Build the texture that will serve as the color attachment for the framebuffer.
val colorRenderbuffer = gl.genRenderbuffer()
val depthRenderbuffer = gl.genRenderbuffer()
val framebuffer = gl.genFramebuffer()
val out = OffscreenKmlGlContext(colorRenderbuffer, depthRenderbuffer, framebuffer, ctx)

out.setSize(fboWidth, fboHeight)

// Build the framebuffer.
gl.bindFramebuffer(KmlGl.FRAMEBUFFER, framebuffer)
gl.framebufferRenderbuffer(KmlGl.FRAMEBUFFER, KmlGl.COLOR_ATTACHMENT0, KmlGl.RENDERBUFFER, colorRenderbuffer)
gl.framebufferRenderbuffer(KmlGl.FRAMEBUFFER, KmlGl.DEPTH_ATTACHMENT, KmlGl.RENDERBUFFER, depthRenderbuffer)

val status = gl.checkFramebufferStatus(KmlGl.FRAMEBUFFER)
//if (status != GL_FRAMEBUFFER_COMPLETE)
// Error

if (doUnset) ctx.unset()

return out
}

inline fun KmlGlContextDefaultTemp(block: (KmlGl) -> Unit) {
KmlGlContextDefault().use {
it.set()
Expand Down
32 changes: 26 additions & 6 deletions korge-core/src/korlibs/render/GameWindow.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package korlibs.render

import korlibs.concurrent.lock.*
import korlibs.concurrent.thread.*
import korlibs.datastructure.*
import korlibs.event.*
import korlibs.graphics.*
Expand Down Expand Up @@ -261,6 +262,9 @@ open class GameWindow :
return onEvent(RenderEvent, block)
}

val fastCounterTimePerFrame: FastDuration get() = (1_000_000.0 / fps).fastMicroseconds
val fastTimePerFrame: FastDuration get() = fastCounterTimePerFrame

val counterTimePerFrame: Duration get() = (1_000_000.0 / fps).microseconds
val timePerFrame: Duration get() = counterTimePerFrame

Expand Down Expand Up @@ -390,11 +394,27 @@ open class GameWindow :
launchImmediately(getCoroutineDispatcherWithCurrentContext()) {
entry()
}
while (running) {
val elapsed = frame()
val available = counterTimePerFrame - elapsed
if (available > TimeSpan.ZERO) delay(available)
}
//withContext(getCoroutineDispatcherWithCurrentContext()) {
//delay(1L)
while (running) {
var elapsed: FastDuration
val realElapsed = measureTime {
elapsed = frame()
}
val available = fastCounterTimePerFrame - elapsed
//val available = fastCounterTimePerFrame - realElapsed
if (available > FastDuration.ZERO) {
//println("delay=$available, elapsed=$elapsed, realElapsed=$realElapsed, fastCounterTimePerFrame=$fastCounterTimePerFrame")
loopDelay(available)
//NativeThread.sleepExact(available)
//NativeThread.sleepExact(available)
}
}
//}
}

open suspend fun loopDelay(time: FastDuration) {
delay(time)
}

// Referenced from korge-plugins repo
Expand Down Expand Up @@ -567,7 +587,7 @@ open class GameWindow :
dispatchReshapeEventEx(x, y, width, height, width, height)
}

fun dispatchReshapeEventEx(x: Int, y: Int, width: Int, height: Int, fullWidth: Int, fullHeight: Int) {
fun dispatchReshapeEventEx(x: Int, y: Int, width: Int, height: Int, fullWidth: Int = width, fullHeight: Int = height) {
ag.mainFrameBuffer.setSize(x, y, width, height, fullWidth, fullHeight)
dispatch(reshapeEvent.reset {
this.x = x
Expand Down
6 changes: 5 additions & 1 deletion korge-core/src@jvm/korlibs/render/DefaultGameWindowJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package korlibs.render
import korlibs.graphics.*
import korlibs.render.awt.*

actual fun CreateDefaultGameWindow(config: GameWindowCreationConfig): GameWindow = AwtGameWindow(config)
actual fun CreateDefaultGameWindow(config: GameWindowCreationConfig): GameWindow = when {
System.getenv("KORGE_HEADLESS") == "true" -> AwtOffscreenGameWindow(config)
else -> AwtGameWindow(config)
}

object JvmAGFactory : AGFactory {
override val supportsNativeFrame: Boolean = true
Expand All @@ -19,3 +22,4 @@ object JvmAGFactory : AGFactory {
}
}
}

57 changes: 57 additions & 0 deletions korge-core/src@jvm/korlibs/render/awt/AwtOffscreenGameWindow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package korlibs.render.awt

import korlibs.concurrent.thread.*
import korlibs.graphics.gl.*
import korlibs.kgl.*
import korlibs.math.geom.*
import korlibs.render.*
import korlibs.time.*

class AwtOffscreenGameWindow(
var size: Size = Size(640, 480),
val context: OffscreenKmlGlContext = NewOffsetKmlGlContext(size.width.toInt(), size.height.toInt(), doUnset = true),
val draw: Boolean = false,
override val ag: AGOpengl = AGOpenglAWT(context = context.ctx),
exitProcessOnClose: Boolean = false,
override var devicePixelRatio: Double = 1.0,
) : GameWindow() {
constructor(
config: GameWindowCreationConfig,
size: Size = Size(640, 480),
context: OffscreenKmlGlContext = NewOffsetKmlGlContext(size.width.toInt(), size.height.toInt(), doUnset = true),
) : this(
size = size,
//draw = config.draw,
context = context,
ag = AGOpenglAWT(config, context.ctx),
//exitProcessOnClose = config.exitProcessOnClose,
//devicePixelRatio = config.devicePixelRatio
)

override val width: Int get() = context.width
override val height: Int get() = context.height

init {
this.exitProcessOnClose = exitProcessOnClose
//onEvent(ReshapeEvent) {
// context.setSize(it.width, it.height)
// //size = Size(it.width.toDouble(), it.height.toDouble())
//}
}

override fun setSize(width: Int, height: Int) {
val rwidth = (width * devicePixelRatio).toInt()
val rheight = (height * devicePixelRatio).toInt()
//println("OFFSCREEN: setSize: $width, $height")
context.setSize(rwidth, rheight)
context.doClear()
dispatchReshapeEvent(0, 0, rwidth, rheight)
}

override suspend fun loopDelay(time: FastDuration) {
NativeThread.sleepExact(time)
}

//override val ag: AG = if (draw) AGSoftware(width, height) else DummyAG(width, height)
//override val ag: AG = AGDummy(width, height)
}
9 changes: 8 additions & 1 deletion korge-ipc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import korlibs.root.*

plugins {
//id "kotlin" version "1.6.21"
id("kotlin")
kotlin("jvm")
//kotlin("plugin.serialization")
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
//id "org.jetbrains.kotlin.jvm"
id("maven-publish")
}
Expand Down Expand Up @@ -56,6 +58,11 @@ korlibs.NativeTools.groovyConfigurePublishing(project, false)
korlibs.NativeTools.groovyConfigureSigning(project)

dependencies {
//implementation(libs.kotlinx.coroutines.core)
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
implementation(libs.korlibs.datastructure.core)
implementation(libs.korlibs.memory)
implementation(libs.korlibs.io.stream)
testImplementation(libs.bundles.kotlin.test)
}

Expand Down
Loading

0 comments on commit d3b01e7

Please sign in to comment.