15 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	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
 
// 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
 
// 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:
- Loading: Reading the source asset
- Validation: Checking format compatibility
- Transformation: Applying format-specific conversions
- Optimization: Platform-specific optimizations
- Output: Writing to target location or embedding
1.4 Embedding Process
For embedded assets, OpenFL:
- Reads the asset content
- Converts it to a code representation (usually a byte array)
- Generates code to reconstruct the asset at runtime
- Includes this code in the compiled application
// 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:
{
  "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
// 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:
- The Assets class parses the ID to determine the library and asset path
- It checks if the asset is already cached (if caching is enabled)
- If not cached, it delegates loading to the appropriate AssetLibrary
- The loaded asset is optionally cached before being returned
// 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:
// 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
// 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:
- Stores loaded assets by ID in type-specific caches
- Enables efficient reuse of frequently accessed assets
- Provides cache control methods to manage memory usage
// 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
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:
// 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
- Asset Scanner:
- Create a recursive directory scanner that identifies assets
- Implement pattern matching for inclusion/exclusion rules
- Generate unique IDs based on file paths
 
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;
}
- Asset Processors:
- Create processor classes for each asset type
- Implement transformation pipelines
- Support embedding and non-embedding modes
 
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
  }
}
- Manifest Generator:
- Create a serializable manifest structure
- Support JSON and binary formats
- Include asset metadata for runtime access
 
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
- 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
 
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
}
- Asset Library:
- Implement a library class to manage asset collections
- Support manifest loading
- Handle asset type detection and loading
 
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
- Asset Cache:
- Implement a multi-type caching system
- Support cache control operations
- Add memory usage tracking
 
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
  }
}
- Memory Management:
- Implement reference counting for disposable assets
- Add cache size limits
- Create cache eviction policies
 
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.