Skip to content

Commit

Permalink
Initial korge-ipc (#2244)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz authored Jun 19, 2024
1 parent fa2f19a commit 681ceec
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 4 deletions.
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 @@ -809,6 +809,7 @@ val Project.isSample: Boolean get() = project.path.startsWith(":samples:") || pr
fun Project.mustAutoconfigureKMM(): Boolean =
!project.name.startsWith("korge-gradle-plugin") &&
project.name != "korge-reload-agent" &&
project.name != "korge-ipc" &&
project.name != "korge-benchmarks" &&
project.hasBuildGradle()

Expand Down
5 changes: 5 additions & 0 deletions korge-ipc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/build
/.gradle
/.idea
/out
/bin
62 changes: 62 additions & 0 deletions korge-ipc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import korlibs.korge.gradle.targets.android.*
import korlibs.root.*

plugins {
//id "kotlin" version "1.6.21"
id("kotlin")
//id "org.jetbrains.kotlin.jvm"
id("maven-publish")
}

description = "Multiplatform Game Engine written in Kotlin"
group = RootKorlibsPlugin.KORGE_RELOAD_AGENT_GROUP

val jversion = GRADLE_JAVA_VERSION_STR

java {
setSourceCompatibility(jversion)
setTargetCompatibility(jversion)
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class).all {
kotlinOptions {
jvmTarget = jversion
apiVersion = "1.8"
languageVersion = "1.8"
suppressWarnings = true
}
}

publishing {
publications {
val maven by creating(MavenPublication::class) {
groupId = group.toString()
artifactId = "korge-ipc"
version = version
from(components["kotlin"])
}
}
}

val publishJvmPublicationToMavenLocal = tasks.register("publishJvmPublicationToMavenLocal", Task::class) {
group = "publishing"
dependsOn("publishMavenPublicationToMavenLocal")
}

afterEvaluate {
if (tasks.findByName("publishMavenPublicationToMavenRepository") != null) {
tasks.register("publishJvmPublicationToMavenRepository", Task::class) {
group = "publishing"
dependsOn("publishMavenPublicationToMavenRepository")
}
}
}

korlibs.NativeTools.groovyConfigurePublishing(project, false)
korlibs.NativeTools.groovyConfigureSigning(project)

dependencies {
testImplementation(libs.bundles.kotlin.test)
}

tasks { val jvmTest by creating { dependsOn("test") } }
176 changes: 176 additions & 0 deletions korge-ipc/src/main/kotlin/korlibs/korge/ipc/KorgeIPC.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package korlibs.korge.ipc

import java.io.*
import java.nio.*
import java.nio.channels.*
import java.nio.file.*
import kotlin.reflect.*

class KorgeIPC(val path: String = System.getenv("KORGE_IPC") ?: DEFAULT_PATH) {
init {
println("KorgeIPC:$path")
}

companion object {
val DEFAULT_PATH = "/tmp/KORGE_IPC"
}
val frame = KorgeFrameBuffer("$path.frame")
val events = KorgeEventsBuffer("$path.events")

val availableEvents get() = events.availableRead
fun writeEvent(e: IPCEvent) = events.writeEvent(e)
fun readEvent(e: IPCEvent = IPCEvent()): IPCEvent? = events.readEvent(e)
fun setFrame(f: IPCFrame) = frame.setFrame(f)
fun getFrame(): IPCFrame = frame.getFrame()
fun getFrameId(): Int = frame.getFrameId()
}

data class IPCEvent(
var timestamp: Long = System.currentTimeMillis(),
var type: Int = 0,
var p0: Int = 0,
var p1: Int = 0,
var p2: Int = 0,
var p3: Int = 0,
) {
fun setNow(): IPCEvent {
timestamp = System.currentTimeMillis()
return this
}

companion object {
val RESIZE = 1

val MOUSE_MOVE = 10
val MOUSE_DOWN = 11
val MOUSE_UP = 12
val MOUSE_CLICK = 13

val KEY_DOWN = 20
val KEY_UP = 21
val KEY_TYPE = 22
}
}

class KorgeEventsBuffer(val path: String) {
val channel = FileChannel.open(Path.of(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)
val HEAD_SIZE = 32
val EVENT_SIZE = 32
val MAX_EVENTS = 4096
var buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, (HEAD_SIZE + EVENT_SIZE * MAX_EVENTS).toLong())

init {
File(path).deleteOnExit()
}

var readPos: Long by DelegateBufferLong(buffer, 0)
var writePos: Long by DelegateBufferLong(buffer, 8)


fun eventOffset(index: Long): Int = 32 + ((index % MAX_EVENTS).toInt() * 32)

fun readEvent(index: Long, e: IPCEvent = IPCEvent()): IPCEvent {
val pos = eventOffset(index)
e.timestamp = buffer.getLong(pos + 0)
e.type = buffer.getInt(pos + 8)
e.p0 = buffer.getInt(pos + 12)
e.p1 = buffer.getInt(pos + 16)
e.p2 = buffer.getInt(pos + 20)
e.p3 = buffer.getInt(pos + 24)
return e
}

fun writeEvent(index: Long, e: IPCEvent) {
val pos = eventOffset(index)
buffer.putLong(pos + 0, e.timestamp)
buffer.putInt(pos + 8, e.type)
buffer.putInt(pos + 12, e.p0)
buffer.putInt(pos + 16, e.p1)
buffer.putInt(pos + 20, e.p2)
buffer.putInt(pos + 24, e.p3)
}

fun reset() {
readPos = 0L
writePos = 0L
}

val availableRead: Int get() = (writePos - readPos).toInt()
val availableWriteWithoutOverflow: Int get() = MAX_EVENTS - availableRead

fun writeEvent(e: IPCEvent) {
//println("EVENT: $e")
writeEvent(writePos++, e)
}

fun readEvent(e: IPCEvent = IPCEvent()): IPCEvent? {
if (readPos >= writePos) return null
return readEvent(readPos++, e)
}

fun close() {
channel.close()
}

class DelegateBufferLong(val buffer: ByteBuffer, val index: Int) {
operator fun getValue(obj: Any, property: KProperty<*>): Long = buffer.getLong(index)
operator fun setValue(obj: Any, property: KProperty<*>, value: Long) { buffer.putLong(index, value) }
}

class DelegateBufferInt(val buffer: ByteBuffer, val index: Int) {
operator fun getValue(obj: Any, property: KProperty<*>): Int = buffer.getInt(index)
operator fun setValue(obj: Any, property: KProperty<*>, value: Int) { buffer.putInt(index, value) }
}
}

class IPCFrame(val id: Int, val width: Int, val height: Int, val pixels: IntArray = IntArray(width * height))

class KorgeFrameBuffer(val path: String) {
val channel = FileChannel.open(Path.of(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)
var width: Int = 0
var height: Int = 0
var buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, 16 + 0)
var ibuffer = buffer.asIntBuffer()

init {
File(path).deleteOnExit()
}

fun ensureSize(width: Int, height: Int) {
if (this.width < width || this.height < height) {
this.width = width
this.height = height
buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, (16 + (width * height * 4)).toLong())
ibuffer = buffer.asIntBuffer()
}
}

fun setFrame(frame: IPCFrame) {
ensureSize(frame.width, frame.height)
ibuffer.clear()
ibuffer.put(frame.id)
ibuffer.put(frame.width)
ibuffer.put(frame.height)
ibuffer.put(frame.pixels)
}

fun getFrameId(): Int {
ibuffer.clear()
return ibuffer.get()
}

fun getFrame(): IPCFrame {
ibuffer.clear()
val id = ibuffer.get()
val width = ibuffer.get()
val height = ibuffer.get()
ensureSize(width, height)
val pixels = IntArray(width * height)
ibuffer.get(pixels)
return IPCFrame(id, width, height, pixels)
}

fun close() {
channel.close()
}
}
81 changes: 81 additions & 0 deletions korge-ipc/src/main/kotlin/korlibs/korge/ipc/KorgeIPCJPanel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package korlibs.korge.ipc

