From 27c5503173e0327c8f366eeb6806737f66c1673f Mon Sep 17 00:00:00 2001 From: Andreas Schaafsma Date: Thu, 10 Apr 2025 03:19:07 +0200 Subject: [PATCH] Stable state --- .../CORE_PRINCIPLES_IMPORTANT!.md | 5 + .../IMPORTANT_INSTRUCTIONS.md | 22 + ...enFL_Build_Chain_Asset_System_Deep_Dive.md | 558 ++++++++++++++++++ .../assets/{ => data}/img/placeholder.txt | 0 standalone-openfl-app/build.hxml | 28 +- standalone-openfl-app/buildlog.txt | 7 + standalone-openfl-app/compile.sh | 9 +- standalone-openfl-app/manifest/default.json | 14 + standalone-openfl-app/src/ApplicationMain.hx | 157 ++--- standalone-openfl-app/src/Assets.hx | 263 ++++++++- .../src/EmbeddedAssetLibrary.hx | 87 +++ standalone-openfl-app/src/Main.hx | 118 +++- .../src/macros/AssetMacro.hx | 320 +++++++--- 13 files changed, 1351 insertions(+), 237 deletions(-) create mode 100644 standalone-openfl-app/CORE_PRINCIPLES_IMPORTANT!.md create mode 100644 standalone-openfl-app/IMPORTANT_INSTRUCTIONS.md create mode 100644 standalone-openfl-app/OpenFL_Build_Chain_Asset_System_Deep_Dive.md rename standalone-openfl-app/assets/{ => data}/img/placeholder.txt (100%) create mode 100644 standalone-openfl-app/buildlog.txt mode change 100644 => 100755 standalone-openfl-app/compile.sh create mode 100644 standalone-openfl-app/manifest/default.json create mode 100644 standalone-openfl-app/src/EmbeddedAssetLibrary.hx diff --git a/standalone-openfl-app/CORE_PRINCIPLES_IMPORTANT!.md b/standalone-openfl-app/CORE_PRINCIPLES_IMPORTANT!.md new file mode 100644 index 00000000..3c236549 --- /dev/null +++ b/standalone-openfl-app/CORE_PRINCIPLES_IMPORTANT!.md @@ -0,0 +1,5 @@ +# Never violate the following rules: + - Make the code work with the .hxml based workflow + - don't use compiletime `#if lime` or `#if openfl` as this relies on the openfl and lime build system. We don't use their buildsystems but try to do the same thing they do for the hxcpp based targets. (Native Windows/ Linux compilation) + - Adhere to the spec outlined in OpenFL_Build_Chain_Asset_System_Deep_dive.md and IMPORTANT_INSTRUCTIONS.md + \ No newline at end of file diff --git a/standalone-openfl-app/IMPORTANT_INSTRUCTIONS.md b/standalone-openfl-app/IMPORTANT_INSTRUCTIONS.md new file mode 100644 index 00000000..6dcb57f8 --- /dev/null +++ b/standalone-openfl-app/IMPORTANT_INSTRUCTIONS.md @@ -0,0 +1,22 @@ +## Important Development Instructions + +In this repository you will find 3 projects. + +One of them is in the root and src directories of this repo, this is the project we're actively working on. + +The other 2 are located at + +- ./lime-repo +- ./openfl-repo + +Instructions: + +Analyze the openfl repo, this project contains a build chain based on HXP that can be called from the commandline. We need to document how this buildchain operates when the following command is ran. + +`openfl test linux` + +We already know that it makes some calls to lime under the hood which has a similar build chain. + +The current project is built to create an openfl app using the hxml build system and bypass lime's/ openfl's build system entirely. + +We are currently working on asset bundling, this is proving to be a difficult task. Please document how openfl does this in a file called BUILD_CHAIN_DOCUMENTATION_OPENFL.md \ No newline at end of file diff --git a/standalone-openfl-app/OpenFL_Build_Chain_Asset_System_Deep_Dive.md b/standalone-openfl-app/OpenFL_Build_Chain_Asset_System_Deep_Dive.md new file mode 100644 index 00000000..7908e284 --- /dev/null +++ b/standalone-openfl-app/OpenFL_Build_Chain_Asset_System_Deep_Dive.md @@ -0,0 +1,558 @@ +# OpenFL Build Chain: Asset System Deep Dive + +This document provides an in-depth analysis of OpenFL's asset handling system, focusing on asset processing, the runtime access layer, and resource management. This information is intended to help implement a custom asset bundling system that bypasses the standard OpenFL/Lime build toolchain. + +## 1. Asset Processing System + +OpenFL's asset processing pipeline transforms source files into platform-optimized assets through several stages: + +### 1.1 Asset Identification + +**Implementation:** +- The asset processing begins with scanning directories specified in `project.xml` +- For each asset, OpenFL records: + - **Source path**: Location in the project directory + - **Target path**: Location in the output bundle + - **ID**: Unique identifier (typically the target path) + - **Type**: Determined by file extension or explicit definition + +```haxe +// Simplified representation of how OpenFL identifies assets +var assets = []; +for (assetPath in FileSystem.readDirectory(sourcePath)) { + if (filterPattern.match(assetPath)) { + var id = targetPath + assetPath.substr(sourcePath.length); + var type = determineAssetType(assetPath); + assets.push({ id: id, path: assetPath, type: type }); + } +} +``` + +### 1.2 Type-Specific Processing + +OpenFL processes different asset types with specialized handlers: + +#### Images (BitmapData) +- **Formats**: PNG, JPG, GIF, BMP, etc. +- **Processing**: + - Conversion to platform-specific formats + - Optional compression + - Resolution handling for multi-DPI targets + - For embedded assets, images are encoded as Base64 or binary data within generated code + +```haxe +// Example of image processing logic +function processImage(path, targetPath, options) { + var image = Image.fromFile(path); + + // Apply transformations based on options + if (options.compress) image.compress(options.quality); + if (options.resize) image.resize(options.width, options.height); + + // Output in appropriate format + if (options.embed) { + return encodeImageForEmbedding(image, options.format); + } else { + image.save(targetPath, options.format); + return { path: targetPath }; + } +} +``` + +#### Audio +- **Formats**: MP3, OGG, WAV +- **Processing**: + - Format conversion based on platform support + - Optional compression + - Stream vs. complete load settings + +#### Fonts +- **Processing**: + - Registration with the text engine + - Subsetting when specified + - Conversion to platform-specific formats + +#### Text/Data Files +- **Processing**: + - Optional minification for JSON/XML + - Encoding conversion if necessary + - Direct embedding or file copying + +### 1.3 Asset Transformation Chain + +For complex asset types, OpenFL applies a chain of transformations: + +1. **Loading**: Reading the source asset +2. **Validation**: Checking format compatibility +3. **Transformation**: Applying format-specific conversions +4. **Optimization**: Platform-specific optimizations +5. **Output**: Writing to target location or embedding + +### 1.4 Embedding Process + +For embedded assets, OpenFL: + +1. Reads the asset content +2. Converts it to a code representation (usually a byte array) +3. Generates code to reconstruct the asset at runtime +4. Includes this code in the compiled application + +```haxe +// Example of how embedding is implemented +var content = File.getBytes(assetPath); +var assetCode = 'var ${safeName}_bytes = new haxe.io.Bytes(${content.length}, "${Base64.encode(content)}");'; +assetCode += 'assets.byteData.set("${assetId}", ${safeName}_bytes);'; +``` + +### 1.5 Manifest Generation + +The final step creates a manifest that catalogs all assets: + +```json +{ + "name": "default", + "assets": [ + { + "id": "assets/images/logo.png", + "path": "./assets/images/logo.png", + "type": "image" + }, + { + "id": "assets/data/levels.json", + "path": "./assets/data/levels.json", + "type": "text" + } + ] +} +``` + +## 2. Runtime Access Layer + +The runtime access layer is responsible for loading assets during application execution. + +### 2.1 Assets Class Architecture + +OpenFL's asset system is built around several key classes: + +- **Assets** (`openfl.utils.Assets`): Static interface for accessing assets +- **AssetLibrary** (`openfl.utils.AssetLibrary`): Container for a collection of assets +- **AssetCache** (`openfl.utils.AssetCache`): Temporary storage for loaded assets + +```haxe +// Core Assets class simplified structure +class Assets { + private static var libraries:Map; + private static var cache:AssetCache; + + public static function getBitmapData(id:String, useCache:Bool = true):BitmapData; + public static function getBytes(id:String):Bytes; + public static function getFont(id:String):Font; + public static function getSound(id:String):Sound; + public static function getText(id:String):String; +} +``` + +### 2.2 Asset Resolution System + +When requesting an asset: + +1. The Assets class parses the ID to determine the library and asset path +2. It checks if the asset is already cached (if caching is enabled) +3. If not cached, it delegates loading to the appropriate AssetLibrary +4. The loaded asset is optionally cached before being returned + +```haxe +// Asset resolution pseudocode +function getAsset(id:String, type:AssetType) { + var libraryName = parseLibraryName(id); + var assetPath = parseAssetPath(id); + var library = libraries.get(libraryName); + + if (useCache && cache.has(id)) { + return cache.get(id); + } + + var asset = library.loadAsset(assetPath, type); + + if (useCache) { + cache.set(id, asset); + } + + return asset; +} +``` + +### 2.3 Library Loading Process + +Asset libraries are typically loaded at application startup: + +```haxe +// How OpenFL initializes the asset system +public static function initializeAssets():Void { + // Load embedded manifest(s) + var manifest = AssetManifest.fromBytes(getEmbeddedManifest()); + var library = AssetLibrary.fromManifest(manifest); + + // Register the library + registerLibrary("default", library); + + // Additional libraries can be loaded dynamically +} +``` + +### 2.4 Asset Loading Methods + +OpenFL supports both synchronous and asynchronous loading: + +- **Synchronous**: `Assets.getBitmapData(id)` - Blocks until the asset is loaded +- **Asynchronous**: `Assets.loadBitmapData(id).onComplete(callback)` - Non-blocking load + +```haxe +// Example of async loading implementation +public static function loadBitmapData(id:String):Future { + var libraryName = parseLibraryName(id); + var assetPath = parseAssetPath(id); + var library = libraries.get(libraryName); + + return library.loadBitmapData(assetPath); +} +``` + +## 3. Resource Management + +Resource management focuses on efficiently handling assets during runtime. + +### 3.1 Asset Caching System + +OpenFL's caching system: + +1. Stores loaded assets by ID in type-specific caches +2. Enables efficient reuse of frequently accessed assets +3. Provides cache control methods to manage memory usage + +```haxe +// AssetCache implementation outline +class AssetCache { + private var bitmapData:Map; + private var font:Map; + private var sound:Map; + + public function set(id:String, asset:Dynamic, type:AssetType):Void { + switch (type) { + case AssetType.IMAGE: + bitmapData.set(id, asset); + case AssetType.FONT: + font.set(id, asset); + // Other types... + } + } + + public function get(id:String, type:AssetType):Dynamic { + switch (type) { + case AssetType.IMAGE: + return bitmapData.get(id); + // Other types... + } + return null; + } + + public function clear(type:AssetType = null):Void { + // Clear specific or all caches + } +} +``` + +### 3.2 Memory Management + +OpenFL implements several strategies for efficient memory usage: + +#### Reference Counting +- Certain assets implement reference counting to track usage +- When references drop to zero, assets can be unloaded + +```haxe +class ManagedAsset { + private var referenceCount:Int = 0; + + public function retain():Void { + referenceCount++; + } + + public function release():Void { + referenceCount--; + if (referenceCount <= 0) { + dispose(); + } + } + + private function dispose():Void { + // Resource-specific cleanup + } +} +``` + +#### Cache Eviction Policies +- Time-based: Assets unused for a period are removed +- Size-based: Cache enforces a maximum memory footprint +- Priority-based: Critical assets remain cached longer + +#### Manual Control +- `Assets.cache.clear()` - Clears all cached assets +- `Assets.cache.removeBitmapData(id)` - Removes specific cached assets +- `Assets.unloadLibrary(name)` - Unloads entire libraries + +### 3.3 Event System + +The asset system uses events to communicate state changes: + +```haxe +// Event system for asset loading +class AssetLibrary { + private var eventDispatcher:EventDispatcher; + + public function loadBitmapData(id:String):Future { + var future = new Future(); + + // Start loading process + var loader = new BitmapDataLoader(id); + loader.onComplete = function(bitmapData) { + eventDispatcher.dispatchEvent(new AssetEvent(AssetEvent.ASSET_LOADED, id)); + future.complete(bitmapData); + }; + loader.onError = function(error) { + eventDispatcher.dispatchEvent(new AssetEvent(AssetEvent.ASSET_ERROR, id)); + future.error(error); + }; + loader.load(); + + return future; + } +} +``` + +## 4. Implementation Guidelines for Custom Asset System + +Based on OpenFL's approach, here are guidelines for implementing your own asset bundling system: + +### 4.1 Asset Processing Implementation + +1. **Asset Scanner**: + - Create a recursive directory scanner that identifies assets + - Implement pattern matching for inclusion/exclusion rules + - Generate unique IDs based on file paths + +```haxe +function scanAssets(directory:String, patterns:Array):Array { + var assets = []; + + for (file in FileSystem.readRecursive(directory)) { + if (matchesAnyPattern(file, patterns)) { + assets.push({ + id: generateAssetId(file, directory), + path: file, + type: determineType(file) + }); + } + } + + return assets; +} +``` + +2. **Asset Processors**: + - Create processor classes for each asset type + - Implement transformation pipelines + - Support embedding and non-embedding modes + +```haxe +interface AssetProcessor { + function process(source:String, target:String, options:Map):ProcessedAsset; + function embed(source:String, options:Map):EmbeddedCode; +} + +class ImageProcessor implements AssetProcessor { + public function process(source, target, options) { + // Image-specific processing + } + + public function embed(source, options) { + // Generate code for embedded image + } +} +``` + +3. **Manifest Generator**: + - Create a serializable manifest structure + - Support JSON and binary formats + - Include asset metadata for runtime access + +```haxe +function generateManifest(assets:Array):AssetManifest { + var manifest = new AssetManifest(); + + for (asset in assets) { + manifest.addAsset( + asset.id, + asset.path, + asset.type, + asset.parameters + ); + } + + return manifest; +} +``` + +### 4.2 Runtime Access Layer Implementation + +1. **Assets Class**: + - Create a static API similar to OpenFL's Assets class + - Implement type-specific getters (getBitmapData, getText, etc.) + - Support both synchronous and asynchronous loading + +```haxe +class Assets { + private static var libraries:Map = new Map(); + private static var cache:AssetCache = new AssetCache(); + + public static function registerLibrary(name:String, library:AssetLibrary):Void { + libraries.set(name, library); + } + + public static function getBitmapData(id:String, useCache:Bool = true):BitmapData { + // Implementation + } + + public static function loadBitmapData(id:String):Future { + // Async implementation + } + + // Other asset type methods +} +``` + +2. **Asset Library**: + - Implement a library class to manage asset collections + - Support manifest loading + - Handle asset type detection and loading + +```haxe +class AssetLibrary { + private var assets:Map = new Map(); + + public static function fromManifest(manifest:AssetManifest):AssetLibrary { + var library = new AssetLibrary(); + + for (asset in manifest.assets) { + library.addAsset(asset.id, asset); + } + + return library; + } + + public function getBitmapData(id:String):BitmapData { + var asset = assets.get(id); + if (asset != null && asset.type == AssetType.IMAGE) { + return loadBitmapDataSync(asset.path); + } + return null; + } + + // Other loading methods +} +``` + +### 4.3 Resource Management Implementation + +1. **Asset Cache**: + - Implement a multi-type caching system + - Support cache control operations + - Add memory usage tracking + +```haxe +class AssetCache { + private var bitmapData:Map = new Map(); + private var text:Map = new Map(); + // Other asset type caches + + public function has(id:String, type:AssetType):Bool { + switch (type) { + case AssetType.IMAGE: + return bitmapData.exists(id); + // Other types + } + return false; + } + + public function set(id:String, asset:Dynamic, type:AssetType):Void { + // Implementation + } + + public function get(id:String, type:AssetType):Dynamic { + // Implementation + } + + public function clear(type:AssetType = null):Void { + // Implementation + } +} +``` + +2. **Memory Management**: + - Implement reference counting for disposable assets + - Add cache size limits + - Create cache eviction policies + +```haxe +class MemoryMonitor { + private static var memoryUsage:Map = new Map(); + private static var maxMemory:Int = 100 * 1024 * 1024; // 100MB example limit + + public static function trackAsset(id:String, size:Int):Void { + memoryUsage.set(id, size); + enforceMemoryLimits(); + } + + public static function untrackAsset(id:String):Void { + memoryUsage.remove(id); + } + + private static function enforceMemoryLimits():Void { + var totalUsage = calculateTotalUsage(); + + if (totalUsage > maxMemory) { + evictCache(totalUsage - maxMemory); + } + } + + private static function evictCache(bytesToFree:Int):Void { + // Implement LRU or other eviction strategy + } +} +``` + +## 5. Critical Implementation Considerations + +When implementing your custom asset system, keep these factors in mind: + +### Performance Optimization +- Implement asynchronous loading for large assets +- Use streaming for audio/video when appropriate +- Implement progressive loading for large textures + +### Platform Compatibility +- Handle platform-specific file format requirements +- Implement fallback loading mechanisms +- Consider thread safety for multi-threaded platforms + +### Memory Management +- Monitor memory usage of cached assets +- Implement automatic cache cleanup +- Provide manual unloading API for memory-constrained environments + +### Asset Versioning +- Implement asset versioning to handle updates +- Consider cache invalidation strategies +- Support hot-reloading during development + +By following these implementation guidelines, you can create a robust asset management system that provides functionality similar to OpenFL's built-in system while giving you complete control over the asset bundling and loading process. \ No newline at end of file diff --git a/standalone-openfl-app/assets/img/placeholder.txt b/standalone-openfl-app/assets/data/img/placeholder.txt similarity index 100% rename from standalone-openfl-app/assets/img/placeholder.txt rename to standalone-openfl-app/assets/data/img/placeholder.txt diff --git a/standalone-openfl-app/build.hxml b/standalone-openfl-app/build.hxml index 2b35d5d1..6c456b64 100644 --- a/standalone-openfl-app/build.hxml +++ b/standalone-openfl-app/build.hxml @@ -1,13 +1,29 @@ +-cp src +-lib lime +-lib openfl +-D lime-default +-D openfl-legacy +-D native +-D lime-cffi +-D lime-native +-D tools + +# Run asset macro to build the manifest +--macro macros.AssetMacro.buildAssets() + +# Entry point -main ApplicationMain --lib openfl --lib lime +# C++ target - adjust as needed +-cpp bin/cpp + +# Debug mode +-debug + -lib hxcpp -lib actuate -lib hxp --cp src - -cp assets -D openfl=8.9.5 -D lime=7.9.0 @@ -32,6 +48,4 @@ -D vsync=true -D depthBuffer=true -D stencilBuffer=false --D parameters="{}" - ---cpp bin/cpp \ No newline at end of file +-D parameters="{}" \ No newline at end of file diff --git a/standalone-openfl-app/buildlog.txt b/standalone-openfl-app/buildlog.txt new file mode 100644 index 00000000..272cb537 --- /dev/null +++ b/standalone-openfl-app/buildlog.txt @@ -0,0 +1,7 @@ +src/macros/AssetMacro.hx:106: Added asset: data/img/placeholder.txt +src/macros/AssetMacro.hx:48: DEBUG - JSON starts with: { + "name": "default", + "rootPath": "../", + "ver... +src/macros/AssetMacro.hx:55: Asset manifest saved to: bin/cpp/manifest/default.json +src/macros/AssetMacro.hx:55: Asset manifest saved to: manifest/default.json diff --git a/standalone-openfl-app/compile.sh b/standalone-openfl-app/compile.sh old mode 100644 new mode 100755 index 12f23f84..7c1595d1 --- a/standalone-openfl-app/compile.sh +++ b/standalone-openfl-app/compile.sh @@ -1,8 +1,5 @@ #!/bin/bash -# Navigate to the project directory -cd "$(dirname "$0")" - # Compile the project using the Haxe compiler with the specified build.hxml configuration haxe build.hxml @@ -11,4 +8,8 @@ if [ $? -eq 0 ]; then echo "Compilation successful!" else echo "Compilation failed!" -fi \ No newline at end of file +fi + +# Run the compiled project +# We need to run ApplicationMain-debug within the bin/cpp directory as the current working directory +(cd ./bin/cpp; ./ApplicationMain-debug) \ No newline at end of file diff --git a/standalone-openfl-app/manifest/default.json b/standalone-openfl-app/manifest/default.json new file mode 100644 index 00000000..1cda77d0 --- /dev/null +++ b/standalone-openfl-app/manifest/default.json @@ -0,0 +1,14 @@ +{ + "name": "default", + "libraryType": null, + "rootPath": ".", + "assets": [ + { + "path": "assets/data/img/placeholder.txt", + "id": "data/img/placeholder.txt", + "type": "text" + } + ], + "version": 2, + "libraryArgs": [] +} \ No newline at end of file diff --git a/standalone-openfl-app/src/ApplicationMain.hx b/standalone-openfl-app/src/ApplicationMain.hx index fddfb6cb..a08cb49b 100644 --- a/standalone-openfl-app/src/ApplicationMain.hx +++ b/standalone-openfl-app/src/ApplicationMain.hx @@ -1,136 +1,63 @@ package; import openfl.Lib; -import lime.utils.AssetType; -import lime.tools.Asset; import lime.ui.WindowAttributes; -import openfl.system.System; import openfl.display.Application; import openfl.display.Stage; import openfl.events.Event; -import openfl.events.FullScreenEvent; @:access(lime.app.Application) @:access(lime.system.System) @:access(openfl.display.Stage) @:access(openfl.events.UncaughtErrorEvents) class ApplicationMain { - public static function main() { - lime.system.System.__registerEntryPoint("src/DocumentClass.hx", create); - create(null); - } + public static function main() { + @:privateAccess lime.system.System.__registerEntryPoint("src/Main.hx", create); + create(null); + } - public static function create(config):Void { - try { - trace("Creating minimal application"); - var app = new Application(); + public static function create(config):Void { + try { + trace("Creating application"); + var app = new Application(); - // Initialize assets via macro-generated code - Assets.initializeAssets(); - trace("Assets initialized"); + // Initialize assets via custom loader + try { + Assets.initializeAssets(); + trace("Assets initialized"); + } catch (e:Dynamic) { + trace("Error initializing assets: " + e); + } - // Minimal metadata - app.meta["name"] = "Debug Application"; + // Minimal metadata + app.meta["name"] = "DSTEngine Demo"; - // Simple window - var attributes:WindowAttributes = { - height: 600, - width: 800, - title: "Debug Window" - }; + // Simple window + var attributes:WindowAttributes = { + height: 600, + width: 800, + title: "DST Engine Demo" + }; - app.createWindow(attributes); - - // Skip preloader, just call start directly - start(app.window.stage); + app.createWindow(attributes); - var result = app.exec(); - lime.system.System.exit(result); - } catch (e:Dynamic) { - trace("Error: " + e); - } - } + // Start application + start(app.window.stage); + + var result = app.exec(); + lime.system.System.exit(result); + } catch (e:Dynamic) { + trace("Error creating application: " + e); + trace(haxe.CallStack.toString(haxe.CallStack.exceptionStack())); + } + } - // public static function create(config):Void - // { - // var app = new Application(); - // app.meta["build"] = "1.0.0"; // Replace with actual build number - // app.meta["company"] = "Your Company"; // Replace with your company name - // app.meta["file"] = "src/Main.hx"; // Path to your main application file - // app.meta["name"] = "Your Application"; // Replace with your application title - // app.meta["packageName"] = "com.yourcompany.yourapp"; // Replace with your package name - // app.meta["version"] = "1.0.0"; // Replace with your application version - // // var asset = new Asset(); - // // asset.sourcePath = "src/assets"; // Path to your assets - // // asset.targetPath = "assets"; // Target path for assets - // // asset.type = AssetType.IMAGE; // Type of asset - // if (config.hxtelemetry != null) - // { - // app.meta["hxtelemetry-allocations"] = config.hxtelemetry.allocations; - // app.meta["hxtelemetry-host"] = config.hxtelemetry.host; - // } - // var attributes:WindowAttributes = { - // allowHighDPI: true, // Set to true or false as needed - // alwaysOnTop: false, // Set to true or false as needed - // borderless: false, // Set to true or false as needed - // frameRate: 60, // Set your desired frame rate - // height: 600, // Set your desired window height - // hidden: false, // Set to true or false as needed - // maximized: false, // Set to true or false as needed - // minimized: false, // Set to true or false as needed - // resizable: true, // Set to true or false as needed - // title: "Your Application", // Replace with your application title - // width: 800, // Set your desired window width - // x: 100, // Set your desired x position - // y: 100 // Set your desired y position - // }; - // app.createWindow(attributes); - // var preloader = getPreloader(); - // app.preloader.onProgress.add(function(loaded, total) - // { - // @:privateAccess preloader.update(loaded, total); - // }); - // app.preloader.onComplete.add(function() - // { - // @:privateAccess preloader.start(); - // }); - // preloader.onComplete.add(start.bind(app.window.stage)); - // app.preloader.load(); - // var result = app.exec(); - // lime.system.System.exit(result); - // } - - public static function start(stage:Stage):Void { - if (stage.__uncaughtErrorEvents.__enabled) { - try { - // Instantiate and add DocumentClass - var documentClass = new DocumentClass(); - Lib.current.addChild(documentClass); - - - // Then dispatch events - stage.dispatchEvent(new Event(Event.RESIZE, false, false)); - if (stage.window.fullscreen) { - stage.dispatchEvent(new FullScreenEvent(FullScreenEvent.FULL_SCREEN, false, false, true, true)); - } - } catch (e:Dynamic) { - stage.__handleError(e); - } - } else { - // Instantiate and add DocumentClass - var documentClass = new DocumentClass(); - Lib.current.addChild(documentClass); - - - // Then dispatch events - stage.dispatchEvent(new Event(Event.RESIZE, false, false)); - if (stage.window.fullscreen) { - stage.dispatchEvent(new FullScreenEvent(FullScreenEvent.FULL_SCREEN, false, false, true, true)); - } - } - } - - public static function getPreloader() { - return new openfl.display.Preloader(); - } + public static function start(stage:Stage):Void { + try { + var main = new Main(); + stage.addChild(main); + } catch (e:Dynamic) { + trace("Error starting application: " + e); + } + } } diff --git a/standalone-openfl-app/src/Assets.hx b/standalone-openfl-app/src/Assets.hx index 8be4541b..42321aec 100644 --- a/standalone-openfl-app/src/Assets.hx +++ b/standalone-openfl-app/src/Assets.hx @@ -1,32 +1,261 @@ package; -import openfl.display.BitmapData; -import openfl.media.Sound; -import openfl.text.Font; -import openfl.utils.ByteArray; +import haxe.io.Path; +import openfl.utils.AssetLibrary; +import openfl.utils.AssetManifest; +import openfl.utils.AssetType; +import openfl.events.Event; +import openfl.events.EventDispatcher; +import sys.FileSystem; +import sys.io.File; +import haxe.crypto.Base64; -@:build(macros.AssetMacro.buildAssets()) +/** + * Custom asset management system that initializes OpenFL's asset system. + * This supplements OpenFL's built-in asset handling. + */ class Assets { - // The @:build macro will inject the initializeAssets method + // Track initialization state + public static var isLoaded:Bool = false; - // Helper methods for convenience - public static function getBitmapData(id:String):BitmapData { - return openfl.Assets.getBitmapData(id); + // Event dispatcher for asset loading events + private static var dispatcher:EventDispatcher = new EventDispatcher(); + + // Potential manifest locations + private static var manifestPaths = [ + "manifest/default.json", + "bin/cpp/manifest/default.json", + "../manifest/default.json" + ]; + + // Event types + public static inline var ASSET_LOADED:String = "assetLoaded"; + public static inline var ASSET_ERROR:String = "assetError"; + + /** + * Initialize the asset system + */ + public static function initializeAssets():Void { + if (isLoaded) return; // Don't load twice + + trace("Initializing asset system..."); + + // First, try to initialize embedded assets if the macro created them + try { + if (Reflect.hasField(macros.AssetMacro, "initEmbeddedAssets")) { + trace("Initializing embedded assets..."); + Reflect.callMethod(macros.AssetMacro, + Reflect.field(macros.AssetMacro, "initEmbeddedAssets"), []); + isLoaded = true; + } + } catch (e:Dynamic) { + trace("No embedded assets available: " + e); + } + + // Then try to load from manifest files + loadFromManifest(); + + if (isLoaded) { + trace("Asset system initialized successfully."); + + // Log available assets + trace("Available assets:"); + for (assetPath in openfl.Assets.list()) { + var type = getAssetTypeString(assetPath); + trace(" - " + assetPath + " (Type: " + type + ")"); + } + } else { + trace("WARNING: Asset system could not be fully initialized."); + } } - public static function getSound(id:String):Sound { - return openfl.Assets.getSound(id); + /** + * Try to load assets from an external manifest file + */ + private static function loadFromManifest():Void { + trace("Checking for asset manifest files..."); + for (path in manifestPaths) { + if (FileSystem.exists(path)) { + try { + trace("Loading asset manifest from: " + path); + var content = File.getContent(path); + var jsonStartPos = 0; + while (jsonStartPos < content.length && content.charAt(jsonStartPos) != '{') { + jsonStartPos++; + } + if (jsonStartPos > 0) { + content = content.substr(jsonStartPos); + } + if (content == null || content.length == 0 || content.charAt(0) != '{') { + trace("Invalid JSON content in manifest file"); + continue; + } + var manifestData = haxe.Json.parse(content); + var manifest = new AssetManifest(); + manifest.name = manifestData.name; + manifest.assets = manifestData.assets; + manifest.rootPath = manifestData.rootPath != null ? manifestData.rootPath : Path.directory(path); + manifest.version = manifestData.version; + manifest.libraryType = manifestData.libraryType; + manifest.libraryArgs = manifestData.libraryArgs; + if (manifest.assets != null && manifest.assets.length > 0) { + trace("Parsed manifest with " + manifest.assets.length + " assets"); + var library = AssetLibrary.fromManifest(manifest); + if (library != null) { + var libraryName = manifest.name != null ? manifest.name : "default"; + if (openfl.Assets.hasLibrary(libraryName)) { + openfl.Assets.unloadLibrary(libraryName); + } + openfl.Assets.registerLibrary(libraryName, library); + trace("Registered asset library: " + libraryName); + isLoaded = true; + dispatcher.dispatchEvent(new Event(ASSET_LOADED)); + return; + } else { + trace("ERROR: Failed to create library from manifest"); + } + } else { + trace("ERROR: No assets found in manifest"); + } + } catch (e:Dynamic) { + trace("Error loading manifest from " + path + ": " + e); + } + } else { + trace("Manifest not found at: " + path); + } + } + dispatcher.dispatchEvent(new Event(ASSET_ERROR)); } - public static function getFont(id:String):Font { - return openfl.Assets.getFont(id); + /** + * Add event listener for asset events + */ + public static function addEventListener(type:String, listener:Dynamic->Void, + useCapture:Bool = false, priority:Int = 0, + useWeakReference:Bool = false):Void { + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); } - public static function getText(id:String):String { - return openfl.Assets.getText(id); + /** + * Remove event listener + */ + public static function removeEventListener(type:String, listener:Dynamic->Void, + useCapture:Bool = false):Void { + dispatcher.removeEventListener(type, listener, useCapture); } - public static function getBytes(id:String):ByteArray { - return openfl.Assets.getBytes(id); + /** + * Force reload the asset system + */ + public static function reload():Void { + var libraryNames = openfl.Assets.list().map(function(id) { + var parts = id.split(":"); + return parts.length > 1 ? parts[0] : "default"; + }).filter(function(name) { + return name != null && name != ""; + }); + + var uniqueNames = new Map(); + for (name in libraryNames) { + uniqueNames.set(name, true); + } + + for (name in uniqueNames.keys()) { + if (openfl.Assets.hasLibrary(name)) { + try { + openfl.Assets.unloadLibrary(name); + } catch (e:Dynamic) { + trace("Error unloading library " + name + ": " + e); + } + } + } + + isLoaded = false; + initializeAssets(); + } + + /** + * Helper function to determine asset types by extension + */ + private static function getAssetTypeString(id:String):String { + try { + if (!openfl.Assets.exists(id)) return "unknown"; + var extension = Path.extension(id).toLowerCase(); + return switch(extension) { + case "jpg", "jpeg", "png", "gif", "bmp": "image"; + case "mp3", "ogg", "wav": "sound"; + case "ttf", "otf": "font"; + case "txt", "json", "xml", "csv", "tsv": "text"; + default: "binary"; + } + } catch (e:Dynamic) { + return "unknown"; + } + } + + /** + * Debug the manifest and asset system status + */ + public static function debugManifest():String { + var result = "Asset System Status:\n"; + result += "Initialized: " + isLoaded + "\n\n"; + var hasManifest = false; + for (path in manifestPaths) { + if (FileSystem.exists(path)) { + hasManifest = true; + try { + var content = File.getContent(path); + var jsonStartPos = 0; + while (jsonStartPos < content.length && content.charAt(jsonStartPos) != '{') { + jsonStartPos++; + } + if (jsonStartPos > 0) { + content = content.substr(jsonStartPos); + } + result += "Manifest: " + path + "\n"; + result += "Size: " + content.length + " bytes\n"; + if (content.length > 200) { + result += "Content: " + content.substr(0, 200) + "...\n"; + } else { + result += "Content: " + content + "\n"; + } + var assetCount = "unknown"; + try { + var jsonData = haxe.Json.parse(content); + if (jsonData != null && jsonData.assets != null) { + assetCount = Std.string(jsonData.assets.length); + } + } catch (e:Dynamic) { + assetCount = "Error: " + e; + } + result += "Assets: " + assetCount + "\n"; + } catch (e:Dynamic) { + result += "Error reading manifest " + path + ": " + e + "\n"; + } + } + } + if (!hasManifest) { + result += "No manifest files found.\n"; + } + var libraryNames = []; + try { + for (asset in openfl.Assets.list()) { + var libName = ""; + var colonIndex = asset.indexOf(":"); + if (colonIndex > -1) { + libName = asset.substring(0, colonIndex); + } else { + libName = "default"; + } + if (libraryNames.indexOf(libName) == -1) { + libraryNames.push(libName); + } + } + } catch (e:Dynamic) { + result += "Error getting libraries: " + e + "\n"; + } + result += "\nLibraries: " + libraryNames.join(", ") + "\n"; + result += "Asset count: " + openfl.Assets.list().length; + return result; } } \ No newline at end of file diff --git a/standalone-openfl-app/src/EmbeddedAssetLibrary.hx b/standalone-openfl-app/src/EmbeddedAssetLibrary.hx new file mode 100644 index 00000000..449280cc --- /dev/null +++ b/standalone-openfl-app/src/EmbeddedAssetLibrary.hx @@ -0,0 +1,87 @@ +package; + +import haxe.io.Bytes; +import openfl.display.BitmapData; +import openfl.media.Sound; +import openfl.utils.AssetLibrary; +import openfl.utils.AssetType; +import openfl.utils.Future; +import openfl.display.Loader; +import openfl.events.Event; +import openfl.utils.ByteArray; + +class EmbeddedAssetLibrary extends AssetLibrary { + private var byteData:Map; + private var texts:Map; + private var images:Map; + private var sounds:Map; + + public function new(byteData:Map, texts:Map, + images:Map, sounds:Map) { + super(); + this.byteData = byteData; + this.texts = texts; + this.images = images; + this.sounds = sounds; + } + + override public function exists(id:String, type:AssetType = null):Bool { + switch (type) { + case AssetType.BINARY: + return byteData.exists(id); + case AssetType.TEXT: + return texts.exists(id); + case AssetType.IMAGE: + return images.exists(id); + case AssetType.SOUND: + return sounds.exists(id); + default: + return byteData.exists(id) || texts.exists(id) || images.exists(id) || sounds.exists(id); + } + } + + override public function getType(id:String):AssetType { + if (byteData.exists(id)) return AssetType.BINARY; + if (texts.exists(id)) return AssetType.TEXT; + if (images.exists(id)) return AssetType.IMAGE; + if (sounds.exists(id)) return AssetType.SOUND; + return null; + } + + override public function getText(id:String):String { + return texts.get(id); + } + + override public function getBytes(id:String):Bytes { + return byteData.get(id); + } + + override public function getBitmapData(id:String):BitmapData { + return images.get(id); + } + + override public function loadBitmapData(id:String):Future { + var future = new Future(); + if (images.exists(id)) { + future.onComplete(images.get(id)); + } else { + loadBitmapDataAsync(id); + } + return future; + } + + private function loadBitmapDataAsync(id:String):Void { + if (byteData.exists(id)) { + var loader = new Loader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) { + var bitmapData = cast(loader.content, BitmapData); + images.set(id, bitmapData); + }); + loader.loadBytes(ByteArray.fromBytes(byteData.get(id))); + } + } + + override public function getSound(id:String):Sound { + return sounds.get(id); + } +} \ No newline at end of file diff --git a/standalone-openfl-app/src/Main.hx b/standalone-openfl-app/src/Main.hx index 417bbabf..83440455 100644 --- a/standalone-openfl-app/src/Main.hx +++ b/standalone-openfl-app/src/Main.hx @@ -1,19 +1,117 @@ package; -import openfl.events.EventDispatcher; import openfl.display.Sprite; import openfl.events.Event; -import openfl.display.DisplayObjectContainer; -import openfl.events.MouseEvent; +import openfl.Assets; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.text.TextField; +import openfl.text.TextFormat; -class Main extends Sprite -{ - public function new() - { +class Main extends Sprite { + public function new() { super(); - dispatchEvent(new openfl.events.Event(openfl.events.Event.ADDED_TO_STAGE, false, false)); - graphics.beginFill(0xFFF00F, 1); + + // Draw background to verify app is running + graphics.beginFill(0x3498db, 1); graphics.drawRect(0, 0, 800, 600); graphics.endFill(); + + // Create title + var title = new TextField(); + title.defaultTextFormat = new TextFormat("Arial", 24, 0xFFFFFF, true); + title.text = "OpenFL Asset Test"; + title.width = 400; + title.x = 20; + title.y = 20; + addChild(title); + + // Display info about available assets + displayAssetInfo(); + + // Try to load and display a text asset + loadTextAsset(); + + // Debug manifest content + debugManifestInfo(); } -} \ No newline at end of file + + private function displayAssetInfo():Void { + var info = new TextField(); + info.defaultTextFormat = new TextFormat("Arial", 16, 0xFFFFFF); + info.width = 760; + info.height = 300; + info.x = 20; + info.y = 60; + info.multiline = true; + info.wordWrap = true; + + var assetList = openfl.Assets.list(); + info.text = "Available Assets (" + assetList.length + "):\n"; + + for (asset in assetList) { + var extension = asset.substr(asset.lastIndexOf(".") + 1).toLowerCase(); + var typeStr = getAssetTypeFromExtension(extension); + info.text += "• " + asset + " [" + typeStr + "]\n"; + } + + addChild(info); + } + + private function getAssetTypeFromExtension(extension:String):String { + return switch(extension) { + case "jpg", "jpeg", "png", "gif", "bmp": "IMAGE"; + case "mp3", "ogg", "wav": "SOUND"; + case "ttf", "otf": "FONT"; + case "txt", "json", "xml", "csv", "tsv": "TEXT"; + default: "BINARY"; + }; + } + + private function loadTextAsset():Void { + var textDisplay = new TextField(); + textDisplay.defaultTextFormat = new TextFormat("Arial", 14, 0xFFFFFF); + textDisplay.width = 760; + textDisplay.height = 200; + textDisplay.x = 20; + textDisplay.y = 380; + textDisplay.multiline = true; + textDisplay.wordWrap = true; + textDisplay.border = true; + textDisplay.borderColor = 0xFFFFFF; + textDisplay.background = true; + textDisplay.backgroundColor = 0x333333; + + var textPath = "data/img/placeholder.txt"; + + if (Assets.exists(textPath)) { + // Synchronous loading + var content = Assets.getText(textPath); + textDisplay.text = "Loaded text content from '" + textPath + "':\n\n" + content; + } else { + // Try alternative paths + var altPath = "assets/data/img/placeholder.txt"; + if (Assets.exists(altPath)) { + var content = Assets.getText(altPath); + textDisplay.text = "Loaded text content from '" + altPath + "':\n\n" + content; + } else { + textDisplay.text = "Could not find text asset '" + textPath + "' or '" + altPath + "'"; + } + } + + addChild(textDisplay); + } + + private function debugManifestInfo():Void { + var debug = new TextField(); + debug.defaultTextFormat = new TextFormat("Arial", 12, 0xFF0000); + debug.width = 760; + debug.height = 100; + debug.x = 20; + debug.y = 540; + debug.multiline = true; + debug.wordWrap = true; + debug.text = Assets.debugManifest(); + addChild(debug); + } +} diff --git a/standalone-openfl-app/src/macros/AssetMacro.hx b/standalone-openfl-app/src/macros/AssetMacro.hx index 29f1b5ec..91a65ed7 100644 --- a/standalone-openfl-app/src/macros/AssetMacro.hx +++ b/standalone-openfl-app/src/macros/AssetMacro.hx @@ -4,109 +4,261 @@ import haxe.macro.Context; import haxe.macro.Expr; import sys.FileSystem; import sys.io.File; +import haxe.io.Path; +#if macro import haxe.Json; +import haxe.crypto.Base64; +import haxe.format.JsonPrinter; +#end class AssetMacro { public static macro function buildAssets():Array { var fields = Context.getBuildFields(); - // Define assets directory - var assetsDir = "assets"; - - // Check if directory exists - if (!FileSystem.exists(assetsDir) || !FileSystem.isDirectory(assetsDir)) { - Context.warning('Assets directory "$assetsDir" not found', Context.currentPos()); - return fields; - } - - // Generate manifest in the format OpenFL expects - var manifest = { - name: "default", - rootPath: "", - assets: [] - }; - - // Scan assets - scanDirectory(assetsDir, manifest.assets); - - // Create manifest file (for runtime use) - var manifestPath = "bin/manifest.json"; + #if !display try { - // Make sure directory exists - var dir = manifestPath.split("/")[0]; - if (!FileSystem.exists(dir)) { - FileSystem.createDirectory(dir); + // Define output directories + var targetBuildDir = "bin/cpp"; + var manifestDir = targetBuildDir + "/manifest"; + var assetsOutputDir = targetBuildDir + "/assets"; + + // Create directories if they don't exist + ensureDirectory(manifestDir); + ensureDirectory(assetsOutputDir); + ensureDirectory("manifest"); + + // Add a test placeholder if no assets exist + if (!FileSystem.exists("assets")) { + ensureDirectory("assets/data/img"); + File.saveContent("assets/data/img/placeholder.txt", "This is a placeholder file."); } - File.saveContent(manifestPath, Json.stringify(manifest, null, " ")); - } catch (e) { - Context.warning('Could not save manifest file: $e', Context.currentPos()); - } - - // Generate expression to register the assets - var registerExpr = macro { - // Create an AssetManifest from our generated file - var manifestPath = "bin/manifest.json"; - if (sys.FileSystem.exists(manifestPath)) { - var manifest = lime.utils.AssetManifest.fromFile(manifestPath); - if (manifest != null) { - // Register the library with OpenFL - var library = openfl.utils.AssetLibrary.fromManifest(manifest); - if (library != null) { - openfl.Assets.registerLibrary("default", library); - trace("Asset library registered successfully"); - } else { - trace("Failed to create library from manifest"); - } - } else { - trace("Failed to parse manifest file"); - } + + // Scan for assets and build asset list + var assets = []; + var assetDir = "assets"; + var embeddedAssets = new Map(); + + if (FileSystem.exists(assetDir)) { + scanAssets(assetDir, assets, "", assetsOutputDir, embeddedAssets); + trace('Found ${assets.length} assets in ${assetDir}'); } else { - trace("Manifest file not found at: " + manifestPath); + trace('WARNING: Assets directory not found: ${assetDir}'); } - }; - - // Add initialization method - fields.push({ - name: "initializeAssets", - access: [Access.APublic, Access.AStatic], - kind: FieldType.FFun({ - args: [], - ret: macro:Void, - expr: registerExpr - }), - pos: Context.currentPos() - }); + + // Create a default asset if none found + if (assets.length == 0) { + var placeholder = { + path: "assets/data/img/placeholder.txt", + type: "TEXT", + id: "data/img/placeholder.txt" + }; + assets.push(placeholder); + var placeholderContent = "This is a placeholder file."; + embeddedAssets.set(placeholder.id, { + data: Base64.encode(haxe.io.Bytes.ofString(placeholderContent)), + type: "TEXT" + }); + } + + // Create manifest in OpenFL format + var manifest = { + name: "default", + assets: assets.map(function(asset) { + return { + id: asset.id, + path: asset.path, + type: asset.type.toLowerCase() + }; + }), + rootPath: ".", + version: 2, + libraryType: null, + libraryArgs: [] + }; + var manifestJson = haxe.format.JsonPrinter.print(manifest, null, " "); + File.saveContent(manifestDir + "/default.json", manifestJson); + trace('Asset manifest saved to: ' + manifestDir + "/default.json"); + File.saveContent("manifest/default.json", manifestJson); + trace('Asset manifest also saved to: manifest/default.json'); + + // Generate the embedded assets code + if (embeddedAssets.keys().hasNext()) { + var initExprs = []; + initExprs.push(macro var assetLibrary = new CustomAssetLibrary()); + for (id in embeddedAssets.keys()) { + var asset = embeddedAssets.get(id); + if (asset == null) continue; + switch (asset.type) { + case "TEXT": + var dataExpr = macro haxe.crypto.Base64.decode($v{asset.data}).getString(0, haxe.crypto.Base64.decode($v{asset.data}).length); + initExprs.push(macro assetLibrary.registerAsset($v{id}, "text", $dataExpr)); + case "IMAGE": + initExprs.push(macro { + var bytes = haxe.crypto.Base64.decode($v{asset.data}); + assetLibrary.registerAsset($v{id}, "image", bytes); + }); + case "SOUND": + initExprs.push(macro { + var bytes = haxe.crypto.Base64.decode($v{asset.data}); + assetLibrary.registerAsset($v{id}, "sound", bytes); + }); + default: + var dataExpr = macro haxe.crypto.Base64.decode($v{asset.data}); + initExprs.push(macro assetLibrary.registerAsset($v{id}, "binary", $dataExpr)); + } + } + initExprs.push(macro openfl.Assets.registerLibrary("default", assetLibrary)); + fields.push({ + name: "initEmbeddedAssets", + doc: "Initializes embedded assets for the application", + meta: [], + access: [Access.APublic, Access.AStatic], + kind: FieldType.FFun({ + args: [], + expr: macro $b{initExprs}, + params: [], + ret: macro :Void + }), + pos: Context.currentPos() + }); + } + } catch (e:Dynamic) { + trace('ERROR in asset macro: ${e}'); + #if debug + trace(haxe.CallStack.toString(haxe.CallStack.exceptionStack())); + #end + } + #end return fields; } - private static function scanDirectory(dir:String, assets:Array, ?prefix:String = ""):Void { - for (file in FileSystem.readDirectory(dir)) { - var path = dir + "/" + file; - var id = prefix + file; - - if (FileSystem.isDirectory(path)) { - scanDirectory(path, assets, id + "/"); - } else { - var type = getAssetType(file); - assets.push({ - id: id, - path: path, - type: type - }); + #if macro + private static function ensureDirectory(dir:String):Void { + if (!FileSystem.exists(dir)) { + try { + var pathParts = dir.split("/"); + var currentPath = ""; + + for (part in pathParts) { + if (part == "") continue; + currentPath += part + "/"; + if (!FileSystem.exists(currentPath)) { + FileSystem.createDirectory(currentPath); + } + } + } catch (e:Dynamic) { + trace('ERROR creating directory ${dir}: ${e}'); } } } - private static function getAssetType(file:String):String { - var ext = file.substr(file.lastIndexOf(".") + 1).toLowerCase(); + private static function scanAssets(dir:String, assets:Array, relativePath:String = "", + outputDir:String = null, embeddedAssets:Map):Void { + if (!FileSystem.exists(dir)) return; - return switch (ext) { - case "jpg", "jpeg", "png", "gif", "bmp": "image"; - case "mp3", "ogg", "wav": "sound"; - case "ttf", "otf": "font"; - case "txt", "json", "xml", "csv": "text"; - default: "binary"; + try { + var files = FileSystem.readDirectory(dir); + + for (file in files) { + try { + var path = dir + "/" + file; + var relPath = relativePath == "" ? file : relativePath + "/" + file; + + if (FileSystem.isDirectory(path)) { + // Create the corresponding directory in output + if (outputDir != null) { + ensureDirectory(outputDir + "/" + relPath); + } + + // Recursive scan subdirectories + scanAssets(path, assets, relPath, outputDir, embeddedAssets); + } else { + // Determine asset type based on extension + var extension = Path.extension(file).toLowerCase(); + var type = getAssetType(extension); + + // Skip hidden files and non-asset files + if (file.charAt(0) == "." || type == "UNKNOWN") continue; + + // Copy the file to output directory + if (outputDir != null) { + var outputPath = outputDir + "/" + relPath; + try { + File.copy(path, outputPath); + trace('Copied asset: ${path} to ${outputPath}'); + } catch (e:Dynamic) { + trace('Error copying ${path} to ${outputPath}: ${e}'); + } + } + + // Add to asset list with proper path format + var assetId = relPath.split("\\").join("/"); // Normalize path separators + var assetPath = "assets/" + assetId; + + assets.push({ + path: assetPath, + type: type, + id: assetId + }); + + // Embed the asset content for runtime access + try { + var content = File.getBytes(path); + embeddedAssets.set(assetId, { + data: Base64.encode(content), + type: type + }); + } catch (e:Dynamic) { + trace('Warning: Could not embed asset ${assetId}: ${e}'); + } + + trace('Added asset: ${assetId}'); + } + } catch (innerE:Dynamic) { + trace('Error processing asset ${file}: ${innerE}'); + } + } + } catch (e:Dynamic) { + trace('Error reading directory ${dir}: ${e}'); } } + + private static function getAssetType(extension:String):String { + return switch(extension) { + case "jpg", "jpeg", "png", "gif", "bmp": + "IMAGE"; + case "mp3", "ogg", "wav": + "SOUND"; + case "ttf", "otf": + "FONT"; + case "txt", "json", "xml", "csv", "tsv", "frag", "vert", "properties": + "TEXT"; + case "bytes", "bin": + "BINARY"; + default: + "TEXT"; // Default to TEXT for unknown types + } + } + + private static function generateEmbeddedAssetExprs(embeddedAssets:Map):Array { + var exprs:Array = []; + for (id in embeddedAssets.keys()) { + var asset = embeddedAssets.get(id); + if (asset == null) continue; + var dataExpr = macro haxe.crypto.Base64.decode($v{asset.data}); + switch (asset.type) { + case "TEXT": + exprs.push(macro texts.set($v{id}, dataExpr.getString(0, dataExpr.length))); + case "IMAGE": + exprs.push(macro byteData.set($v{id}, dataExpr)); + case "SOUND": + exprs.push(macro byteData.set($v{id}, dataExpr)); + default: + exprs.push(macro byteData.set($v{id}, dataExpr)); + } + } + return exprs; + } + #end } \ No newline at end of file