···11+package assets
22+33+import assets.ui.BackgroundAsset
44+import assets.ui.MapAsset
55+import assets.ui.TexturesAsset
66+import java.io.IOException
77+import java.nio.file.DirectoryStream
88+import java.nio.file.Files
99+import java.nio.file.Path
1010+import kotlin.io.path.*
1111+1212+/**
1313+ * Registry for asset types. Maps file extensions to factory functions that create typed Asset instances.
1414+ *
1515+ * Built-in types are registered at startup via init().
1616+ * Addon types can be registered dynamically via register() with addon = true.
1717+ *
1818+ * When an unregistered extension is encountered, create() returns a plain Asset instance.
1919+ */
2020+class AssetRegistry {
2121+ internal data class Registration(
2222+ val extension: String,
2323+ val factory: (Path, Path) -> Asset,
2424+ val addon: Boolean,
2525+ )
2626+2727+ internal val byExtension = mutableMapOf<String, Registration>()
2828+2929+ /**
3030+ * Registers an asset type for a file extension.
3131+ * @param extension File extension without leading dot (e.g., "xml", "png")
3232+ * @param factory Constructor reference that takes (root: Path, relativePath: Path) -> Asset
3333+ * @param addon Whether this is an addon type (used for hot reload)
3434+ */
3535+ fun register(extension: String, factory: (Path, Path) -> Asset, addon: Boolean = false) {
3636+ byExtension[extension] = Registration(extension, factory, addon)
3737+ }
3838+3939+ /**
4040+ * Unloads all addon-registered types.
4141+ * After unloading, those extensions will create plain Asset instances.
4242+ */
4343+ fun unloadAddon() {
4444+ byExtension.values.removeAll { it.addon }
4545+ }
4646+4747+ /**
4848+ * Creates an Asset for the given path.
4949+ * Returns a typed subclass if the extension is registered, otherwise a plain Asset.
5050+ */
5151+ fun create(root: Path, relativePath: Path): Asset {
5252+ val ext = relativePath.extension
5353+ val reg = byExtension[ext]
5454+ return reg?.factory?.invoke(root, relativePath) ?: Asset(root, relativePath)
5555+ }
5656+5757+ /**
5858+ * Returns all assets of a specific type by scanning asset directories.
5959+ * @param T The asset type to filter by
6060+ * @param root Root directory to scan
6161+ * @return List of assets matching the type
6262+ */
6363+ inline fun <reified T : Asset> getAllOfType(root: Path): List<T> {
6464+ // TODO: Implement
6565+ return emptyList()
6666+ }
6767+6868+ companion object {
6969+ /**
7070+ * Global registry instance.
7171+ * Call init() at startup to register built-in types.
7272+ */
7373+ @JvmStatic
7474+ val instance = AssetRegistry()
7575+7676+ /**
7777+ * Registers built-in asset types.
7878+ * Should be called once at application startup.
7979+ */
8080+ @JvmStatic
8181+ fun init() {
8282+ instance.register("xml", ::MapAsset)
8383+ instance.register("png", ::BackgroundAsset)
8484+ instance.register("json", ::TexturesAsset)
8585+ }
8686+ }
8787+}
+16
src/main/java/assets/LiveAsset.kt
···11+package assets
22+33+/**
44+ * Base class for loaded, in-memory asset data.
55+ * Holds a reference back to the asset it was loaded from.
66+ *
77+ * The relationship:
88+ * - Asset (file on disk, cheap)
99+ * ↓ load
1010+ * - LiveAsset (parsed data in memory, editable)
1111+ * ↓ save
1212+ * - Asset (written back to disk)
1313+ */
1414+abstract class LiveAsset(
1515+ val asset: Asset,
1616+)