# 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.