com.utopiarise.serialization.godot.scene.SceneDeserializer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jvm-godot-resource-serialization Show documentation
Show all versions of jvm-godot-resource-serialization Show documentation
Godot resource data model serialization api for JVM
The newest version!
package com.utopiarise.serialization.godot.scene
import com.utopiarise.serialization.godot.core.*
import com.utopiarise.serialization.godot.model.ExternalResource
import com.utopiarise.serialization.godot.model.Node
import com.utopiarise.serialization.godot.model.SceneModel
import com.utopiarise.serialization.godot.model.SignalConnection
import java.io.File
fun sceneFromTscn(tscnFilePath: String): SceneModel {
return SceneDeserializer().deserialize(File(tscnFilePath))
}
@PublishedApi
internal class SceneDeserializer {
fun deserialize(file: File): SceneModel {
val declarations = Tokenizer(file.readText()).tokenize()
val sceneDeclaration = requireNotNull(declarations.firstOrNull { it is GdSceneDeclaration }) {
"Malformed scene ${file.path}: Got no \"gd_scene\" declaration"
}
val loadSteps = requireNotNull(sceneDeclaration.getValue("load_steps")) {
"Malformed scene ${file.path}: Got no \"load_steps\" in \"gd_scene\" declaration"
}
val format = requireNotNull(sceneDeclaration.getValue("format")) {
"Malformed scene ${file.path}: Got no \"load_steps\" in \"gd_scene\" declaration"
}
val externalResources = deserializeExternalResources(declarations, file)
val nodes = deserializeNodes(declarations, file, externalResources)
val signalConnections = deserializeSignalConnections(declarations, file, nodes)
return SceneModel(
loadSteps,
format,
externalResources,
nodes,
signalConnections
)
}
private fun deserializeExternalResources(declarations: List, file: File) = declarations
.filterIsInstance()
.map { externalResourceDeclaration ->
val path = requireNotNull(externalResourceDeclaration.getValue("path")) {
"Malformed \"ext_resource\" declaration in ${file.path}: Got no \"path\" value"
}
val type = requireNotNull(externalResourceDeclaration.getValue("type")) {
"Malformed \"ext_resource\" declaration in ${file.path}: Got no \"type\" value"
}
val id = requireNotNull(externalResourceDeclaration.getValue("id")) {
"Malformed \"ext_resource\" declaration in ${file.path}: Got no \"id\" value"
}
ExternalResource(
id,
path,
type
)
}
private fun deserializeNodes(declarations: List, file: File, externalResources: List): List {
val declarationToChildDeclarations: MutableMap> = mutableMapOf()
val tmpNonStandaloneDeclarations = mutableListOf()
declarations
.reversed()
.forEach {
if (it is StandaloneDeclaration) {
declarationToChildDeclarations[it] = tmpNonStandaloneDeclarations.toList()
tmpNonStandaloneDeclarations.clear()
} else {
tmpNonStandaloneDeclarations.add(it)
}
}
return declarationToChildDeclarations
.filterKeys { it is NodeDeclaration }
.map { (node, childDeclarations) ->
val name = requireNotNull(node.getValue("name")) {
"Malformed \"node\" declaration in ${file.path}: Got no \"name\" value"
}
val type = node.getValue("type")
val pathToInheritedScene = if (type == null) {
node
.getValue("instance")
?.let { externalResourceId ->
externalResources
.firstOrNull { it.id == externalResourceId }
?.path
}
} else null
val parent = node.getValue("parent")
if (type == null && pathToInheritedScene == null) {
val rootNode = declarationToChildDeclarations
.keys
.filterIsInstance()
.first { it.getValue("parent") == null }
// if scene is inherited, it is allowed for nodes to not have a type nor a pathToInheritedScene as they are defined in the scene that was inherited from
if (rootNode.getValue("instance") == null) {
throw IllegalArgumentException("Malformed \"node\" declaration in ${file.path}: Got no \"type\" or \"path\" value")
}
}
val scriptPath = childDeclarations
.filterIsInstance()
.map { scriptDeclaration ->
scriptDeclaration
.values
.filterIsInstance()
.first()
.values
.first()
.let { it as Int }
}
.firstOrNull()
?.let { scriptId ->
declarations
.filterIsInstance()
.firstOrNull { externalResourceDeclaration ->
externalResourceDeclaration.getValue("id") == scriptId
}
?.getValue("path")
}
Node(
name,
type,
pathToInheritedScene,
parent,
scriptPath
)
}
.reversed()
}
private fun deserializeSignalConnections(declarations: List, file: File, nodes: List): List {
return declarations
.filterIsInstance()
.map { signalConnectionDeclaration ->
val signal = requireNotNull(signalConnectionDeclaration.getValue("signal")) {
"Malformed \"connection\" declaration in ${file.path}: Got no \"signal\" value"
}
val from = requireNotNull(signalConnectionDeclaration.getValue("from")) {
"Malformed \"connection\" declaration in ${file.path}: Got no \"from\" value"
}
.let { nodeName ->
getNodeBySignalConnection(nodeName, nodes)
}
val to = requireNotNull(signalConnectionDeclaration.getValue("to")) {
"Malformed \"connection\" declaration in ${file.path}: Got no \"to\" value"
}
.let { nodeName ->
getNodeBySignalConnection(nodeName, nodes)
}
val method = requireNotNull(signalConnectionDeclaration.getValue("method")) {
"Malformed \"connection\" declaration in ${file.path}: Got no \"method\" value"
}
SignalConnection(
signal,
from,
to,
method
)
}
}
private fun getNodeBySignalConnection(nodeName: String, nodes: List) = if (nodeName == ".") {
nodes.first { it.parent == null }
} else {
val nodePathSegments = nodeName.split("/")
val node = nodePathSegments.last()
val parent = nodePathSegments.subList(0, nodePathSegments.size - 1).let {
if (it.isEmpty()) {
"."
} else {
it.joinToString("/")
}
}
nodes.first {
it.parent == parent && it.name == node
}
}
private inline fun Declaration.getValue(lexeme: String): T? = values
.filterIsInstance()
.firstOrNull { stringDeclaration ->
stringDeclaration.identifierToken?.lexeme == lexeme
}
?.let { declaration ->
if (declaration is NumberDeclaration) {
listOf(declaration.getValueToType(T::class.java))
} else {
declaration.values.toList()
}
}
?.firstOrNull()
?.let {
if (it is NumberDeclaration) {
it.getValueToType(T::class.java)
} else {
it
} as? T
}
}