Files
DSTEngine/standalone-openfl-app/src/macros/AssetMacro.hx

339 lines
12 KiB
Haxe

package macros;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
import sys.FileSystem;
import sys.io.File;
import haxe.Json;
using StringTools;
/**
* Macro for processing @:Assets meta tags and building asset manifests
*/
@:keep
class AssetMacro {
// Track all assets processed
private static var assetMap:Map<String, Array<{id:String, path:String, size:Int, type:String}>> = new Map();
/** Force Assets to be typed, which invokes its @:build macro. */
public static function includeAssets():Void {
Context.getType("Assets");
}
/**
* Entry point for the macro - called during compilation
*/
public static function build():Array<Field> {
#if macro
Sys.println("AssetMacro.build() called - macro is running");
Context.warning("AssetMacro is running", Context.currentPos());
#end
// A compilation server can retain macro statics between builds.
assetMap = new Map();
// Get current build context
var fields = Context.getBuildFields();
var localClass = Context.getLocalClass().get();
#if macro
Sys.println("Processing class: " + localClass.name);
#end
// Get the output directory from build.hxml
var outputDir = getOutputDir();
var manifestDir = outputDir + "/manifest";
var assetsOutputDir = outputDir + "/assets";
// These directories are owned by this macro. Recreate them so removed or
// renamed source assets cannot survive as stale build output.
removeDirectory(manifestDir);
removeDirectory(assetsOutputDir);
createDirectory(manifestDir);
createDirectory(assetsOutputDir);
copyLimeNativeLibrary(outputDir);
// Process asset fields
for (field in fields) {
processField(field, assetsOutputDir);
}
// Build the manifest files
buildManifests(manifestDir);
return fields;
}
/**
* Process a field for @:Assets meta
*/
private static function processField(field:Field, assetsOutputDir:String) {
// Look for our custom meta
var assetsMeta = findAssetsMeta(field.meta);
if (assetsMeta == null) return;
var params = assetsMeta.params;
if (params.length < 2) {
Context.error("@:Assets meta requires at least 2 parameters: source path and library name", assetsMeta.pos);
return;
}
// Extract parameters
var sourcePath = extractString(params[0]);
var libraryName = extractString(params[1]);
var targetPath = params.length > 2 ? extractString(params[2]) : null;
// Process directory or single file
if (FileSystem.exists(sourcePath)) {
if (FileSystem.isDirectory(sourcePath)) {
processDirectory(sourcePath, libraryName, assetsOutputDir);
} else {
processSingleFile(sourcePath, libraryName, targetPath, assetsOutputDir);
}
} else {
Context.warning('Asset source not found: ${sourcePath}', field.pos);
}
}
/**
* Process a directory of assets
*/
private static function processDirectory(sourcePath:String, libraryName:String, assetsOutputDir:String) {
if (!sourcePath.endsWith("/")) sourcePath += "/";
var targetDir = assetsOutputDir + "/" + libraryName + "/";
createDirectory(targetDir);
function scanDir(path:String, relPath:String = "") {
if (!FileSystem.exists(path)) return;
for (entry in FileSystem.readDirectory(path)) {
var fullPath = path + entry;
var relAssetPath = relPath + entry;
if (FileSystem.isDirectory(fullPath)) {
var newRelPath = relPath + entry + "/";
var newTargetDir = targetDir + newRelPath;
createDirectory(newTargetDir);
scanDir(fullPath + "/", newRelPath);
} else {
// Copy file and register in asset map
var targetPath = targetDir + relAssetPath;
try {
File.copy(fullPath, targetPath);
// Register asset
if (!assetMap.exists(libraryName)) {
assetMap.set(libraryName, []);
}
assetMap.get(libraryName).push({
id: relAssetPath,
path: relAssetPath,
size: FileSystem.stat(fullPath).size,
type: getAssetType(fullPath)
});
} catch (e) {
Context.warning('Failed to copy asset: ${fullPath} -> ${targetPath}. Error: ${e}', Context.currentPos());
}
}
}
}
scanDir(sourcePath);
}
/**
* Process a single file asset
*/
private static function processSingleFile(sourcePath:String, libraryName:String, targetPath:String, assetsOutputDir:String) {
var targetDir = assetsOutputDir + "/" + libraryName + "/";
createDirectory(targetDir);
var fileName = targetPath != null ? targetPath : sourcePath.split("/").pop();
var destPath = targetDir + fileName;
try {
File.copy(sourcePath, destPath);
// Register asset
if (!assetMap.exists(libraryName)) {
assetMap.set(libraryName, []);
}
assetMap.get(libraryName).push({
id: fileName,
path: fileName,
size: FileSystem.stat(sourcePath).size,
type: getAssetType(sourcePath)
});
} catch (e) {
Context.warning('Failed to copy asset: ${sourcePath} -> ${destPath}. Error: ${e}', Context.currentPos());
}
}
/**
* Build manifest JSON files for each library
*/
private static function buildManifests(manifestDir:String) {
// Create an array to hold all assets for the default manifest
var allAssets = [];
// First, process individual library manifests
for (library in assetMap.keys()) {
var assets = assetMap.get(library);
var manifest = {
name: library,
assets: assets,
rootPath: "../assets/" + library + "/"
};
// Add assets to combined collection with library prefix
for (asset in assets) {
allAssets.push({
id: library + "/" + asset.id,
path: library + "/" + asset.path,
size: asset.size,
type: asset.type
});
}
var manifestPath = manifestDir + "/" + library + ".json";
try {
var content = Json.stringify(manifest, null, " ");
File.saveContent(manifestPath, content);
Context.info('Built manifest: ${manifestPath} with ${assets.length} assets', Context.currentPos());
} catch (e) {
Context.warning('Failed to write manifest: ${manifestPath}. Error: ${e}', Context.currentPos());
}
}
// Create the default manifest with all assets
var defaultManifest = {
name: "default",
assets: allAssets,
rootPath: "../assets/"
};
// Save the default manifest
var defaultManifestPath = manifestDir + "/default.json";
try {
var content = Json.stringify(defaultManifest, null, " ");
File.saveContent(defaultManifestPath, content);
Context.info('Built default manifest: ${defaultManifestPath} with ${allAssets.length} assets', Context.currentPos());
} catch (e) {
Context.warning('Failed to write default manifest: ${defaultManifestPath}. Error: ${e}', Context.currentPos());
}
}
// Helper functions
private static function getOutputDir():String {
// Try to parse the --cpp argument from the build.hxml
var args = Sys.args();
for (i in 0...args.length) {
if (args[i] == "--cpp" && i < args.length - 1) {
return args[i + 1];
}
}
return "bin/cpp"; // Default output directory
}
private static function createDirectory(path:String) {
if (!FileSystem.exists(path)) {
try {
FileSystem.createDirectory(path);
} catch (e) {
Context.warning('Failed to create directory: ${path}. Error: ${e}', Context.currentPos());
}
}
}
private static function removeDirectory(path:String):Void {
if (!FileSystem.exists(path)) return;
for (entry in FileSystem.readDirectory(path)) {
var entryPath = path + "/" + entry;
if (FileSystem.isDirectory(entryPath)) {
removeDirectory(entryPath);
} else {
FileSystem.deleteFile(entryPath);
}
}
FileSystem.deleteDirectory(path);
}
/** Stage the Lime CFFI module that matches the selected haxelib version. */
private static function copyLimeNativeLibrary(outputDir:String):Void {
var platformDirectory = if (Context.defined("linux")) {
"Linux64";
} else if (Context.defined("windows")) {
"Windows64";
} else if (Context.defined("mac")) {
Context.defined("arm64") ? "MacArm64" : "Mac64";
} else {
return;
}
var limeRoot:String = null;
for (classPath in Context.getClassPath()) {
var normalized = classPath.split("\\").join("/");
if (normalized.indexOf("/lime/") != -1 && normalized.endsWith("/src/")) {
limeRoot = normalized.substr(0, normalized.length - 4);
break;
}
}
if (limeRoot == null) {
Context.warning("Could not locate Lime native library from the compiler class path", Context.currentPos());
return;
}
var source = limeRoot + "ndll/" + platformDirectory + "/lime.ndll";
if (!FileSystem.exists(source) && platformDirectory.endsWith("64")) {
platformDirectory = platformDirectory.substr(0, platformDirectory.length - 2);
source = limeRoot + "ndll/" + platformDirectory + "/lime.ndll";
}
if (!FileSystem.exists(source)) {
Context.warning("Lime native library not found: " + source, Context.currentPos());
return;
}
try {
File.copy(source, outputDir + "/lime.ndll");
} catch (e:Dynamic) {
Context.error("Failed to copy Lime native library: " + e, Context.currentPos());
}
}
private static function findAssetsMeta(metaAccess:Array<MetadataEntry>):MetadataEntry {
if (metaAccess == null) return null;
for (meta in metaAccess) {
if (meta.name == ":Assets" || meta.name == "Assets") {
return meta;
}
}
return null;
}
private static function extractString(expr:Expr):String {
switch (expr.expr) {
case EConst(CString(s)): return s;
default: Context.error("Expected string literal", expr.pos);
}
return null;
}
private static function getAssetType(path:String):String {
var ext = path.split(".").pop().toLowerCase();
return (switch (ext) {
case "jpg", "jpeg", "png", "gif", "bmp": "image";
case "mp3", "ogg", "wav": "sound";
case "ttf", "otf": "font";
case "json", "xml", "txt": "text";
default: "binary";
}).toUpperCase();
}
}
#end