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
|
||||||
@ -32,6 +48,4 @@
|
|||||||
-D vsync=true
|
-D vsync=true
|
||||||
-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
|
||||||
9
standalone-openfl-app/compile.sh
Normal file → Executable file
9
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
|
||||||
|
|
||||||
@ -11,4 +8,8 @@ if [ $? -eq 0 ]; then
|
|||||||
echo "Compilation successful!"
|
echo "Compilation successful!"
|
||||||
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(app.window.stage);
|
|
||||||
|
|
||||||
var result = app.exec();
|
// Start application
|
||||||
lime.system.System.exit(result);
|
start(app.window.stage);
|
||||||
} catch (e:Dynamic) {
|
|
||||||
trace("Error: " + e);
|
var result = app.exec();
|
||||||
}
|
lime.system.System.exit(result);
|
||||||
}
|
} catch (e:Dynamic) {
|
||||||
|
trace("Error creating application: " + e);
|
||||||
|
trace(haxe.CallStack.toString(haxe.CallStack.exceptionStack()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// public static function create(config):Void
|
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";
|
||||||
|
|
||||||
|
// Create directories if they don't exist
|
||||||
|
ensureDirectory(manifestDir);
|
||||||
|
ensureDirectory(assetsOutputDir);
|
||||||
|
ensureDirectory("manifest");
|
||||||
|
|
||||||
|
// Add a test placeholder if no assets exist
|
||||||
|
if (!FileSystem.exists("assets")) {
|
||||||
|
ensureDirectory("assets/data/img");
|
||||||
|
File.saveContent("assets/data/img/placeholder.txt", "This is a placeholder file.");
|
||||||
}
|
}
|
||||||
File.saveContent(manifestPath, Json.stringify(manifest, null, " "));
|
|
||||||
} catch (e) {
|
// Scan for assets and build asset list
|
||||||
Context.warning('Could not save manifest file: $e', Context.currentPos());
|
var assets = [];
|
||||||
}
|
var assetDir = "assets";
|
||||||
|
var embeddedAssets = new Map<String, {data: String, type: String}>();
|
||||||
// Generate expression to register the assets
|
|
||||||
var registerExpr = macro {
|
if (FileSystem.exists(assetDir)) {
|
||||||
// Create an AssetManifest from our generated file
|
scanAssets(assetDir, assets, "", assetsOutputDir, embeddedAssets);
|
||||||
var manifestPath = "bin/manifest.json";
|
trace('Found ${assets.length} assets in ${assetDir}');
|
||||||
if (sys.FileSystem.exists(manifestPath)) {
|
|
||||||
var manifest = lime.utils.AssetManifest.fromFile(manifestPath);
|
|
||||||
if (manifest != null) {
|
|
||||||
// Register the library with OpenFL
|
|
||||||
var library = openfl.utils.AssetLibrary.fromManifest(manifest);
|
|
||||||
if (library != null) {
|
|
||||||
openfl.Assets.registerLibrary("default", library);
|
|
||||||
trace("Asset library registered successfully");
|
|
||||||
} else {
|
|
||||||
trace("Failed to create library from manifest");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
trace("Failed to parse manifest file");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
trace("Manifest file not found at: " + manifestPath);
|
trace('WARNING: Assets directory not found: ${assetDir}');
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// Create a default asset if none found
|
||||||
// Add initialization method
|
if (assets.length == 0) {
|
||||||
fields.push({
|
var placeholder = {
|
||||||
name: "initializeAssets",
|
path: "assets/data/img/placeholder.txt",
|
||||||
access: [Access.APublic, Access.AStatic],
|
type: "TEXT",
|
||||||
kind: FieldType.FFun({
|
id: "data/img/placeholder.txt"
|
||||||
args: [],
|
};
|
||||||
ret: macro:Void,
|
assets.push(placeholder);
|
||||||
expr: registerExpr
|
var placeholderContent = "This is a placeholder file.";
|
||||||
}),
|
embeddedAssets.set(placeholder.id, {
|
||||||
pos: Context.currentPos()
|
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("/");
|
||||||
if (FileSystem.isDirectory(path)) {
|
var currentPath = "";
|
||||||
scanDirectory(path, assets, id + "/");
|
|
||||||
} else {
|
for (part in pathParts) {
|
||||||
var type = getAssetType(file);
|
if (part == "") continue;
|
||||||
assets.push({
|
currentPath += part + "/";
|
||||||
id: id,
|
if (!FileSystem.exists(currentPath)) {
|
||||||
path: path,
|
FileSystem.createDirectory(currentPath);
|
||||||
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