diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt index accc2a5a..5166b665 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt @@ -173,7 +173,7 @@ class GameEngine( workEvents.forEach { resource -> // The resource is loading if (!resource.reload) { - logger.info("GAME_ENGINE") { "Loaded ${resource.name} ${resource.type}" } + logger.info("GAME_ENGINE") { "Loaded ${resource.name} ${resource.type} (version: ${resource.version})" } when (resource.type) { BOOT_GAMESCRIPT -> { // Always put the boot script at the top of the stack @@ -221,7 +221,7 @@ class GameEngine( scripts[0]!!.resourcesLoaded() } } else { - logger.info("GAME_ENGINE") { "Reload ${resource.name} ${resource.type}" } + logger.info("GAME_ENGINE") { "Reload ${resource.name} ${resource.type} (version: ${resource.version})" } // The resource already has been loaded. when (resource.type) { BOOT_GAMESCRIPT -> { @@ -279,6 +279,8 @@ class GameEngine( GAME_LEVEL -> { levels[resource.index] = resource as GameLevel + // Force the reloading of the script as level init might occur in the _init block. + scripts[current]?.reload = true } GAME_SOUND -> { diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt index ccfe57af..89d11e33 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt @@ -89,6 +89,7 @@ class GfxLib(private val resourceAccess: GameResourceAccess) : TwoArgFunction() copyFrom(frameBuffer.colorIndexBuffer) { index, _, _ -> index } } val sheet = SpriteSheet( + 0, arg.checkint(), "frame_buffer", ResourceType.GAME_SPRITESHEET, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt index f6d1f678..1964eaa5 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt @@ -47,8 +47,8 @@ class KeysLib : TwoArgFunction() { keys["space"] = LuaInteger.valueOf(Key.SPACE.ordinal) keys["enter"] = LuaInteger.valueOf(Key.ENTER.ordinal) - arg2.set("keys", keys) - arg2.get("package").get("loaded").set("keys", keys) + arg2["keys"] = keys + arg2["package"]["loaded"]["keys"] = keys return keys } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt index 9d5fc25e..592021c0 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt @@ -7,6 +7,7 @@ import com.github.mingdx.tiny.doc.TinyFunction import com.github.mingdx.tiny.doc.TinyLib import com.github.minigdx.tiny.Pixel import com.github.minigdx.tiny.engine.GameResourceAccess +import com.github.minigdx.tiny.resources.GameLevel import com.github.minigdx.tiny.resources.LdtkEntity import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement @@ -33,7 +34,8 @@ import kotlin.math.min "WARNING: Projects need to be exported using " + "https://ldtk.io/docs/game-dev/super-simple-export/['Super simple export']", ) -class MapLib(private val resourceAccess: GameResourceAccess, private val spriteSize: Pair) : TwoArgFunction() { +class MapLib(private val resourceAccess: GameResourceAccess, private val spriteSize: Pair) : + TwoArgFunction() { private var currentLevel: Int = 0 @@ -186,15 +188,22 @@ entity.customFields -- access custom field of the entity private val cachedEntities: MutableMap = mutableMapOf() + private var currentLevelVersion = currentLevel to -1 + private val entities: LuaValue get() { - return cachedEntities[currentLevel] ?: cacheMe(resourceAccess.level(currentLevel)?.entities) + // When the level is update, clear the cache. + val version = resourceAccess.level(currentLevel)?.version + if (currentLevel to version != currentLevelVersion) { + cachedEntities.clear() + } + return cachedEntities[currentLevel] ?: cacheMe(resourceAccess.level(currentLevel)) } - private fun cacheMe(entities: Map>?): LuaValue { + private fun cacheMe(level: GameLevel?): LuaValue { // Transform the list of entities into a table in Lua. val toCache = LuaTable() - entities?.forEach { (key, v) -> + level?.entities?.forEach { (key, v) -> val entitiesOfType = LuaTable() v.forEach { entitiesOfType[it.iid] = it.toLuaTable() @@ -203,6 +212,7 @@ entity.customFields -- access custom field of the entity } cachedEntities[currentLevel] = toCache + currentLevelVersion = currentLevel to (level?.version ?: -1) return toCache } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameLevel.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameLevel.kt index 164b13aa..bc75ea97 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameLevel.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameLevel.kt @@ -3,13 +3,17 @@ package com.github.minigdx.tiny.resources import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement -class GameLevel( +data class GameLevel( + + override val version: Int, override val index: Int, override val type: ResourceType, override val name: String, val numberOfLayers: Int, val ldktLevel: LdtkLevel, + ) : GameResource { + override var reload: Boolean = false val imageLayers: Array = Array(numberOfLayers) { null } val intLayers: Array = Array(numberOfLayers) { null } @@ -17,6 +21,7 @@ class GameLevel( fun copy(): GameLevel { val gameLevel = GameLevel( + version, index, type, name, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameResource.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameResource.kt index f87faca6..83c442f5 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameResource.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameResource.kt @@ -1,6 +1,12 @@ package com.github.minigdx.tiny.resources interface GameResource { + /** + * Version of the resource (ie: ~ number of time it has been reloaded) + * This version should be used only to know if the resources has been updated. + */ + val version: Int + /** * Index of this game resource, by type. */ diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt index 19b0c2bd..484e4310 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt @@ -29,6 +29,7 @@ import org.luaj.vm2.lib.StringLib import org.luaj.vm2.lib.TableLib class GameScript( + override val version: Int, /** * Index of game script */ diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt index 19e8116b..2fd5b04b 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt @@ -16,15 +16,11 @@ import com.github.minigdx.tiny.resources.ResourceType.GAME_GAMESCRIPT import com.github.minigdx.tiny.resources.ResourceType.GAME_LEVEL import com.github.minigdx.tiny.resources.ResourceType.GAME_SPRITESHEET import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json class LdKtIntLayer( @@ -47,7 +43,7 @@ class LdKtImageLayer( var pixels: PixelArray = PixelArray(width, height), ) -@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) class ResourceFactory( private val vfs: VirtualFileSystem, private val platform: Platform, @@ -58,8 +54,9 @@ class ResourceFactory( private val json = Json { ignoreUnknownKeys = true } fun soundEffect(index: Int, name: String): Flow { + var version = 0 return vfs.watch(platform.createSoundStream(name)) - .map { soundData -> Sound(index, name, soundData) } + .map { soundData -> Sound(version++, index, name, soundData) } .onEach { logger.debug("RESOURCE_FACTORY") { "Loading sound '$name'" @@ -68,6 +65,7 @@ class ResourceFactory( } fun gameLevel(index: Int, name: String): Flow { + var version = 0 return flowOf("$name/data.json") .map { platform.createByteArrayStream(it) } .flatMapMerge { filestream -> vfs.watch(filestream) } @@ -78,8 +76,7 @@ class ResourceFactory( logger.debug("RESOURCE_FACTORY") { "Loading level " + level.uniqueIdentifer + " with layers " + level.layers.joinToString(", ") } - }.flatMapMerge { level -> - + }.map { level -> val layers = listOf("$name/_composite.png") + level.layers.map { layer -> "$name/$layer" } val pngLayers = layers .mapIndexed { index, layer -> @@ -91,29 +88,29 @@ class ResourceFactory( width = level.width, height = level.height, ) - }.asFlow() - .flatMapMerge { layer -> - vfs.watch(platform.createImageStream(layer.name)).map { imageData -> - convertToColorIndex(imageData.data, level.width, level.height) - }.map { texture -> + }.mapNotNull { layer -> + val stream = platform.createImageStream(layer.name) + if (stream.exists()) { + val imageData = stream.read() + val texture = convertToColorIndex(imageData.data, level.width, level.height) layer.apply { pixels = texture } + } else { + null } } val intLayers = layers .map { layer -> layer.replace(".png", ".csv") } - .mapIndexed { index, layer -> - layer to index - }.asFlow() - .flatMapMerge { (layer, index) -> - vfs.watch(platform.createByteArrayStream(layer)).map { data -> - data.decodeToString() + .mapIndexedNotNull { index, layer -> + val stream = platform.createByteArrayStream(layer) + if (stream.exists()) { + val data = stream.read() + val lines = data.decodeToString() .lines() .map { l -> l.split(",").filter { it.isNotBlank() } } .filterNot { it.isEmpty() } - }.map { lines -> val l = LdKtIntLayer( name = layer, index = index, @@ -129,21 +126,19 @@ class ResourceFactory( } } l + } else { + null } } - flowOf(GameLevel(index, GAME_LEVEL, name, level.layers.size + 1, level)) - .combine(pngLayers) { l, layer -> - l.apply { - imageLayers[layer.index] = layer - } - }.combine(intLayers) { l, layer -> - l.apply { - this.intLayers[layer.index] = layer - } - }.map { - it.copy() + GameLevel(version++, index, GAME_LEVEL, name, level.layers.size + 1, level).apply { + pngLayers.forEach { layer -> + imageLayers[layer.index] = layer + } + intLayers.forEach { layer -> + this.intLayers[layer.index] = layer } + }.copy() } } @@ -163,8 +158,9 @@ class ResourceFactory( gameOptions: GameOptions, resourceType: ResourceType, ): Flow { + var version = 0 return vfs.watch(platform.createByteArrayStream(name)).map { content -> - GameScript(index, name, gameOptions, inputHandler, resourceType).apply { + GameScript(version++, index, name, gameOptions, inputHandler, resourceType).apply { this.content = content } }.onEach { @@ -183,9 +179,10 @@ class ResourceFactory( } private fun spritesheet(index: Int, name: String, resourceType: ResourceType): Flow { + var version = 0 return vfs.watch(platform.createImageStream(name)).map { imageData -> val sheet = convertToColorIndex(imageData.data, imageData.width, imageData.height) - SpriteSheet(index, name, resourceType, sheet, imageData.width, imageData.height) + SpriteSheet(version++, index, name, resourceType, sheet, imageData.width, imageData.height) }.onEach { logger.debug("RESOURCE_FACTORY") { "Loading spritesheet '$name' ($resourceType)" diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/Sound.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/Sound.kt index 837f8de7..c3f9416d 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/Sound.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/Sound.kt @@ -3,6 +3,7 @@ package com.github.minigdx.tiny.resources import com.github.minigdx.tiny.platform.SoundData data class Sound( + override val version: Int, override val index: Int, override val name: String, val data: SoundData, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/SpriteSheet.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/SpriteSheet.kt index 04d8aa33..222e65d7 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/SpriteSheet.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/SpriteSheet.kt @@ -4,6 +4,7 @@ import com.github.minigdx.tiny.Pixel import com.github.minigdx.tiny.graphic.PixelArray class SpriteSheet( + override val version: Int, override val index: Int, override val name: String, override val type: ResourceType, diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt index b36bf765..d75101d2 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt @@ -22,6 +22,7 @@ class StdLibTest { private val mockResources = object : GameResourceAccess { override val bootSpritesheet: SpriteSheet = SpriteSheet( + 0, 0, "boot", ResourceType.BOOT_SPRITESHEET,