All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.utopiarise.serialization.godot.scene.SceneDeserializer.kt Maven / Gradle / Ivy

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
        }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy