Skip to content

Commit 7ece533

Browse files
committed
Rework plugin to use core dynamic library
1 parent af7fe43 commit 7ece533

File tree

16 files changed

+225
-326
lines changed

16 files changed

+225
-326
lines changed

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ description = "A type-safe Godot node tree representation in Kotlin"
1111

1212
plugins {
1313
kotlin("jvm") version "1.9.20"
14+
kotlin("plugin.serialization") version "1.9.25"
1415
id("com.gradle.plugin-publish") version "1.2.1"
1516
id("com.adarshr.test-logger") version "4.0.0"
1617
}
@@ -21,6 +22,8 @@ repositories {
2122

2223
dependencies {
2324
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
25+
implementation("net.java.dev.jna:jna:5.17.0")
26+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
2427
testImplementation(kotlin("test"))
2528
}
2629

src/main/kotlin/com/tomwyr/Command.kt

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/main/kotlin/com/tomwyr/Plugin.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.tomwyr
22

3+
import com.tomwyr.command.GenerateTreeCommand
34
import org.gradle.api.Plugin
45
import org.gradle.api.Project
56
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
@@ -11,10 +12,10 @@ class GodotNodeTree : Plugin<Project> {
1112
}
1213

1314
private fun registerTask(project: Project) {
14-
val config = project.extensions.create("godotNodeTree", GodotNodeTreeConfig::class.java)
15+
val params = project.extensions.create("godotNodeTree", GodotNodeTreeInput::class.java)
1516
project.tasks.register("generateNodeTree") { task ->
1617
task.doLast {
17-
GenerateTreeCommand().run(project, config)
18+
GenerateTreeCommand.from(project, params).run()
1819
}
1920
}
2021
}
@@ -26,7 +27,7 @@ class GodotNodeTree : Plugin<Project> {
2627
}
2728
}
2829

