Stable state
This commit is contained in:
		
							parent
							
								
									8c36316409
								
							
						
					
					
						commit
						27c5503173
					
				
							
								
								
									
										5
									
								
								standalone-openfl-app/CORE_PRINCIPLES_IMPORTANT!.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								standalone-openfl-app/CORE_PRINCIPLES_IMPORTANT!.md
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
|  |   | ||||||
							
								
								
									
										22
									
								
								standalone-openfl-app/IMPORTANT_INSTRUCTIONS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								standalone-openfl-app/IMPORTANT_INSTRUCTIONS.md
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
| @ -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<String, AssetLibrary>; | ||||||
|  |   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<BitmapData> { | ||||||
|  |   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<String, BitmapData>; | ||||||
|  |   private var font:Map<String, Font>; | ||||||
|  |   private var sound:Map<String, Sound>; | ||||||
|  |    | ||||||
|  |   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<BitmapData> { | ||||||
|  |     var future = new Future<BitmapData>(); | ||||||
|  |      | ||||||
|  |     // 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<String>):Array<AssetInfo> { | ||||||
|  |   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<String, Dynamic>):ProcessedAsset; | ||||||
|  |   function embed(source:String, options:Map<String, Dynamic>):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<AssetInfo>):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<String, AssetLibrary> = 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<BitmapData> { | ||||||
|  |     // 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<String, AssetInfo> = 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<String, BitmapData> = new Map(); | ||||||
|  |   private var text:Map<String, String> = 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<String, Int> = 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. | ||||||
| @ -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 | -main ApplicationMain | ||||||
| 
 | 
 | ||||||
| -lib openfl | # C++ target - adjust as needed | ||||||
| -lib lime | -cpp bin/cpp | ||||||
|  | 
 | ||||||
|  | # Debug mode | ||||||
|  | -debug | ||||||
|  | 
 | ||||||
| -lib hxcpp | -lib hxcpp | ||||||
| -lib actuate | -lib actuate | ||||||
| -lib hxp | -lib hxp | ||||||
| 
 | 
 | ||||||
| -cp src |  | ||||||
| 
 |  | ||||||
| -cp assets | -cp assets | ||||||
| -D openfl=8.9.5 | -D openfl=8.9.5 | ||||||
| -D lime=7.9.0 | -D lime=7.9.0 | ||||||
| @ -33,5 +49,3 @@ | |||||||
| -D depthBuffer=true | -D depthBuffer=true | ||||||
| -D stencilBuffer=false | -D stencilBuffer=false | ||||||
| -D parameters="{}" | -D parameters="{}" | ||||||
| 
 |  | ||||||
| --cpp bin/cpp |  | ||||||
							
								
								
									
										7
									
								
								standalone-openfl-app/buildlog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								standalone-openfl-app/buildlog.txt
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
							
								
								
									
										7
									
								
								standalone-openfl-app/compile.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										7
									
								
								standalone-openfl-app/compile.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,8 +1,5 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| # Navigate to the project directory |  | ||||||
| cd "$(dirname "$0")" |  | ||||||
| 
 |  | ||||||
| # Compile the project using the Haxe compiler with the specified build.hxml configuration | # Compile the project using the Haxe compiler with the specified build.hxml configuration | ||||||
| haxe build.hxml | haxe build.hxml | ||||||
| 
 | 
 | ||||||
| @ -12,3 +9,7 @@ if [ $? -eq 0 ]; then | |||||||
| else | else | ||||||
|     echo "Compilation failed!" |     echo "Compilation failed!" | ||||||
| fi | 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) | ||||||
							
								
								
									
										14
									
								
								standalone-openfl-app/manifest/default.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								standalone-openfl-app/manifest/default.json
									
									
									
									
									
										Normal file
									
								
							| @ -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": [] | ||||||
|  | } | ||||||
| @ -1,136 +1,63 @@ | |||||||
| package; | package; | ||||||
| 
 | 
 | ||||||
| import openfl.Lib; | import openfl.Lib; | ||||||
| import lime.utils.AssetType; |  | ||||||
| import lime.tools.Asset; |  | ||||||
| import lime.ui.WindowAttributes; | import lime.ui.WindowAttributes; | ||||||
| import openfl.system.System; |  | ||||||
| import openfl.display.Application; | import openfl.display.Application; | ||||||
| import openfl.display.Stage; | import openfl.display.Stage; | ||||||
| import openfl.events.Event; | import openfl.events.Event; | ||||||
| import openfl.events.FullScreenEvent; |  | ||||||
| 
 | 
 | ||||||