import java.awt.*
import java.awt.event.*
import java.awt.image.*
import javax.swing.*

class KorgeIPCJPanel(val ipc: KorgeIPC = KorgeIPC()) : JPanel(), MouseListener, MouseMotionListener, MouseWheelListener, KeyListener {
var image: BufferedImage? = null

init {
Timer(16) {
readFrame()
//println(events.availableRead)
}.also { it.isRepeats = true }.start()
}

override fun paint(g: Graphics) {
//g.color = Color.RED
//g.fillRect(0, 0, 100, 100)
if (image != null) {
g.drawImage(image, 0, 0, width, height, null)
}
}

fun rgbaToBgra(v: Int): Int = ((v shl 16) and 0x00FF0000) or ((v shr 16) and 0x000000FF) or (v and 0xFF00FF00.toInt())

var currentFrameId = -1

fun readFrame() {
val frameId = ipc.getFrameId()
if (frameId == currentFrameId) return // Do not update
val frame = ipc.getFrame()
if (frame.width == 0 || frame.height == 0) return // Empty frame
currentFrameId = frame.id
val image = BufferedImage(frame.width, frame.height, BufferedImage.TYPE_INT_ARGB)
this.image = image
val imgPixels = (image.raster.dataBuffer as DataBufferInt).data
System.arraycopy(frame.pixels, 0, imgPixels, 0, frame.width * frame.height)
for (n in imgPixels.indices) imgPixels[n] = rgbaToBgra(imgPixels[n])
repaint()
}

private fun sendEv(type: Int, e: KeyEvent) = ipc.writeEvent(IPCEvent(type = type, p0 = e.keyCode, p1 = e.keyChar.code))
private fun sendEv(type: Int, e: MouseEvent) = ipc.writeEvent(IPCEvent(type = type, p0 = e.x, p1 = e.y, p2 = e.button))
override fun keyTyped(e: KeyEvent) = sendEv(IPCEvent.KEY_TYPE, e)
override fun keyPressed(e: KeyEvent) = sendEv(IPCEvent.KEY_DOWN, e)
override fun keyReleased(e: KeyEvent) = sendEv(IPCEvent.KEY_UP, e)
override fun mouseMoved(e: MouseEvent) = sendEv(IPCEvent.MOUSE_MOVE, e)
override fun mouseDragged(e: MouseEvent) = sendEv(IPCEvent.MOUSE_MOVE, e)
override fun mouseWheelMoved(e: MouseWheelEvent) = sendEv(IPCEvent.MOUSE_MOVE, e)
override fun mouseExited(e: MouseEvent) = sendEv(IPCEvent.MOUSE_MOVE, e)
override fun mouseEntered(e: MouseEvent) = sendEv(IPCEvent.MOUSE_MOVE, e)
override fun mouseReleased(e: MouseEvent) = sendEv(IPCEvent.MOUSE_UP, e)
override fun mousePressed(e: MouseEvent) = sendEv(IPCEvent.MOUSE_DOWN, e)
override fun mouseClicked(e: MouseEvent) = sendEv(IPCEvent.MOUSE_CLICK, e)

init {
addKeyListener(this)
addMouseListener(this)
addMouseMotionListener(this)
addMouseWheelListener(this)
}

companion object {
@JvmStatic
fun main() {
val frame = JFrame()
val frameHolder = korlibs.korge.ipc.KorgeIPCJPanel()
frame.add(frameHolder)
frame.addKeyListener(frameHolder)

frame.preferredSize = Dimension(640, 480)
frame.pack()
frame.setLocationRelativeTo(null)

frame.isVisible = true

}
}
}
1 change: 1 addition & 0 deletions korge/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ project.extensions.extraProperties.properties.apply {

dependencies {
commonMainApi(project(":korge-core"))
jvmMainApi(project(":korge-ipc"))
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
korlibs.korge.StandardViewsCompleter
korlibs.korge.IPCViewsCompleter
4 changes: 0 additions & 4 deletions korge/src/korlibs/korge/Korge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ import kotlin.time.*

typealias KorgeConfig = Korge

suspend fun test() = Korge {

}

data class KorgeDisplayMode(val scaleMode: ScaleMode, val scaleAnchor: Anchor, val clipBorders: Boolean) {
companion object {
val DEFAULT get() = CENTER
Expand Down
Loading

0 comments on commit 681ceec

Please sign in to comment.