Files
lime/src/lime/utils/PackedAssetLibrary.hx
2022-09-03 15:22:26 -04:00

462 lines
11 KiB
Haxe

package lime.utils;
import haxe.io.Bytes;
import haxe.io.Path;
import lime.app.Event;
import lime.app.Future;
import lime.app.Promise;
import lime.media.AudioBuffer;
import lime.graphics.Image;
import lime.net.HTTPRequest;
import lime.text.Font;
import lime.utils.AssetType;
import lime.utils.Bytes;
#if flash
import flash.display.BitmapData;
import flash.media.Sound;
#end
#if !lime_debug
@:fileXml('tags="haxe,release"')
@:noDebug
#end
@:keep class PackedAssetLibrary extends AssetLibrary
{
@:noCompletion private var id:String;
@:noCompletion private var lengths = new Map<String, Int>();
@:noCompletion private var packedData:Bytes;
@:noCompletion private var positions = new Map<String, Int>();
@:noCompletion private var type:String;
@:noCompletion private var rootPath:String;
public function new(id:String, type:String)
{
super();
this.id = id;
this.type = type;
}
public static function fromBytes(bytes:Bytes, rootPath:String = null):PackedAssetLibrary
{
return cast fromManifest(AssetManifest.fromBytes(bytes, rootPath));
}
public static function fromFile(path:String, rootPath:String = null):PackedAssetLibrary
{
return cast fromManifest(AssetManifest.fromFile(path, rootPath));
}
public static function fromManifest(manifest:AssetManifest):PackedAssetLibrary
{
return cast AssetLibrary.fromManifest(manifest);
}
public override function getAudioBuffer(id:String):AudioBuffer
{
#if (js && html5)
return super.getAudioBuffer(id);
#else
if (cachedAudioBuffers.exists(id))
{
return cachedAudioBuffers.get(id);
}
else
{
// TODO: More efficient method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return AudioBuffer.fromBytes(bytes);
}
#end
}
public override function getBytes(id:String):Bytes
{
if (cachedBytes.exists(id))
{
return cachedBytes.get(id);
}
else if (cachedText.exists(id))
{
var bytes = Bytes.ofString(cachedText.get(id));
cachedBytes.set(id, bytes);
return bytes;
}
else
{
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return bytes;
}
}
public override function getFont(id:String):Font
{
#if (js && html5)
return super.getFont(id);
#else
if (cachedFonts.exists(id))
{
return cachedFonts.get(id);
}
else
{
// TODO: More efficient method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Font.fromBytes(bytes);
}
#end
}
public override function getImage(id:String):Image
{
if (cachedImages.exists(id))
{
return cachedImages.get(id);
}
else
{
// TODO: More efficient method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Image.fromBytes(bytes);
}
}
public override function getText(id:String):String
{
if (cachedText.exists(id))
{
return cachedText.get(id);
}
else if (type == "gzip" || type == "zip" || type == "deflate")
{
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return bytes.getString(0, bytes.length);
}
else
{
return packedData.getString(positions[id], lengths[id]);
}
}
public override function isLocal(id:String, type:String):Bool
{
return true;
}
public override function load():Future<AssetLibrary>
{
if (loaded)
{
return Future.withValue(cast this);
}
if (promise == null)
{
promise = new Promise<AssetLibrary>();
// TODO: Handle `preload` for individual assets
// TODO: Do not preload bytes on native, if we can read from it instead (all non-Android targets?)
var packedData_onComplete = function(data:Bytes)
{
cachedBytes.set(id, data);
packedData = data;
assetsLoaded = 0;
assetsTotal = 1;
for (id in preload.keys())
{
if (!preload.get(id)) continue;
Log.verbose("Preloading asset: " + id + " [" + types.get(id) + "]");
switch (types.get(id))
{
case BINARY:
assetsTotal++;
var future = loadBytes(id);
// future.onProgress (load_onProgress.bind (id));
future.onError(load_onError.bind(id));
future.onComplete(loadBytes_onComplete.bind(id));
case FONT:
assetsTotal++;
var future = loadFont(id);
// future.onProgress (load_onProgress.bind (id));
future.onError(load_onError.bind(id));
future.onComplete(loadFont_onComplete.bind(id));
case IMAGE:
assetsTotal++;
var future = loadImage(id);
// future.onProgress (load_onProgress.bind (id));
future.onError(load_onError.bind(id));
future.onComplete(loadImage_onComplete.bind(id));
case MUSIC, SOUND:
assetsTotal++;
var future = loadAudioBuffer(id);
// future.onProgress (load_onProgress.bind (id));
future.onError(load_onError.bind(id));
future.onComplete(loadAudioBuffer_onComplete.bind(id));
case TEXT:
assetsTotal++;
var future = loadText(id);
// future.onProgress (load_onProgress.bind (id));
future.onError(load_onError.bind(id));
future.onComplete(loadText_onComplete.bind(id));
default:
}
}
__assetLoaded(null);
};
if (cachedBytes.exists(id))
{
packedData_onComplete(cachedBytes.get(id));
}
else
{
var basePath = rootPath == null || rootPath == "" ? "" : Path.addTrailingSlash(rootPath);
var libPath = paths.exists(id) ? paths.get(id) : id;
var path = Path.join([basePath, libPath]);
path = __cacheBreak(path);
Bytes.loadFromFile(path).onError(promise.error).onComplete(packedData_onComplete);
}
}
return promise.future;
}
public override function loadAudioBuffer(id:String):Future<AudioBuffer>
{
#if (js && html5)
return super.loadAudioBuffer(id);
#else
if (cachedAudioBuffers.exists(id))
{
return Future.withValue(cachedAudioBuffers.get(id));
}
else
{
// TODO: More efficient method, use `loadFromBytes` method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Future.withValue(AudioBuffer.fromBytes(bytes));
}
#end
}
public override function loadBytes(id:String):Future<Bytes>
{
if (cachedBytes.exists(id))
{
return Future.withValue(cachedBytes.get(id));
}
else
{
// TODO: More efficient method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Future.withValue(bytes);
}
}
public override function loadFont(id:String):Future<Font>
{
#if (js && html5)
return super.loadFont(id);
#else
if (cachedFonts.exists(id))
{
return Future.withValue(cachedFonts.get(id));
}
else
{
// TODO: More efficient method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Font.loadFromBytes(bytes);
}
#end
}
public static function loadFromBytes(bytes:Bytes, rootPath:String = null):Future<PackedAssetLibrary>
{
return AssetLibrary.loadFromBytes(bytes, rootPath).then(function(library)
{
var assetLibrary:PackedAssetLibrary = cast library;
return Future.withValue(assetLibrary);
});
}
public static function loadFromFile(path:String, rootPath:String = null):Future<PackedAssetLibrary>
{
return AssetLibrary.loadFromFile(path, rootPath).then(function(library)
{
var assetLibrary:PackedAssetLibrary = cast library;
return Future.withValue(assetLibrary);
});
}
public static function loadFromManifest(manifest:AssetManifest):Future<PackedAssetLibrary>
{
return AssetLibrary.loadFromManifest(manifest).then(function(library)
{
var assetLibrary:PackedAssetLibrary = cast library;
return Future.withValue(assetLibrary);
});
}
public override function loadImage(id:String):Future<Image>
{
if (cachedImages.exists(id))
{
return Future.withValue(cachedImages.get(id));
}
else
{
// TODO: More efficient method
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Image.loadFromBytes(bytes);
}
}
public override function loadText(id:String):Future<String>
{
if (cachedText.exists(id))
{
return Future.withValue(cachedText.get(id));
}
else if (cachedBytes.exists(id))
{
var bytes = getBytes(id);
if (bytes == null)
{
return cast Future.withValue(null);
}
else
{
var text = bytes.getString(0, bytes.length);
cachedText.set(id, text);
return Future.withValue(text);
}
}
else if (type == "gzip" || type == "deflate")
{
var bytes = Bytes.alloc(lengths[id]);
bytes.blit(0, packedData, positions[id], lengths[id]);
if (type == "gzip") bytes = bytes.decompress(GZIP);
else if (type == "zip" || type == "deflate") bytes = bytes.decompress(DEFLATE);
return Future.withValue(bytes.getString(0, bytes.length));
}
else
{
return Future.withValue(packedData.getString(positions[id], lengths[id]));
}
}
public override function unload():Void {}
@:noCompletion private override function __fromManifest(manifest:AssetManifest):Void
{
rootPath = manifest.rootPath;
super.__fromManifest(manifest);
for (asset in manifest.assets)
{
if (Reflect.hasField(asset, "position"))
{
positions.set(asset.id, Reflect.field(asset, "position"));
}
if (Reflect.hasField(asset, "length"))
{
lengths.set(asset.id, Reflect.field(asset, "length"));
}
}
}
@:noCompletion private override function __assetLoaded(id:String):Void
{
assetsLoaded++;
if (id != null)
{
Log.verbose("Loaded asset: " + id + " [" + types.get(id) + "] (" + (assetsLoaded - 1) + "/" + (assetsTotal - 1) + ")");
}
// if (id != null) {
// var size = sizes.get (id);
// if (!bytesLoadedCache.exists (id)) {
// bytesLoaded += size;
// } else {
// var cache = bytesLoadedCache.get (id);
// if (cache < size) {
// bytesLoaded += (size - cache);
// }
// }
// bytesLoadedCache.set (id, size);
// }
if (assetsLoaded < assetsTotal)
{
// promise.progress (bytesLoaded, bytesTotal);
}
else
{
loaded = true;
// promise.progress (bytesTotal, bytesTotal);
promise.complete(this);
}
}
}