How to properly load external assets (so like, not in the manifest.json) #3633
|
So I have a function that's supposed to load external assets, but it always returns // Contents of "source/utils/Utils.hx"
package utils;
import haxe.Json;
import haxe.io.Path;
import lime.app.Future;
import openfl.display.BitmapData;
import openfl.media.Sound;
import sys.FileSystem;
import sys.io.File;
// There are other util functions but for the sake of straight-forwardness, they are skipped
function parseAsset(assetPath:String):Null<Any>
{
if (!FileSystem.exists(assetPath))
return null;
trace('Loading asset at "$assetPath"...');
var ext:String = Path.extension(assetPath);
var result:Any;
switch (ext)
{
case "ogg", "wav", "mp3":
result = Sound.loadFromFile(assetPath);
case "png":
result = BitmapData.loadFromFile(assetPath);
case "xml":
result = Xml.parse(File.getContent(assetPath));
case "json":
result = Json.parse(File.getContent(assetPath));
case "txt":
result = File.getContent(assetPath);
default:
result = File.getBytes(assetPath);
}
if (result is Future)
{
var future:Future<Any> = result;
var completed:Bool = false;
future.onComplete(e ->
{
completed = true;
result = e;
});
future.onProgress((v, m) -> trace('Progress = ${Math.floor((v / m) * 100)}%'));
future.onError(e ->
{
completed = true;
result = e;
});
var timeout:Float = Sys.time() + 5.0;
while (!completed)
{
Sys.sleep(1 / 10);
var curtime:Float = Sys.time();
if (curtime > timeout)
{
trace("Timed Out");
break;
}
}
if (future.isError)
{
trace('Oh my fucking god what is it now?\n${result}');
return null;
}
return result;
}
return result;
} |
Replies: 3 comments 1 reply
|
also excuse the cursing near the end, I was getting really annoyed at the problem... |
|
Ok so it turns out I'm a dumbass and I had to screw the // Contents of "source/utils/Utils.hx"
package utils;
import haxe.Json;
import haxe.io.Path;
import openfl.display.BitmapData;
import openfl.media.Sound;
import sys.FileSystem;
import sys.io.File;
// There are other util functions but for the sake of straight-forwardness, they are skipped
function parseAsset(assetPath:String):Null<Any>
{
if (!FileSystem.exists(assetPath))
return null;
trace('Loading asset at "$assetPath"...');
var ext:String = Path.extension(assetPath);
var result:Any;
switch (ext)
{
case "ogg", "wav", "mp3":
result = Sound.fromFile(assetPath);
case "png":
result = BitmapData.fromFile(assetPath);
case "xml":
result = Xml.parse(File.getContent(assetPath));
case "json":
result = Json.parse(File.getContent(assetPath));
case "txt":
result = File.getContent(assetPath);
default:
result = File.getBytes(assetPath);
}
return result;
} |
|
The important detail is that your image/sound branches are not returning the loaded asset yet. Both OpenFL APIs you are using are asynchronous: BitmapData.loadFromFile(path):Future<BitmapData>
Sound.loadFromFile(path):Future<Sound>So while (!completed) Sys.sleep(...)is the fragile part. On some targets the completion callback is dispatched through the current thread/event loop, and your sleep loop can prevent the callback from being processed. On HTML5 this is especially the wrong shape, because file/media loading is inherently async. I would change the function to be async all the way through, for example: import openfl.utils.Future;
function parseAsset(assetPath:String):Future<Any>
{
if (!FileSystem.exists(assetPath))
return Future.withValue(null);
var ext = Path.extension(assetPath).toLowerCase();
return switch (ext) {
case "ogg" | "wav" | "mp3":
cast Sound.loadFromFile(assetPath);
case "png":
cast BitmapData.loadFromFile(assetPath);
case "xml":
Future.withValue(Xml.parse(File.getContent(assetPath)));
case "json":
Future.withValue(Json.parse(File.getContent(assetPath)));
case "txt":
Future.withValue(File.getContent(assetPath));
default:
Future.withValue(File.getBytes(assetPath));
}
}Then use it like: parseAsset(path)
.onComplete(asset -> {
trace(asset);
// add BitmapData/Sound to your own cache here
})
.onError(err -> trace(err));If you are only targeting native/sys and only loading local files, OpenFL also has synchronous APIs: BitmapData.fromFile(path)
Sound.fromFile(path)But those are not portable. The OpenFL source comments explicitly say the synchronous One extra check: normalize the extension with If this fixes the loading path, please mark it as the answer so other people hitting external asset loading can find the async pattern. |
The important detail is that your image/sound branches are not returning the loaded asset yet. Both OpenFL APIs you are using are asynchronous:
So
resultis aFuture, not aBitmapDataorSound. Trying to make that synchronous with:is the fragile part. On some targets the completion callback is dispatched through the current thread/event loop, and your sleep loop can prevent the callback from being processed. On HTML5 this is especially the wrong shape, because file/media loading is inherently async.
I would change the function to be async all the way through, for exam…