Stable state
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user