Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue #35 by writing FlashEmbeddedAssetPackLoader and integrating hxswfml #267

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added bin/hxswfml.n
Binary file not shown.
6 changes: 6 additions & 0 deletions command/data/scaffold/flambe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ default_platform: flash
orientation: portrait
fullscreen: true

# Whether to embed assets in to SWF's. If not, assets will be loaded over HTTP.
embed_assets: false

width: 640
height: 480

# Additional flags to pass to the Haxe compiler.
# haxe_flags: -lib nape -D foobar

Expand Down
106 changes: 91 additions & 15 deletions command/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ var os = require("os");
var path = require("path");
var spawn = require("child_process").spawn;
var wrench = require("wrench");
var xmldom = require("xmldom");

var DATA_DIR = __dirname + "/data/";
var BIN_DIR = __dirname + "/../bin/";
var CACHE_DIR = "build/.cache/";

var HAXE_COMPILER_PORT = 6000;
Expand Down Expand Up @@ -154,18 +156,88 @@ exports.build = function (config, platforms, opts) {
return Q();
};

var prepareAssets = function (dest) {
var generateAssetXml = function (relAssetDir, hxswfmlDoc) {
forEachFileIn(relAssetDir, function (file) {
var filePath = relAssetDir + "/" + file;
if(fs.lstatSync(filePath).isDirectory()) {
generateAssetXml(filePath, hxswfmlDoc);
} else {
var dataType = "";
var extension = filePath.substring(filePath.lastIndexOf(".") + 1).toLowerCase();
switch (extension) {
case "png":
case "jpg":
case "jpeg":
case "jxr":
case "gif":
dataType = "bitmap";
break;
case "mp3":
dataType = "sound";
break;
default:
dataType = "bytearray";
break;
}

var el = hxswfmlDoc.createElement(dataType);
el.setAttribute("file", filePath);
// create a valid AS3 class name from the asset url by replacing all non digits, non word characters, and leading non-letters with dollar signs
var className = filePath.substring(filePath.indexOf("/") + 1, filePath.lastIndexOf(".")).replace(/[^\d|\w|\$]|^[^A-za-z]/g, '$');
el.setAttribute("class", className);
hxswfmlDoc.documentElement.appendChild(el);
}
});
};

var prepareEmbeddedAssetLibrary = function () {
var hxswfmlDoc = new xmldom.DOMParser().parseFromString("<lib></lib>");
hxswfmlDoc.documentElement.setAttribute("width", get(config, "width"));
hxswfmlDoc.documentElement.setAttribute("height", get(config, "height"));
assetPaths.forEach(function (assetPath) {
generateAssetXml(assetPath, hxswfmlDoc);
});
hxswfmlDoc.documentElement.appendChild(hxswfmlDoc.createElement("frame"));

var xmlPath = CACHE_DIR+"swf/hxswfml_asset_lib_def.xml";
fs.writeFileSync(xmlPath, new xmldom.XMLSerializer().serializeToString(hxswfmlDoc));

return hxswfml(["xml2lib", xmlPath, "libs/library.swf"]);
};

var prepareAssets = function (dest, platform) {
var assetFlags = ["--macro", "flambe.platform.ManifestBuilder.use(\""+dest+"\")"];

wrench.rmdirSyncRecursive(dest, true);
if (fs.existsSync("libs/library.swf")) {
fs.unlinkSync("libs/library.swf");
}

// TODO(bruno): Filter out certain formats based on the platform
return copyDirs(assetPaths, dest)
.then(function () {
return ["--macro", "flambe.platform.ManifestBuilder.use(\""+dest+"\")"];
var promise = copyDirs(assetPaths, dest);
if (platform == "flash" && get(config, "embed_assets")) {
wrench.mkdirSyncRecursive(CACHE_DIR+"swf");
assetFlags.push("-D", "embed_assets");

promise = promise.then(function () {
return prepareEmbeddedAssetLibrary()
});
}

return promise.then(function () {
return assetFlags;
});
};

var swfFlags = function (air) {
// Flags common to all swf-based targets (flash, android, ios)
var flags = ["--flash-strict", "-swf-header", "640:480:60:000000"];
var flags = ["--flash-strict"];
if (!get(config, "embed_assets")) {
// when assets are embedded, we take the header from their swf library so that
// we can also absorb their timeline with -D flash-use-stage, which is incompatible with -swf-header
var swfHeader = get(config, "width") + ":" + get(config, "height") + ":60:000000";
flags.push("-swf-header", swfHeader);
}
if (debug) flags.push("-D", "fdb", "-D", "advanced-telemetry");
else flags.push("-D", "native_trace");

Expand Down Expand Up @@ -223,7 +295,7 @@ exports.build = function (config, platforms, opts) {
var outputDir = "build/web";

return prepareWeb(outputDir)
.then(function () { return prepareAssets(outputDir+"/assets") })
.then(function () { return prepareAssets(outputDir+"/assets", "html") })
.then(function (assetFlags) {
console.log("Building: " + outputDir);
return buildJS({
Expand All @@ -236,29 +308,28 @@ exports.build = function (config, platforms, opts) {

var buildFlash = function () {
var swf = "build/web/targets/main-flash.swf";
var flashFlags = swfFlags(false).concat([
"-swf-version", SWF_VERSION, "-swf", swf]);

return prepareWeb()
.then(function () { return prepareAssets("build/web/assets") })
.then(function () { return prepareAssets("build/web/assets", "flash") })
.then(function (assetFlags) {
console.log("Building: " + swf);
var flashFlags = swfFlags(false).concat([
"-swf-version", SWF_VERSION, "-swf", swf]);
return haxe(commonFlags.concat(assetFlags).concat(flashFlags));
});
};

var buildAir = function (flags) {
var buildAir = function (flags, platform) {
var airFlags = swfFlags(true).concat(["-swf-version", "11.7", "-D", "air"]);

wrench.mkdirSyncRecursive(CACHE_DIR+"air");
return prepareAssets(CACHE_DIR+"air/assets")
return prepareAssets(CACHE_DIR+"air/assets", platform)
.then(function (assetFlags) {
return haxe(commonFlags.concat(assetFlags).concat(airFlags).concat(flags));
});
};

var generateAirXml = function (swf, output) {
var xmldom = require("xmldom");
var xml =
"<application xmlns=\"http://ns.adobe.com/air/application/4.0\">\n" +
" <id>"+get(config, "id")+"</id>\n" +
Expand Down Expand Up @@ -349,7 +420,7 @@ exports.build = function (config, platforms, opts) {
var cert = CACHE_DIR+"air/certificate-android.p12";
var xml = CACHE_DIR+"air/config-android.xml";

return buildAir(["-D", "android", "-swf", CACHE_DIR+"air/"+swf])
return buildAir(["-D", "android", "-swf", CACHE_DIR+"air/"+swf], "android")
.then(function () {
// Generate a dummy certificate if it doesn't exist
if (!fs.existsSync(cert)) {
Expand Down Expand Up @@ -387,7 +458,7 @@ exports.build = function (config, platforms, opts) {
var mobileProvision = "certs/ios.mobileprovision";
var xml = CACHE_DIR+"air/config-ios.xml";

return buildAir(["-D", "ios", "-D", "no-flash-override", "-swf", CACHE_DIR+"air/"+swf])
return buildAir(["-D", "ios", "-D", "no-flash-override", "-swf", CACHE_DIR+"air/"+swf], "ios")
.then(function () {
var pathOptions = generateAirXml(swf, xml);

Expand All @@ -414,7 +485,7 @@ exports.build = function (config, platforms, opts) {
var outputDir = "build/firefox";
wrench.mkdirSyncRecursive(outputDir+"/targets");

return prepareAssets(outputDir+"/assets")
return prepareAssets(outputDir+"/assets", "firefox")
.then(function (assetFlags) {
console.log("Building: " + outputDir);
return buildJS({
Expand Down Expand Up @@ -563,6 +634,11 @@ var haxelib = function (flags, opts) {
};
exports.haxelib = haxelib;

var hxswfml = function (flags, opts) {
return exec("neko " + BIN_DIR + "hxswfml.n", flags, opts);
};
exports.hxswfml = hxswfml;

var adt = function (flags, opts) {
return exec("adt", flags, opts);
};
Expand Down
78 changes: 78 additions & 0 deletions src/flambe/platform/flash/FlashEmbeddedAssetPackLoader.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// Flambe - Rapid game development
// https://github.com/aduros/flambe/blob/master/LICENSE.txt

package flambe.platform.flash;

import flambe.display.Texture;
import flash.display.Bitmap;
import flash.display.Loader;
import flash.errors.Error;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.filesystem.File;
import flash.media.Sound;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.Capabilities;
import flash.system.LoaderContext;
import flash.utils.ByteArray;

import flambe.asset.AssetEntry;
import flambe.asset.Manifest;
import flambe.util.Assert;

class FlashEmbeddedAssetPackLoader extends BasicAssetPackLoader
{
public function new (platform :FlashPlatform, manifest :Manifest)
{
super(platform, manifest);
}

override private function loadEntry (url :String, entry :AssetEntry)
{
var asset : Dynamic;
// create a valid AS3 class name from the asset url, replace all non digits, non word characters, and leading non-letters with dollar signs
var className = ~/[^\d|\w|\$]|^[^A-za-z]/g.replace(url.substring(url.indexOf("/") + 1, url.lastIndexOf(".")), "$");

if (Type.resolveClass(className) != null) {
var resInst = Type.createInstance(Type.resolveClass(className), []); // an instance of the resource

switch (entry.format) {
case JXR, PNG, JPG, GIF:
asset = _platform.getRenderer().createTextureFromImage(resInst.bitmapData);
resInst.bitmapData.dispose();

case MP3:
var sound :Sound = cast resInst;
asset = new FlashSound(sound);

case Data:
var data :ByteArray = cast resInst;
asset = new BasicFile(data.toString());

default:
// Should never happen
Assert.fail("Unsupported format", ["format", entry.format]);
return;
}

if (asset != null) {
handleLoad(entry, asset);
} else {
// Assume this is a failed createTexture()
trace("failed to create texture...");
handleTextureError(entry);
}
}
}

override private function getAssetFormats (fn :Array<AssetFormat> -> Void)
{
fn([JXR, PNG, JPG, GIF, MP3, Data]);
}
}
6 changes: 5 additions & 1 deletion src/flambe/platform/flash/FlashPlatform.hx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ class FlashPlatform

public function loadAssetPack (manifest :Manifest) :Promise<AssetPack>
{
return new FlashAssetPackLoader(this, manifest).promise;
#if embed_assets
return new FlashEmbeddedAssetPackLoader(this, manifest).promise;
#else
return new FlashAssetPackLoader(this, manifest).promise;
#end
}

public function getStage () :StageSystem
Expand Down