29-
open class GodotNodeTreeConfig(
30-
var projectPath: String? = null,
31-
var packageName: String? = null,
30+
open class GodotNodeTreeInput(
31+
var projectPath: String? = null,
32+
var packageName: String? = null,
3233
)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.tomwyr.command
2+
3+
import com.tomwyr.GodotNodeTreeInput
4+
import com.tomwyr.common.InvalidGodotProject
5+
import com.tomwyr.ffi.generateNodeTree
6+
import org.gradle.api.Project
7+
import java.io.File
8+
import java.nio.file.Paths
9+
10+
class GenerateTreeCommand(
11+
val libPath: String,
12+
val projectPath: String,
13+
val outputPath: String,
14+
val packageName: String?,
15+
) {
16+
fun run() {
17+
val tree = generateNodeTree(libPath = libPath, projectPath = projectPath)
18+
val content = NodeTreeRenderer().render(packageName, tree)
19+
NodeTreeWriter().write(content, outputPath)
20+
}
21+
22+
companion object Factory {
23+
fun from(project: Project, input: GodotNodeTreeInput): GenerateTreeCommand {
24+
val rootPath = project.projectDir.absolutePath
25+
val libPath = "NodeTreeGenerator.dylib"
26+
val projectPath = getProjectPath(rootPath = rootPath, relativePath = input.projectPath)
27+
val outputPath = getOutputPath(rootPath = rootPath, packageName = input.packageName)
28+
29+
return GenerateTreeCommand(
30+
libPath = libPath,
31+
projectPath = projectPath,
32+
outputPath = outputPath,
33+
packageName = input.packageName
34+
)
35+
}
36+
37+
private fun getProjectPath(rootPath: String, relativePath: String?): String {
38+
val fileName = "project.godot"
39+
val path = Paths.get(rootPath, relativePath ?: "", fileName).toString()
40+
if (!File(path).exists()) throw InvalidGodotProject()
41+
return path
42+
}
43+
44+
private fun getOutputPath(rootPath: String, packageName: String?): String {
45+
val buildPath = listOf("build", "generated", "godotNodeTree", "kotlin").toTypedArray()
46+
val packagePath = packageName?.split(".")?.toTypedArray() ?: emptyArray()
47+
val fileName = "GodotNodeTree.kt"
48+
return Paths.get(rootPath, *buildPath, *packagePath, fileName).toString()
49+
}
50+
}
51+
}

src/main/kotlin/com/tomwyr/generator/Renderer.kt renamed to src/main/kotlin/com/tomwyr/command/Renderer.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
package com.tomwyr.generator
1+
package com.tomwyr.command
22

33
import com.tomwyr.common.*
4-
import com.tomwyr.utils.capitalize
54

65
class NodeTreeRenderer {
7-
fun render(packageName: String?, scenes: List<Scene>): String {
6+
fun render(packageName: String?, tree: NodeTree): String {
87
val header = renderHeader(packageName)
9-
val nodeTree = renderNodeTree(scenes)
10-
val sceneNodes = scenes.map { renderScene(it) }.joinLines(spacing = 2)
8+
val nodeTree = renderNodeTree(tree.scenes)
9+
val sceneNodes = tree.scenes.map { renderScene(it) }.joinLines(spacing = 2)
1110
val types = renderTypes()
1211

1312
return listOf(header, nodeTree, sceneNodes, types)
@@ -150,4 +149,6 @@ private fun String.indentLine(times: Int = 1): String = lineSequence().joinToStr
150149

151150
private fun String.trimBlankLines(): String = lineSequence().joinToString("\n") { it.ifBlank { "" } }
152151

152+
private fun String.capitalize(): String = replaceFirstChar { it.uppercase() }
153+
153154
private data class RenderNodeResult(val field: String, val nestedClass: String?)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.tomwyr.command
2+
3+
import java.io.File
4+
5+
class NodeTreeWriter {
6+
fun write(content: String, path: String) {
7+
val file = File(path).apply {
8+
parentFile.mkdirs()
9+
createNewFile()
10+
}
11+
file.outputStream().apply {
12+
write(content.toByteArray())
13+
close()
14+
}
15+
}
16+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.tomwyr.common
2+
3+
import kotlinx.serialization.Serializable
4+
5+
sealed class GodotKotlinTreeError : Exception() {
6+
override fun getLocalizedMessage(): String = when (this) {
7+
is InvalidGodotProject -> "The project in which GodotNodeTree annotation was used isn't a valid Godot project directory"
8+
is GeneratorError -> error.localizedMessage
9+
}
10+
}
11+
12+
class InvalidGodotProject : GodotKotlinTreeError()
13+
14+
class GeneratorError(val error: GodotNodeTreeError) : GodotKotlinTreeError()
15+
16+
@Serializable
17+
sealed class GodotNodeTreeError : Exception() {
18+
override fun getLocalizedMessage(): String = when (this) {
19+
is ScanningScenesFailed -> "Unable to scan scene files for project at `$projectPath`."
20+
is ReadingSceneFailed -> "Unable to read contens of scene at `$scenePath`."
21+
is UnexpectedNodeParameters -> "A node with unexpected set of parameters encountered: $nodeParams."
22+
is UnexpectedSceneResource -> "A node pointing to an unknown scene resource encountered with id: $instance."
23+
is ParentNodeNotFound -> "None of the parsed nodes was identified as the parent node of scene $sceneName."
24+
}
25+
}
26+
27+
@Serializable
28+
class ScanningScenesFailed(val projectPath: String) : GodotNodeTreeError()
29+
30+
@Serializable
31+
class ReadingSceneFailed(val scenePath: String) : GodotNodeTreeError()
32+
33+
@Serializable
34+
class UnexpectedNodeParameters(val nodeParams: NodeParams) : GodotNodeTreeError()
35+
36+
@Serializable
37+
class UnexpectedSceneResource(val instance: String) : GodotNodeTreeError()
38+
39+
@Serializable
40+
class ParentNodeNotFound(val sceneName: String) : GodotNodeTreeError()

src/main/kotlin/com/tomwyr/common/Exceptions.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 23 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,41 @@
11
package com.tomwyr.common
22

3-
import kotlin.math.max
3+
import kotlinx.serialization.Serializable
44

5-
class SceneData(val name: String, val content: String)
5+
@Serializable
6+
class NodeParams(
7+
val name: String,
8+
val type: String?,
9+
val instance: String?,
10+
val parent: String?,
11+
)
612

7-
data class Scene(val name: String, val root: Node) {
8-
val nodesCount: Int = root.flatten().size
9-
val nodesDepth: Int = root.longestPath().size
10-
}
13+
@Serializable
14+
class NodeTree(val scenes: List<Scene>)
15+
16+
@Serializable
17+
class Scene(val name: String, val root: Node)
1118

19+
@Serializable
1220
sealed class Node {
1321
abstract val name: String
14-
abstract fun flatten(): List<Node>
15-
abstract fun longestPath(): List<Node>
1622
}
1723

18-
data class ParentNode(
24+
@Serializable
25+
class ParentNode(
1926
override val name: String,
2027
val type: String,
2128
val children: List<Node>,
22-
) : Node() {
23-
override fun flatten(): List<Node> {
24-
return children.flatMap { it.flatten() } + this
25-
}
26-
27-
override fun longestPath(): List<Node> {
28-
return children.map { it.longestPath() }.maxByOrNull { it.size }.orEmpty() + this
29-
}
30-
}
29+
) : Node()
3130

32-
data class LeafNode(
31+
@Serializable
32+
class LeafNode(
3333
override val name: String,
3434
val type: String,
35-
) : Node() {
36-
override fun flatten(): List<Node> = listOf(this)
37-
38-
override fun longestPath(): List<Node> = listOf(this)
39-
}
35+
) : Node()
4036

41-
data class NestedScene(
37+
@Serializable
38+
class NestedScene(
4239
override val name: String,
4340
val scene: String,
44-
) : Node() {
45-
override fun flatten(): List<Node> = listOf(this)
46-
47-
override fun longestPath(): List<Node> = listOf(this)
48-
}
49-
50-
class NodeTreeInfo(
51-
val nodes: Int,
52-
val depth: Int,
53-
val scenes: Int,
54-
) {
55-
companion object {
56-
operator fun invoke(scenes: List<Scene>): NodeTreeInfo {
57-
var nodes = 0
58-
var depth = 0
59-
60-
for (scene in scenes) {
61-
nodes += scene.nodesCount
62-
depth = max(depth, scene.nodesDepth)
63-
}
64-
65-
return NodeTreeInfo(
66-
nodes = nodes,
67-
depth = depth,
68-
scenes = scenes.size,
69-
)
70-
}
71-
}
72-
}
41+
) : Node()

src/main/kotlin/com/tomwyr/ffi/Ffi.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.tomwyr.ffi
2+
3+
import com.sun.jna.Library
4+
import com.sun.jna.Pointer
5+
import com.sun.jna.Native
6+
import com.tomwyr.common.GeneratorError
7+
import com.tomwyr.common.GodotNodeTreeError
8+
import com.tomwyr.common.NodeTree
9+
10+
fun generateNodeTree(libPath: String, projectPath: String): NodeTree {
11+
val lib = Native.load(libPath, GodotNodeTreeLib::class.java)
12+
val resultPtr = lib.generateNodeTree(projectPath)
13+
val result = Result.fromJson<NodeTree, GodotNodeTreeError>(resultPtr.getString(0))
14+
return when (result) {
15+
is Ok -> result.value
16+
is Err -> throw GeneratorError(result.value)
17+
}
18+
}
19+
20+
interface GodotNodeTreeLib : Library {
21+
fun generateNodeTree(projectPath: String): Pointer
22+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.tomwyr.ffi
2+
3+
import kotlinx.serialization.*
4+
import kotlinx.serialization.descriptors.SerialDescriptor
5+
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
6+
import kotlinx.serialization.encoding.Decoder
7+
import kotlinx.serialization.encoding.Encoder
8+
import kotlinx.serialization.json.*
9+
10+
@Serializable
11+
sealed class Result<T, E : Throwable> {
12+
fun unwrap(): T = when (this) {
13+
is Ok -> value
14+
is Err -> throw value
15+
}
16+
}
17+
18+
@Serializable
19+
class Ok<T, E : Throwable>(val value: T) : Result<T, E>()
20+
21+
@Serializable
22+
class Err<T, E : Throwable>(val value: E) : Result<T, E>()
23+
24+
class ResultSerializer<T, E : Throwable>(
25+
private val tSerializer: KSerializer<T>,
26+
private val eSerializer: KSerializer<E>
27+
) : KSerializer<Result<T, E>> {
28+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Result")
29+
30+
override fun serialize(encoder: Encoder, value: Result<T, E>) {
31+
require(encoder is JsonEncoder)
32+
val json = when (value) {
33+
is Ok -> buildJsonObject { put("ok", encoder.json.encodeToJsonElement(tSerializer, value.value)) }
34+
is Err -> buildJsonObject { put("err", encoder.json.encodeToJsonElement(eSerializer, value.value)) }
35+
}
36+
encoder.encodeJsonElement(json)
37+
}
38+
39+
override fun deserialize(decoder: Decoder): Result<T, E> {
40+
require(decoder is JsonDecoder)
41+
val json = decoder.decodeJsonElement().jsonObject
42+
return when {
43+
"ok" in json -> Ok(decoder.json.decodeFromJsonElement(tSerializer, json.getValue("ok")))
44+
"err" in json -> Err(decoder.json.decodeFromJsonElement(eSerializer, json.getValue("err")))
45+
else -> throw SerializationException("Invalid data format for the expected Result type")
46+
}
47+
}
48+
}
49+
50+
inline fun <reified T, reified E : Throwable> Result.Companion.fromJson(input: String): Result<T, E> {
51+
val serializer = ResultSerializer<T, E>(serializer(), serializer())
52+
return Json.decodeFromString(serializer, input)
53+
}
54+
55+
inline fun <reified T, reified E : Throwable> Result<T, E>.toJson(): String {
56+
val serializer = ResultSerializer<T, E>(serializer(), serializer())
57+
return Json.encodeToString(serializer, this)
58+
}

0 commit comments

Comments
 (0)