package iron.data; import haxe.Json; import iron.data.SceneFormat; import iron.system.LnxPack; import iron.system.Lz4; using StringTools; // Global data list and asynchronous data loading class Data { public static var cachedSceneRaws: Map = new Map(); public static var cachedMeshes: Map = new Map(); public static var cachedLights: Map = new Map(); public static var cachedCameras: Map = new Map(); public static var cachedMaterials: Map = new Map(); public static var cachedParticles: Map = new Map(); public static var cachedWorlds: Map = new Map(); public static var cachedShaders: Map = new Map(); #if rp_probes public static var cachedProbes: Map = new Map(); #end public static var cachedBlobs: Map = new Map(); public static var cachedImages: Map = new Map(); #if lnx_audio public static var cachedSounds: Map = new Map(); #end public static var cachedVideos: Map = new Map(); public static var cachedFonts: Map = new Map(); public static var assetsLoaded = 0; static var loadingMeshes: MapVoid>> = new Map(); static var loadingLights: MapVoid>> = new Map(); static var loadingCameras: MapVoid>> = new Map(); static var loadingMaterials: MapVoid>> = new Map(); static var loadingParticles: MapVoid>> = new Map(); static var loadingWorlds: MapVoid>> = new Map(); static var loadingShaders: MapVoid>> = new Map(); static var loadingSceneRaws: MapVoid>> = new Map(); #if rp_probes static var loadingProbes: MapVoid>> = new Map(); #end static var loadingBlobs: MapVoid>> = new Map(); static var loadingImages: MapVoid>> = new Map(); #if lnx_audio static var loadingSounds: MapVoid>> = new Map(); #end static var loadingVideos: MapVoid>> = new Map(); static var loadingFonts: MapVoid>> = new Map(); #if krom_windows public static inline var sep = "\\"; #else public static inline var sep = "/"; #end #if lnx_data_dir #if krom_android public static var dataPath = "data" + sep; #else public static var dataPath = "." + sep + "data" + sep; #end #else public static var dataPath = ""; #end public function new() {} public static function deleteAll() { for (c in cachedMeshes) c.delete(); cachedMeshes = new Map(); for (c in cachedShaders) c.delete(); cachedShaders = new Map(); cachedSceneRaws = new Map(); cachedLights = new Map(); cachedCameras = new Map(); cachedMaterials = new Map(); cachedParticles = new Map(); cachedWorlds = new Map(); if (RenderPath.active != null) RenderPath.active.unload(); for (c in cachedBlobs) c.unload(); cachedBlobs = new Map(); for (c in cachedImages) c.unload(); cachedImages = new Map(); #if lnx_audio for (c in cachedSounds) c.unload(); cachedSounds = new Map(); #end for (c in cachedVideos) c.unload(); cachedVideos = new Map(); for (c in cachedFonts) c.unload(); cachedFonts = new Map(); } public static function getMesh(file: String, name: String, done: MeshData->Void) { var handle = file + name; var cached = cachedMeshes.get(handle); if (cached != null) { done(cached); return; } var loading = loadingMeshes.get(handle); if (loading != null) { loading.push(done); return; } loadingMeshes.set(handle, [done]); MeshData.parse(file, name, function(b: MeshData) { cachedMeshes.set(handle, b); b.handle = handle; for (f in loadingMeshes.get(handle)) f(b); loadingMeshes.remove(handle); }); } public static function deleteMesh(handle: String) { // Remove cached mesh var mesh = cachedMeshes.get(handle); if (mesh == null) return; mesh.delete(); cachedMeshes.remove(handle); } public static function getLight(file: String, name: String, done: LightData->Void) { var handle = file + name; var cached = cachedLights.get(handle); if (cached != null) { done(cached); return; } var loading = loadingLights.get(handle); if (loading != null) { loading.push(done); return; } loadingLights.set(handle, [done]); LightData.parse(file, name, function(b: LightData) { cachedLights.set(handle, b); for (f in loadingLights.get(handle)) f(b); loadingLights.remove(handle); }); } #if rp_probes public static function getProbe(file: String, name: String, done: ProbeData->Void) { var handle = file + name; var cached = cachedProbes.get(handle); if (cached != null) { done(cached); return; } var loading = loadingProbes.get(handle); if (loading != null) { loading.push(done); return; } loadingProbes.set(handle, [done]); ProbeData.parse(file, name, function(b: ProbeData) { cachedProbes.set(handle, b); for (f in loadingProbes.get(handle)) f(b); loadingProbes.remove(handle); }); } #end public static function getCamera(file: String, name: String, done: CameraData->Void) { var handle = file + name; var cached = cachedCameras.get(handle); if (cached != null) { done(cached); return; } var loading = loadingCameras.get(handle); if (loading != null) { loading.push(done); return; } loadingCameras.set(handle, [done]); CameraData.parse(file, name, function(b: CameraData) { cachedCameras.set(handle, b); for (f in loadingCameras.get(handle)) f(b); loadingCameras.remove(handle); }); } public static function getMaterial(file: String, name: String, done: MaterialData->Void) { var handle = file + name; var cached = cachedMaterials.get(handle); if (cached != null) { done(cached); return; } var loading = loadingMaterials.get(handle); if (loading != null) { loading.push(done); return; } loadingMaterials.set(handle, [done]); MaterialData.parse(file, name, function(b: MaterialData) { cachedMaterials.set(handle, b); for (f in loadingMaterials.get(handle)) f(b); loadingMaterials.remove(handle); }); } public static function getParticle(file: String, name: String, done: ParticleData->Void) { var handle = file + name; var cached = cachedParticles.get(handle); if (cached != null) { done(cached); return; } var loading = loadingParticles.get(handle); if (loading != null) { loading.push(done); return; } loadingParticles.set(handle, [done]); ParticleData.parse(file, name, function(b: ParticleData) { cachedParticles.set(handle, b); for (f in loadingParticles.get(handle)) f(b); loadingParticles.remove(handle); }); } public static function getWorld(file: String, name: String, done: WorldData->Void) { if (name == null) { // No world defined in scene done(null); return; } var handle = file + name; var cached = cachedWorlds.get(handle); if (cached != null) { done(cached); return; } var loading = loadingWorlds.get(handle); if (loading != null) { loading.push(done); return; } loadingWorlds.set(handle, [done]); WorldData.parse(file, name, function(b: WorldData) { cachedWorlds.set(handle, b); for (f in loadingWorlds.get(handle)) f(b); loadingWorlds.remove(handle); }); } public static function getShader(file: String, name: String, done: ShaderData->Void, overrideContext: TShaderOverride = null) { // Only one context override per shader data for now var cacheName = name; if (overrideContext != null) cacheName += "2"; var cached = cachedShaders.get(cacheName); // Shader must have unique name if (cached != null) { done(cached); return; } var loading = loadingShaders.get(cacheName); if (loading != null) { loading.push(done); return; } loadingShaders.set(cacheName, [done]); ShaderData.parse(file, name, function(b: ShaderData) { cachedShaders.set(cacheName, b); for (f in loadingShaders.get(cacheName)) f(b); loadingShaders.remove(cacheName); }, overrideContext); } public static function getSceneRaw(file: String, done: TSceneFormat->Void) { var cached = cachedSceneRaws.get(file); if (cached != null) { done(cached); return; } var loading = loadingSceneRaws.get(file); if (loading != null) { loading.push(done); return; } loadingSceneRaws.set(file, [done]); // If no extension specified, set to .arm var compressed = file.endsWith(".lz4"); var isJson = file.endsWith(".json"); var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx"; getBlob(file + ext, function(b: kha.Blob) { var parsed: TSceneFormat = null; #if lnx_compress if (compressed) { var bytes = b.toBytes(); // First 8 bytes contain data size for decoding var packedSize = haxe.Int64.toInt(bytes.getInt64(0)); parsed = LnxPack.decode(Lz4.decode(bytes.sub(8, bytes.length - 8), packedSize)); } else #end if (isJson) { var s = b.toString(); parsed = s.charAt(0) == "{" ? Json.parse(s) : LnxPack.decode(b.toBytes()); } else { parsed = LnxPack.decode(b.toBytes()); } returnSceneRaw(file, parsed); }); } static function returnSceneRaw(file: String, parsed: TSceneFormat) { cachedSceneRaws.set(file, parsed); for (f in loadingSceneRaws.get(file)) f(parsed); loadingSceneRaws.remove(file); } public static function getMeshRawByName(datas: Array, name: String): TMeshData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } public static function getLightRawByName(datas: Array, name: String): TLightData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } #if rp_probes public static function getProbeRawByName(datas: Array, name: String): TProbeData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } #end public static function getCameraRawByName(datas: Array, name: String): TCameraData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } public static function getMaterialRawByName(datas: Array, name: String): TMaterialData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } public static function getParticleRawByName(datas: Array, name: String): TParticleData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } public static function getWorldRawByName(datas: Array, name: String): TWorldData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } public static function getShaderRawByName(datas: Array, name: String): TShaderData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } #if lnx_audio public static function getSpeakerRawByName(datas: Array, name: String): TSpeakerData { if (name == "") return datas[0]; for (dat in datas) if (dat.name == name) return dat; return null; } #end // Raw assets public static function getBlob(file: String, done: kha.Blob->Void) { var cached = cachedBlobs.get(file); // Is already cached if (cached != null) { done(cached); return; } var loading = loadingBlobs.get(file); // Is already being loaded if (loading != null) { loading.push(done); return; } loadingBlobs.set(file, [done]); // Start loading kha.Assets.loadBlobFromPath(resolvePath(file), function(b: kha.Blob) { cachedBlobs.set(file, b); for (f in loadingBlobs.get(file)) f(b); loadingBlobs.remove(file); assetsLoaded++; }); } public static function deleteBlob(handle: String) { var blob = cachedBlobs.get(handle); if (blob == null) return; blob.unload(); cachedBlobs.remove(handle); } public static function getImage(file: String, done: kha.Image->Void, readable = false, format = "RGBA32") { #if (cpp || hl || lnx_use_k_images) if (!file.endsWith(".k")) { file = file.substring(0, file.length - 4) + ".k"; } #end var cached = cachedImages.get(file); if (cached != null) { done(cached); return; } var loading = loadingImages.get(file); if (loading != null) { loading.push(done); return; } loadingImages.set(file, [done]); #if lnx_image_embed var imageBlob = cachedBlobs.get(file); if (imageBlob != null) { kha.Image.fromEncodedBytes(imageBlob.bytes, ".k", function(b: kha.Image) { cachedImages.set(file, b); for (f in loadingImages.get(file)) f(b); loadingImages.remove(file); assetsLoaded++; }, null, readable); return; } #end kha.Assets.loadImageFromPath(resolvePath(file), readable, function(b: kha.Image) { cachedImages.set(file, b); for (f in loadingImages.get(file)) f(b); loadingImages.remove(file); assetsLoaded++; }); } public static function deleteImage(handle: String) { var image = cachedImages.get(handle); if (image == null) return; image.unload(); cachedImages.remove(handle); } /** Load sound file from disk into ram. @param file A String matching the file name of the sound file on disk. @param done Completion handler function to do something after the sound is loaded. */ #if lnx_audio public static function getSound(file: String, done: kha.Sound->Void) { #if lnx_soundcompress if (file.endsWith(".wav")) file = file.substring(0, file.length - 4) + ".ogg"; #end var cached = cachedSounds.get(file); if (cached != null) { done(cached); return; } var loading = loadingSounds.get(file); if (loading != null) { loading.push(done); return; } loadingSounds.set(file, [done]); kha.Assets.loadSoundFromPath(resolvePath(file), function(b: kha.Sound) { #if lnx_soundcompress b.uncompress(function () { #end cachedSounds.set(file, b); for (f in loadingSounds.get(file)) f(b); loadingSounds.remove(file); assetsLoaded++; #if lnx_soundcompress }); #end }); } public static function deleteSound(handle: String) { var sound = cachedSounds.get(handle); if (sound == null) return; sound.unload(); cachedSounds.remove(handle); } #end // lnx_audio public static function getVideo(file: String, done: kha.Video->Void) { #if (cpp || hl) file = file.substring(0, file.length - 4) + ".avi"; #elseif krom file = file.substring(0, file.length - 4) + ".webm"; #end var cached = cachedVideos.get(file); if (cached != null) { done(cached); return; } var loading = loadingVideos.get(file); if (loading != null) { loading.push(done); return; } loadingVideos.set(file, [done]); kha.Assets.loadVideoFromPath(resolvePath(file), function(b: kha.Video) { cachedVideos.set(file, b); for (f in loadingVideos.get(file)) f(b); loadingVideos.remove(file); assetsLoaded++; }); } public static function deleteVideo(handle: String) { var video = cachedVideos.get(handle); if (video == null) return; video.unload(); cachedVideos.remove(handle); } public static function getFont(file: String, done: kha.Font->Void) { var cached = cachedFonts.get(file); if (cached != null) { done(cached); return; } var loading = loadingFonts.get(file); if (loading != null) { loading.push(done); return; } loadingFonts.set(file, [done]); kha.Assets.loadFontFromPath(resolvePath(file), function(b: kha.Font) { cachedFonts.set(file, b); for (f in loadingFonts.get(file)) f(b); loadingFonts.remove(file); assetsLoaded++; }); } public static function deleteFont(handle: String) { var font = cachedFonts.get(handle); if (font == null) return; font.unload(); cachedFonts.remove(handle); } public static function isAbsolute(file: String): Bool { return file.charAt(0) == "/" || file.charAt(1) == ":" || file.charAt(4) == ":" || (file.charAt(0) == "\\" && file.charAt(1) == "\\"); } static inline function isUp(file: String): Bool { return file.charAt(0) == "." && file.charAt(1) == "."; } /** Extract filename from path. */ static inline function baseName(path: String): String { var slash = path.lastIndexOf(sep); return slash >= 0 ? path.substr(slash + 1) : path; } static inline function resolvePath(file: String): String { if (isAbsolute(file) || isUp(file)) return file; #if lnx_data_dir return dataPath + file; #else return baseName(file); #end } }