| @:access(lime.app.Application) | @:access(lime.app.Application) | ||||||
| @:access(lime.system.System) | @:access(lime.system.System) | ||||||
| @:access(openfl.display.Stage) | @:access(openfl.display.Stage) | ||||||
| @:access(openfl.events.UncaughtErrorEvents) | @:access(openfl.events.UncaughtErrorEvents) | ||||||
| class ApplicationMain { | class ApplicationMain { | ||||||
| 	public static function main() { |     public static function main() { | ||||||
| 		lime.system.System.__registerEntryPoint("src/DocumentClass.hx", create); |         @:privateAccess lime.system.System.__registerEntryPoint("src/Main.hx", create); | ||||||
| 		create(null); |         create(null); | ||||||
| 	} |     } | ||||||
| 
 | 
 | ||||||
| 	public static function create(config):Void { |     public static function create(config):Void { | ||||||
| 		try { |         try { | ||||||
| 			trace("Creating minimal application"); |             trace("Creating application"); | ||||||
| 			var app = new Application(); |             var app = new Application(); | ||||||
| 
 | 
 | ||||||
| 			// Initialize assets via macro-generated code |             // Initialize assets via custom loader | ||||||
| 			Assets.initializeAssets(); |             try { | ||||||
| 			trace("Assets initialized"); |                 Assets.initializeAssets(); | ||||||
|  |                 trace("Assets initialized"); | ||||||
|  |             } catch (e:Dynamic) { | ||||||
|  |                 trace("Error initializing assets: " + e); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
| 			// Minimal metadata |             // Minimal metadata | ||||||
| 			app.meta["name"] = "Debug Application"; |             app.meta["name"] = "DSTEngine Demo"; | ||||||
| 
 | 
 | ||||||
| 			// Simple window |             // Simple window | ||||||
| 			var attributes:WindowAttributes = { |             var attributes:WindowAttributes = { | ||||||
| 				height: 600, |                 height: 600, | ||||||
| 				width: 800, |                 width: 800, | ||||||
| 				title: "Debug Window" |                 title: "DST Engine Demo" | ||||||
| 			}; |             }; | ||||||
| 
 | 
 | ||||||
| 			app.createWindow(attributes); |             app.createWindow(attributes); | ||||||
|              |              | ||||||
| 			// Skip preloader, just call start directly |             // Start application | ||||||
| 			start(app.window.stage); |             start(app.window.stage); | ||||||
|              |              | ||||||
| 			var result = app.exec(); |             var result = app.exec(); | ||||||
| 			lime.system.System.exit(result); |             lime.system.System.exit(result); | ||||||
| 		} catch (e:Dynamic) { |         } catch (e:Dynamic) { | ||||||
| 			trace("Error: " + e); |             trace("Error creating application: " + e); | ||||||
| 		} |             trace(haxe.CallStack.toString(haxe.CallStack.exceptionStack())); | ||||||
| 	} |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	// public static function create(config):Void |     public static function start(stage:Stage):Void { | ||||||
| 	// { |         try { | ||||||
| 	//     var app = new Application(); |             var main = new Main(); | ||||||
| 	//     app.meta["build"] = "1.0.0"; // Replace with actual build number |             stage.addChild(main); | ||||||
| 	//     app.meta["company"] = "Your Company"; // Replace with your company name |         } catch (e:Dynamic) { | ||||||
| 	//     app.meta["file"] = "src/Main.hx"; // Path to your main application file |             trace("Error starting application: " + e); | ||||||
| 	//     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(); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,32 +1,261 @@ | |||||||
| package; | package; | ||||||
| 
 | 
 | ||||||
| import openfl.display.BitmapData; | import haxe.io.Path; | ||||||
| import openfl.media.Sound; | import openfl.utils.AssetLibrary; | ||||||
| import openfl.text.Font; | import openfl.utils.AssetManifest; | ||||||
| import openfl.utils.ByteArray; | 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 { | class Assets { | ||||||
|     // The @:build macro will inject the initializeAssets method |     // Track initialization state | ||||||
|  |     public static var isLoaded:Bool = false; | ||||||
|      |      | ||||||
|     // Helper methods for convenience |     // Event dispatcher for asset loading events | ||||||
|     public static function getBitmapData(id:String):BitmapData { |     private static var dispatcher:EventDispatcher = new EventDispatcher(); | ||||||
|         return openfl.Assets.getBitmapData(id); |      | ||||||
|  |     // 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<String, Bool>(); | ||||||
|  |         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; | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										87
									
								
								standalone-openfl-app/src/EmbeddedAssetLibrary.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								standalone-openfl-app/src/EmbeddedAssetLibrary.hx
									
									
									
									
									
										Normal file
									
								
							| @ -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<String, Bytes>; | ||||||
|  |     private var texts:Map<String, String>; | ||||||
|  |     private var images:Map<String, BitmapData>; | ||||||
|  |     private var sounds:Map<String, Sound>; | ||||||
|  | 
 | ||||||
|  |     public function new(byteData:Map<String, Bytes>, texts:Map<String, String>,  | ||||||
|  |                        images:Map<String, BitmapData>, sounds:Map<String, Sound>) { | ||||||
|  |         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<BitmapData> { | ||||||
|  |         var future = new Future<BitmapData>(); | ||||||
|  |         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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,19 +1,117 @@ | |||||||
| package; | package; | ||||||
| 
 | 
 | ||||||
| import openfl.events.EventDispatcher; |  | ||||||
| import openfl.display.Sprite; | import openfl.display.Sprite; | ||||||
| import openfl.events.Event; | import openfl.events.Event; | ||||||
| import openfl.display.DisplayObjectContainer; | import openfl.Assets; | ||||||
| import openfl.events.MouseEvent; | import openfl.display.Bitmap; | ||||||
|  | import openfl.display.BitmapData; | ||||||
|  | import openfl.text.TextField; | ||||||
|  | import openfl.text.TextFormat; | ||||||
| 
 | 
 | ||||||
| class Main extends Sprite | class Main extends Sprite { | ||||||
| { |     public function new() { | ||||||
|     public function new() |  | ||||||
|     { |  | ||||||
|         super(); |         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.drawRect(0, 0, 800, 600); | ||||||
|         graphics.endFill(); |         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(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     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); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -4,109 +4,261 @@ import haxe.macro.Context; | |||||||
| import haxe.macro.Expr; | import haxe.macro.Expr; | ||||||
| import sys.FileSystem; | import sys.FileSystem; | ||||||
| import sys.io.File; | import sys.io.File; | ||||||
|  | import haxe.io.Path; | ||||||
|  | #if macro | ||||||
| import haxe.Json; | import haxe.Json; | ||||||
|  | import haxe.crypto.Base64; | ||||||
|  | import haxe.format.JsonPrinter; | ||||||
|  | #end | ||||||
| 
 | 
 | ||||||
| class AssetMacro { | class AssetMacro { | ||||||
|     public static macro function buildAssets():Array<Field> { |     public static macro function buildAssets():Array<Field> { | ||||||
|         var fields = Context.getBuildFields(); |         var fields = Context.getBuildFields(); | ||||||
|          |          | ||||||
|         // Define assets directory |         #if !display | ||||||
|         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"; |  | ||||||
|         try { |         try { | ||||||
|             // Make sure directory exists |             // Define output directories | ||||||
|             var dir = manifestPath.split("/")[0]; |             var targetBuildDir = "bin/cpp"; | ||||||
|             if (!FileSystem.exists(dir)) { |             var manifestDir = targetBuildDir + "/manifest"; | ||||||
|                 FileSystem.createDirectory(dir); |             var assetsOutputDir = targetBuildDir + "/assets"; | ||||||
|             } |  | ||||||
|             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 |             // Create directories if they don't exist | ||||||
|         var registerExpr = macro { |             ensureDirectory(manifestDir); | ||||||
|             // Create an AssetManifest from our generated file |             ensureDirectory(assetsOutputDir); | ||||||
|             var manifestPath = "bin/manifest.json"; |             ensureDirectory("manifest"); | ||||||
|             if (sys.FileSystem.exists(manifestPath)) { |              | ||||||
|                 var manifest = lime.utils.AssetManifest.fromFile(manifestPath); |             // Add a test placeholder if no assets exist | ||||||
|                 if (manifest != null) { |             if (!FileSystem.exists("assets")) { | ||||||
|                     // Register the library with OpenFL |                 ensureDirectory("assets/data/img"); | ||||||
|                     var library = openfl.utils.AssetLibrary.fromManifest(manifest); |                 File.saveContent("assets/data/img/placeholder.txt", "This is a placeholder file."); | ||||||
|                     if (library != null) { |             } | ||||||
|                         openfl.Assets.registerLibrary("default", library); |              | ||||||
|                         trace("Asset library registered successfully"); |             // Scan for assets and build asset list | ||||||
|                     } else { |             var assets = []; | ||||||
|                         trace("Failed to create library from manifest"); |             var assetDir = "assets"; | ||||||
|                     } |             var embeddedAssets = new Map<String, {data: String, type: String}>(); | ||||||
|                 } else { |              | ||||||
|                     trace("Failed to parse manifest file"); |             if (FileSystem.exists(assetDir)) { | ||||||
|                 } |                 scanAssets(assetDir, assets, "", assetsOutputDir, embeddedAssets); | ||||||
|  |                 trace('Found ${assets.length} assets in ${assetDir}'); | ||||||
|             } else { |             } else { | ||||||
|                 trace("Manifest file not found at: " + manifestPath); |                 trace('WARNING: Assets directory not found: ${assetDir}'); | ||||||
|             } |             } | ||||||
|         }; |  | ||||||
|              |              | ||||||
|         // Add initialization method |             // Create a default asset if none found | ||||||
|         fields.push({ |             if (assets.length == 0) { | ||||||
|             name: "initializeAssets", |                 var placeholder = { | ||||||
|             access: [Access.APublic, Access.AStatic], |                     path: "assets/data/img/placeholder.txt", | ||||||
|             kind: FieldType.FFun({ |                     type: "TEXT", | ||||||
|                 args: [], |                     id: "data/img/placeholder.txt" | ||||||
|                 ret: macro:Void, |                 }; | ||||||
|                 expr: registerExpr |                 assets.push(placeholder); | ||||||
|             }), |                 var placeholderContent = "This is a placeholder file."; | ||||||
|             pos: Context.currentPos() |                 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; |         return fields; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private static function scanDirectory(dir:String, assets:Array<Dynamic>, ?prefix:String = ""):Void { |     #if macro | ||||||
|         for (file in FileSystem.readDirectory(dir)) { |     private static function ensureDirectory(dir:String):Void { | ||||||
|             var path = dir + "/" + file; |         if (!FileSystem.exists(dir)) { | ||||||
|             var id = prefix + file; |             try { | ||||||
|  |                 var pathParts = dir.split("/"); | ||||||
|  |                 var currentPath = ""; | ||||||
|                  |                  | ||||||
|             if (FileSystem.isDirectory(path)) { |                 for (part in pathParts) { | ||||||
|                 scanDirectory(path, assets, id + "/"); |                     if (part == "") continue; | ||||||
|             } else { |                     currentPath += part + "/"; | ||||||
|                 var type = getAssetType(file); |                     if (!FileSystem.exists(currentPath)) { | ||||||
|                 assets.push({ |                         FileSystem.createDirectory(currentPath); | ||||||
|                     id: id, |                     } | ||||||
|                     path: path, |                 } | ||||||
|                     type: type |             } catch (e:Dynamic) { | ||||||
|                 }); |                 trace('ERROR creating directory ${dir}: ${e}'); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private static function getAssetType(file:String):String { |     private static function scanAssets(dir:String, assets:Array<Dynamic>, relativePath:String = "",  | ||||||
|         var ext = file.substr(file.lastIndexOf(".") + 1).toLowerCase(); |                                      outputDir:String = null, embeddedAssets:Map<String, {data:String, type:String}>):Void { | ||||||
|  |         if (!FileSystem.exists(dir)) return; | ||||||
|          |          | ||||||
|         return switch (ext) { |         try { | ||||||
|             case "jpg", "jpeg", "png", "gif", "bmp": "image"; |             var files = FileSystem.readDirectory(dir); | ||||||
|             case "mp3", "ogg", "wav": "sound"; |              | ||||||
|             case "ttf", "otf": "font"; |             for (file in files) { | ||||||
|             case "txt", "json", "xml", "csv": "text"; |                 try { | ||||||
|             default: "binary"; |                     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<String, {data:String, type:String}>):Array<Expr> { | ||||||
|  |         var exprs:Array<Expr> = []; | ||||||
|  |         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 | ||||||
| } | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user