diff --git a/hGameTest/Makefile b/hGameTest/Makefile new file mode 100644 index 00000000..e2130bc0 --- /dev/null +++ b/hGameTest/Makefile @@ -0,0 +1,24 @@ +# Makefile for standalone OpenFL application + +# Default target +.PHONY: all +all: build + +# Build the application +.PHONY: build +build: + haxe build.hxml + +# Run the compiled application +.PHONY: run +run: + bash -c "cd bin/cpp && ./ApplicationMain" + +# Build and run the application +.PHONY: test +test: build run + +# Clean build artifacts +.PHONY: clean +clean: + rm -rf bin/cpp diff --git a/hGameTest/Project.hx b/hGameTest/Project.hx index e959e771..7becc36c 100644 --- a/hGameTest/Project.hx +++ b/hGameTest/Project.hx @@ -17,7 +17,8 @@ class Project extends HXProject { // App settings app.main = "Main"; app.file = "hgame"; - app.path = "bin"; + app.path = "bin/openfl-build-chain"; + app.preloader = "HGamePreloader"; // Window config @@ -52,6 +53,8 @@ class Project extends HXProject { haxeflags.push("--macro keep('Main')"); haxeflags.push("-dce full"); - // target = Platform.LINUX; + target = Platform.LINUX; + + } } \ No newline at end of file diff --git a/hGameTest/assets/data/config.json b/hGameTest/assets/data/config.json new file mode 100644 index 00000000..544b7b4d --- /dev/null +++ b/hGameTest/assets/data/config.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/hGameTest/assets/data/img/placeholder.txt b/hGameTest/assets/data/img/placeholder.txt new file mode 100644 index 00000000..aa46a7cf --- /dev/null +++ b/hGameTest/assets/data/img/placeholder.txt @@ -0,0 +1 @@ +This file is intentionally left blank. \ No newline at end of file diff --git a/hGameTest/assets/data/textures.json b/hGameTest/assets/data/textures.json new file mode 100644 index 00000000..b148db7d --- /dev/null +++ b/hGameTest/assets/data/textures.json @@ -0,0 +1,18 @@ +{ + "dataScheme":"textureList", + "version":1, + "textureData": { + "spriteSheets":[{ + "sheetName":"testSheet", + "path":"sheets/sheet.png", + "sheetDataPath":"sheets/sheet.json", + "explicitDimensions":true, + "tileDimensions":{"width":16,"height":16} + }], + "sprites": + [{ + "spriteName":"player", + "path":"individual/character.png" + }] + } +} diff --git a/hGameTest/assets/fonts/Terminus.ttf b/hGameTest/assets/fonts/Terminus.ttf new file mode 100644 index 00000000..eafa3a6e Binary files /dev/null and b/hGameTest/assets/fonts/Terminus.ttf differ diff --git a/hGameTest/assets/images/logo.png b/hGameTest/assets/images/logo.png new file mode 100644 index 00000000..1a410954 Binary files /dev/null and b/hGameTest/assets/images/logo.png differ diff --git a/hGameTest/build.hxml b/hGameTest/build.hxml index e828b5f3..8fb3ad70 100644 --- a/hGameTest/build.hxml +++ b/hGameTest/build.hxml @@ -1,9 +1,31 @@ --lib actuate --lib lime +-main ApplicationMain + -lib openfl --lib hxp +-lib lime +-lib actuate +-lib hxcpp-debug-server ---main ApplicationMain -cp src - ---cpp bin/cpp \ No newline at end of file +-D lime-harfbuzz +-D lime-openal +-D lime-threads +-D tools=8.2.2 +-D lime-cairo +-D lime-opengl +# -D no-compilation +-D native +-D lime-curl +-D lime-native +-D lime-vorbis +-D openfl-native +-D lime-cffi +-D linux +-D desktop +-D debug +--remap flash:openfl +-cpp bin/cpp +--macro keep("Main") +--macro keep("Assets") +--macro keep("AssetMacro") +--macro addGlobalMetadata('', '@:build(engine.macros.ConVarDecorators.build())') +# --macro macros.MacroLoader.run() \ No newline at end of file diff --git a/hGameTest/src/ApplicationMain.hx b/hGameTest/src/ApplicationMain.hx index a2e6a8b5..102e9bf8 100644 --- a/hGameTest/src/ApplicationMain.hx +++ b/hGameTest/src/ApplicationMain.hx @@ -1,26 +1,39 @@ +/* + * Copyright (c) 2025 ${company} + * Author: ${author} + * License: ${license} + */ +// TODO: kanker package; +import openfl.events.Event; #if macro import haxe.macro.Compiler; import haxe.macro.Context; import haxe.macro.Expr; #end + @:access(lime.app.Application) @:access(lime.system.System) @:access(openfl.display.Stage) @:access(openfl.events.UncaughtErrorEvents) @:dox(hide) + class ApplicationMain { + /** + * [Description] + * + */ #if !macro public static function main() { - lime.system.System.__registerEntryPoint("::APP_FILE::", create); + lime.system.System.__registerEntryPoint("MinimalApplication", create); #if (js && html5) #if (munit || (utest && openfl_enable_utest_legacy_mode)) - lime.system.System.embed("::APP_FILE::", null, ::WIN_WIDTH::, ::WIN_HEIGHT::); + lime.system.System.embed("MinimalApplication", null, 800, 600); #end #else create(null); @@ -35,49 +48,48 @@ class ApplicationMain ManifestResources.init(config); #end - app.meta["build"] = "::meta.buildNumber::"; - app.meta["company"] = "::meta.company::"; - app.meta["file"] = "::APP_FILE::"; - app.meta["name"] = "::meta.title::"; - app.meta["packageName"] = "::meta.packageName::"; - app.meta["version"] = "::meta.version::"; + - ::if (config.hxtelemetry != null)::#if hxtelemetry - app.meta["hxtelemetry-allocations"] = "::config.hxtelemetry.allocations::"; - app.meta["hxtelemetry-host"] = "::config.hxtelemetry.host::"; - #end::end:: + app.meta["build"] = "48"; + app.meta["company"] = "Company Name"; + app.meta["file"] = "MinimalApplication"; + app.meta["name"] = "MinimalApplication"; + app.meta["packageName"] = "com.sample.minimalapplication"; + app.meta["version"] = "1.0.0"; + + #if !flash - ::foreach windows:: + var attributes:lime.ui.WindowAttributes = { - allowHighDPI: ::allowHighDPI::, - alwaysOnTop: ::alwaysOnTop::, - borderless: ::borderless::, - // display: ::display::, + allowHighDPI: false, + alwaysOnTop: false, + borderless: false, + // display: 0, element: null, - frameRate: ::fps::, - #if !web fullscreen: ::fullscreen::, #end - height: ::height::, - hidden: #if munit true #else ::hidden:: #end, - maximized: ::maximized::, - minimized: ::minimized::, - parameters: ::parameters::, - resizable: ::resizable::, - title: "::title::", - width: ::width::, - x: ::x::, - y: ::y::, + frameRate: 30, + #if !web fullscreen: false, #end + height: 600, + hidden: #if munit true #else false #end, + maximized: false, + minimized: false, + parameters: {}, + resizable: true, + title: "MinimalApplication", + width: 800, + x: null, + y: null, }; attributes.context = { - antialiasing: ::antialiasing::, - background: ::background::, - colorDepth: ::colorDepth::, - depth: ::depthBuffer::, - hardware: ::hardware::, - stencil: ::stencilBuffer::, + antialiasing: 0, + background: 16777215, + colorDepth: 32, + depth: true, + hardware: true, + stencil: true, type: null, - vsync: ::vsync:: + vsync: false }; if (app.window == null) @@ -103,13 +115,14 @@ class ApplicationMain } app.createWindow(attributes); - ::end:: + #elseif air - app.window.title = "::meta.title::"; + app.window.title = "MinimalApplication"; #else - app.window.context.attributes.background = ::WIN_BACKGROUND::; - app.window.frameRate = ::WIN_FPS::; + app.window.context.attributes.background = 16777215; + app.window.frameRate = 30; #end + var preloader = getPreloader(); app.preloader.onProgress.add (function(loaded, total) @@ -120,7 +133,6 @@ class ApplicationMain { @:privateAccess preloader.start(); }); - preloader.onComplete.add(start.bind((cast app.window:openfl.display.Window).stage)); #if !disable_preloader_assets @@ -188,7 +200,7 @@ class ApplicationMain { var hasMain = false; - switch (Context.follow(Context.getType("::APP_MAIN::"))) + switch (Context.follow(Context.getType("Main"))) { case TInst(t, params): @@ -204,7 +216,7 @@ class ApplicationMain if (hasMain) { - return Context.parse("@:privateAccess ::APP_MAIN::.main()", Context.currentPos()); + return Context.parse("@:privateAccess Main.main()", Context.currentPos()); } else if (type.constructor != null) { @@ -223,12 +235,12 @@ class ApplicationMain } else { - Context.fatalError("Main class \"::APP_MAIN::\" has neither a static main nor a constructor.", Context.currentPos()); + Context.fatalError("Main class \"Main\" has neither a static main nor a constructor.", Context.currentPos()); } default: - Context.fatalError("Main class \"::APP_MAIN::\" isn't a class.", Context.currentPos()); + Context.fatalError("Main class \"Main\" isn't a class.", Context.currentPos()); } return null; @@ -236,8 +248,8 @@ class ApplicationMain macro public static function getPreloader() { - ::if (PRELOADER_NAME != ""):: - var type = Context.getType("::PRELOADER_NAME::"); + + var type = Context.getType("HGamePreloader"); switch (type) { @@ -251,7 +263,7 @@ class ApplicationMain { return macro { - new ::PRELOADER_NAME::(); + new HGamePreloader(); }; } @@ -270,16 +282,12 @@ class ApplicationMain return macro { - new openfl.display.Preloader(new ::PRELOADER_NAME::()); + new openfl.display.Preloader(new HGamePreloader()); } - ::else:: - return macro - { - new openfl.display.Preloader(new openfl.display.Preloader.DefaultPreloader()); - }; - ::end:: + } + #if !macro @:noCompletion @:dox(hide) public static function __init__() { @@ -326,7 +334,7 @@ class ApplicationMain #if !macro @:build(DocumentClass.build()) -@:keep @:dox(hide) class DocumentClass extends ::APP_MAIN:: {} +@:keep @:dox(hide) class DocumentClass extends Main {} #else class DocumentClass { diff --git a/hGameTest/src/Assets.hx b/hGameTest/src/Assets.hx new file mode 100644 index 00000000..c526002b --- /dev/null +++ b/hGameTest/src/Assets.hx @@ -0,0 +1,29 @@ +package; + +/** + * Asset definition class using meta tags instead of project.xml + * The AssetMacro will process these declarations at compile time + */ +@:build(macros.AssetMacro.build()) // Fix: use fully qualified path with package name +@:keep +class Assets { + // Define image assets + @:Assets("assets/images/", "images") + private static var images:String; + + // Define sound assets + @:Assets("assets/sounds/", "sounds") + private static var sounds:String; + + // Define font assets + @:Assets("assets/fonts/", "fonts") + private static var fonts:String; + + // Define data assets + @:Assets("assets/data/", "data") + private static var data:String; + + // You can also define individual files if needed + // @:Assets("assets/images/logo.png", "images", "logo.png") + // public static var logo:String; +} \ No newline at end of file diff --git a/hGameTest/src/HGamePreloader.hx b/hGameTest/src/HGamePreloader.hx new file mode 100644 index 00000000..26950008 --- /dev/null +++ b/hGameTest/src/HGamePreloader.hx @@ -0,0 +1,200 @@ +package; + +import openfl.events.ProgressEvent; +import openfl.Lib; +import haxe.Timer; +import openfl.events.KeyboardEvent; +import openfl.text.TextField; +import openfl.display.Preloader; +import openfl.events.Event; +import openfl.display.Sprite; + +@:keep @:noCompletion #if display private #end class __ASSET__fonts_terminus_ttf extends lime.text.Font { + public function new() { + __fontPath = ManifestResources.rootPath + "fonts/Terminus.ttf"; + name = "Terminus (TTF)"; + super(); + } +} + +class HGamePreloader extends Sprite { + public static var preloadScreen:HGamePreloader; + + private var textField:TextField; + + public function new() { + super(); + + init(); + initUI(); + + // openfl.Lib.current.addChild(preloadScreen); + trace("Preloader initialized"); + textField.text = "Preloader ..."; + + this.addEventListener(Event.COMPLETE, onComplete); + this.addEventListener(ProgressEvent.PROGRESS, (e:ProgressEvent) -> { + trace(e.bytesLoaded + "/" + e.bytesTotal); + onUpdate(cast e.bytesLoaded, cast e.bytesTotal); + }); + Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, onDismiss); + } + + public function onComplete(event:Event) { + event.preventDefault(); + trace("Preloader complete"); + } + + public function pltrace(msg:String) { + trace(msg); + if (textField != null) { + textField.text += "\n" + msg; + } + } + + public var initialized:Bool = false; + + public function init() { + // Preload assets + trace("Preloading assets..."); + registerFonts(); + // Add your asset loading logic here + // For example, you can load images, sounds, etc. + // You can use openfl.Assets.loadBitmapData() or openfl.Assets.loadSound() to load assets + // and then add them to the preloadScreen or do any other processing you need. + + // Require clicking to start the game + // Add an event listener to the stage to check for input + initialized = true; + } + + public function onUpdate(loaded:Int, total:Int) { + trace("Preloader update: " + loaded + "/" + total); + // Update the preloader screen + // You can update the progress bar or any other UI elements here + // For example, you can use textField.text to show loading progress + // textField.text = "Loading... " + Math.round(progress * 100) + "%"; + if (textField != null) { + textField.text = "Loading... " + Math.round(loaded / total * 100) + "%"; + } + // You can also update the preloader screen's graphics or animations here + // For example, you can change the color or size of the preloader screen + + preloadScreen.graphics.clear(); + preloadScreen.graphics.beginFill(Math.random() * 0xFFFFFF); + preloadScreen.graphics.drawRect(0, 0, 800, 600); + preloadScreen.graphics.endFill(); + } + + public function registerFonts() { + @:privateAccess + var fontLibrary = openfl.utils.Assets.getLibrary("fonts"); + if (fontLibrary == null) { + trace("Font library not found"); + return; + } + var fontAssets:Array = fontLibrary.list('FONT'); + if (fontAssets == null) { + trace("Font assets not found"); + return; + } + for (fontAsset in fontAssets) { + trace('fontAsset: ${fontAsset}'); + var font = fontLibrary.getFont(fontAsset); + if (font == null) { + trace("Font not found: " + fontAsset); + continue; + } + trace(@:privateAccess font.__fontPath); + + openfl.text.Font.registerFont(font); + + trace("Font registered: " + fontAsset); + } + } + + public function onDismiss(event:KeyboardEvent) { + trace("Preloader dismissed"); + + if (preloadScreen == null || openfl.Lib.current == null) + return; + + if (event.keyCode != openfl.ui.Keyboard.ENTER) + return this.removeEventListener(KeyboardEvent.KEY_UP, onDismiss); + + openfl.Lib.current.removeChild(preloadScreen); + // var app = ApplicationMain.app; + dispatchEvent(new Event(Event.UNLOAD)); + } + + public function initUI() { + // Initialize the UI elements + trace("Initializing UI..."); + // You can add your UI initialization logic here + // For example, you can create buttons, sliders, etc. + // and add them to the preloadScreen or any other container + preloadScreen = this; + preloadScreen.graphics.beginFill(0x000000); + preloadScreen.graphics.drawRect(0, 0, 800, 600); + preloadScreen.graphics.endFill(); + preloadScreen.x = 0; + preloadScreen.y = 0; + preloadScreen.alpha = 0.5; + preloadScreen.scaleX = 1; + preloadScreen.scaleY = 1; + preloadScreen.mouseEnabled = false; + preloadScreen.mouseChildren = false; + preloadScreen.name = "preloadScreen"; + preloadScreen.visible = true; + preloadScreen.cacheAsBitmap = true; + preloadScreen.filters = []; + preloadScreen.transform.colorTransform = new openfl.geom.ColorTransform(1, 1, 1, 1, 0, 0, 0, 0); + preloadScreen.transform.matrix = new openfl.geom.Matrix(1, 0, 0, 1, 0, 0); + preloadScreen.transform.matrix3D = new openfl.geom.Matrix3D(); + + textField = new openfl.text.TextField(); + textField.x = 10; + textField.y = 10; + textField.width = 780; + textField.height = 580; + textField.multiline = true; + textField.wordWrap = true; + textField.selectable = false; + textField.autoSize = openfl.text.TextFieldAutoSize.LEFT; + textField.defaultTextFormat = new openfl.text.TextFormat("Arial", 12, 0xFFFFFF); + textField.embedFonts = true; + textField.antiAliasType = openfl.text.AntiAliasType.ADVANCED; + textField.background = true; + textField.backgroundColor = 0x000000; + textField.border = true; + textField.borderColor = 0xFFFFFF; + textField.textColor = 0xFFFFFF; + textField.text = "Loading..."; + textField.alpha = 1; + textField.scaleX = 1; + textField.scaleY = 1; + textField.mouseEnabled = false; + textField.name = "textField"; + textField.visible = true; + textField.cacheAsBitmap = true; + textField.filters = []; + textField.transform.colorTransform = new openfl.geom.ColorTransform(1, 1, 1, 1, 0, 0, 0, 0); + textField.transform.matrix = new openfl.geom.Matrix(1, 0, 0, 1, 0, 0); + textField.transform.matrix3D = new openfl.geom.Matrix3D(); + preloadScreen.addChild(textField); + } + + public function onKeyDown(event:KeyboardEvent) { + trace("Key pressed: " + event.keyCode); + + if (!initialized) + return; + + if (event.keyCode == openfl.ui.Keyboard.ENTER) { + // Start the game + trace("Starting game..."); + openfl.Lib.current.stage.removeChild(preloadScreen); + } + } +} diff --git a/hGameTest/src/Main.hx b/hGameTest/src/Main.hx index 527c92e0..d0131e8b 100644 --- a/hGameTest/src/Main.hx +++ b/hGameTest/src/Main.hx @@ -1,13 +1,8 @@ -import openfl.display.Window; -import assets.Scanner; -import game.video.Mode; import openfl.events.KeyboardEvent; import openfl.display.Sprite; -import openfl.display.Stage; import openfl.events.Event; import game.Input; import game.Game; -import game.video.Mode; class Main extends Sprite { public function new () { @@ -24,26 +19,13 @@ class Main extends Sprite { stage.addEventListener(KeyboardEvent.KEY_UP,Input.onKeyIsUp); stage.addEventListener(Event.RESIZE,onResize); game.onInit(); - //Mode.setVideoMode(1280, 960); + + } private function onResize (event:Event):Void { - //Here we can do shit with window scaling //stage.stageWidth; //stage.stageHeight; } - // static function main () { - // // Bootstrap the OpenFL project - // var application = new lime.app.Application(); - // application.createWindow({ - // hidden: false, - // x: 0, - // y: 0, - // width: 800, - // height: 600, - // title: "DSTEngine Window" - // }); - // } - } \ No newline at end of file diff --git a/hGameTest/src/ManifestResources.hx b/hGameTest/src/ManifestResources.hx new file mode 100644 index 00000000..25d0ba8c --- /dev/null +++ b/hGameTest/src/ManifestResources.hx @@ -0,0 +1,61 @@ +package; + +#if sys +import haxe.io.Path; +import openfl.utils.AssetLibrary; +import openfl.utils.AssetManifest; +import openfl.utils.Assets as LimeAssets; +import sys.FileSystem; + +@:access(lime.utils.Assets) +@:keep class ManifestResources { + public static var preloadLibraries:Array = []; + public static var preloadLibraryNames:Array = []; + public static var rootPath:String; + + public static function init(config:Dynamic):Void { + loadManifests(); + } + public static function loadManifests() { + rootPath = ""; + + var manifestDirPath = Path.join(["./manifest"]); + + var manifestFiles = FileSystem.readDirectory(manifestDirPath); + + for (manifestFile in manifestFiles) { + var manifestPath = Path.join([manifestDirPath, manifestFile]); + + if (!StringTools.endsWith(manifestFile, ".json")){ + trace('${manifestFile} is not a json file, skipping...'); + continue; + } + + loadManifest(manifestPath, manifestFile.split(".json")[0]); + } + } + + public static function loadManifest(manifestPath, manifestName) { + if (!FileSystem.exists(manifestPath)) { + trace("Manifest file not found: " + manifestPath); + return; + } + try { + var manifest = AssetManifest.fromFile(manifestPath); + if (manifest != null) { + var library = AssetLibrary.fromManifest(manifest); + if (library != null) { + trace("Library: `"+library+"`"); + trace("manifestName: `"+manifestName+"`"); + preloadLibraries.push(library); + preloadLibraryNames.push(manifestName); + // LimeAssets.registerLibrary(manifestName, library); + openfl.utils.Assets.registerLibrary(manifestName, library); + } + } + } catch (e:Dynamic) { + trace("Error loading asset manifest: " + e); + } + } +} +#end diff --git a/hGameTest/src/game/ui/text/TextFormats.hx b/hGameTest/src/game/ui/text/TextFormats.hx index d87a6570..2fb379d9 100644 --- a/hGameTest/src/game/ui/text/TextFormats.hx +++ b/hGameTest/src/game/ui/text/TextFormats.hx @@ -10,8 +10,16 @@ class TextFormats{ public var cInputFmt:TextFormat; public var cOutputFmt:TextFormat; public function new(){ + var font = Assets.getFont("fonts/Terminus.ttf"); cInputFmt = new TextFormat(Assets.getFont("fonts/Terminus.ttf").fontName, 24, 0xffffff); cOutputFmt = new TextFormat(Assets.getFont("fonts/Terminus.ttf").fontName, 24, 0xffffff); + trace('font: ${font}'); + trace('fontName: ${font.fontName}'); + trace('fontStyle: ${font.fontStyle}'); + trace('fontType: ${font.fontType}'); + trace('fontGlyphs: ${font.getGlyphs()}'); + trace(cInputFmt.font); + trace(cOutputFmt.font); } public static function getFormats():TextFormats { diff --git a/hGameTest/src/macros/AssetMacro.hx b/hGameTest/src/macros/AssetMacro.hx new file mode 100644 index 00000000..2f3a85f2 --- /dev/null +++ b/hGameTest/src/macros/AssetMacro.hx @@ -0,0 +1,269 @@ +package macros; + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +import sys.FileSystem; +import sys.io.File; +import haxe.Json; +using StringTools; + +/** + * Macro for processing @:Assets meta tags and building asset manifests + */ +@:keep +class AssetMacro { + // Track all assets processed + private static var assetMap:Map> = new Map(); + + /** + * Entry point for the macro - called during compilation + */ + public static function build():Array { + #if macro + Sys.println("AssetMacro.build() called - macro is running"); + Context.warning("AssetMacro is running", Context.currentPos()); + #end + + // Get current build context + var fields = Context.getBuildFields(); + var localClass = Context.getLocalClass().get(); + + #if macro + Sys.println("Processing class: " + localClass.name); + #end + + // Get the output directory from build.hxml + var outputDir = getOutputDir(); + var manifestDir = outputDir + "/manifest"; + var assetsOutputDir = outputDir + "/assets"; + + // Create directories if they don't exist + createDirectory(manifestDir); + createDirectory(assetsOutputDir); + + // Process asset fields + for (field in fields) { + processField(field, assetsOutputDir); + } + + // Build the manifest files + buildManifests(manifestDir); + + return fields; + } + + /** + * Process a field for @:Assets meta + */ + private static function processField(field:Field, assetsOutputDir:String) { + // Look for our custom meta + var assetsMeta = findAssetsMeta(field.meta); + if (assetsMeta == null) return; + + var params = assetsMeta.params; + if (params.length < 2) { + Context.error("@:Assets meta requires at least 2 parameters: source path and library name", assetsMeta.pos); + return; + } + + // Extract parameters + var sourcePath = extractString(params[0]); + var libraryName = extractString(params[1]); + var targetPath = params.length > 2 ? extractString(params[2]) : null; + + // Process directory or single file + if (FileSystem.exists(sourcePath)) { + if (FileSystem.isDirectory(sourcePath)) { + processDirectory(sourcePath, libraryName, assetsOutputDir); + } else { + processSingleFile(sourcePath, libraryName, targetPath, assetsOutputDir); + } + } else { + Context.warning('Asset source not found: ${sourcePath}', field.pos); + } + } + + /** + * Process a directory of assets + */ + private static function processDirectory(sourcePath:String, libraryName:String, assetsOutputDir:String) { + if (!sourcePath.endsWith("/")) sourcePath += "/"; + var targetDir = assetsOutputDir + "/" + libraryName + "/"; + createDirectory(targetDir); + + function scanDir(path:String, relPath:String = "") { + if (!FileSystem.exists(path)) return; + + for (entry in FileSystem.readDirectory(path)) { + var fullPath = path + entry; + var relAssetPath = relPath + entry; + + if (FileSystem.isDirectory(fullPath)) { + var newRelPath = relPath + entry + "/"; + var newTargetDir = targetDir + newRelPath; + createDirectory(newTargetDir); + scanDir(fullPath + "/", newRelPath); + } else { + // Copy file and register in asset map + var targetPath = targetDir + relAssetPath; + try { + File.copy(fullPath, targetPath); + + // Register asset + if (!assetMap.exists(libraryName)) { + assetMap.set(libraryName, []); + } + + assetMap.get(libraryName).push({ + id: relAssetPath, + path: relAssetPath, + size: FileSystem.stat(fullPath).size, + type: getAssetType(fullPath) + }); + } catch (e) { + Context.warning('Failed to copy asset: ${fullPath} -> ${targetPath}. Error: ${e}', Context.currentPos()); + } + } + } + } + + scanDir(sourcePath); + } + + /** + * Process a single file asset + */ + private static function processSingleFile(sourcePath:String, libraryName:String, targetPath:String, assetsOutputDir:String) { + var targetDir = assetsOutputDir + "/" + libraryName + "/"; + createDirectory(targetDir); + + var fileName = targetPath != null ? targetPath : sourcePath.split("/").pop(); + var destPath = targetDir + fileName; + + try { + File.copy(sourcePath, destPath); + + // Register asset + if (!assetMap.exists(libraryName)) { + assetMap.set(libraryName, []); + } + + assetMap.get(libraryName).push({ + id: fileName, + path: fileName, + size: FileSystem.stat(sourcePath).size, + type: getAssetType(sourcePath) + }); + } catch (e) { + Context.warning('Failed to copy asset: ${sourcePath} -> ${destPath}. Error: ${e}', Context.currentPos()); + } + } + + /** + * Build manifest JSON files for each library + */ + private static function buildManifests(manifestDir:String) { + // Create an array to hold all assets for the default manifest + var allAssets = []; + + // First, process individual library manifests + for (library in assetMap.keys()) { + var assets = assetMap.get(library); + var manifest = { + name: library, + assets: assets, + rootPath: "../assets/" + library + "/" + }; + + // Add assets to combined collection with library prefix + for (asset in assets) { + allAssets.push({ + id: library + "/" + asset.id, + path: library + "/" + asset.path, + size: asset.size, + type: asset.type + }); + } + + var manifestPath = manifestDir + "/" + library + ".json"; + try { + var content = Json.stringify(manifest, null, " "); + File.saveContent(manifestPath, content); + Context.info('Built manifest: ${manifestPath} with ${assets.length} assets', Context.currentPos()); + } catch (e) { + Context.warning('Failed to write manifest: ${manifestPath}. Error: ${e}', Context.currentPos()); + } + } + + // Create the default manifest with all assets + var defaultManifest = { + name: "default", + assets: allAssets, + rootPath: "../assets/" + }; + + // Save the default manifest + var defaultManifestPath = manifestDir + "/default.json"; + try { + var content = Json.stringify(defaultManifest, null, " "); + File.saveContent(defaultManifestPath, content); + Context.info('Built default manifest: ${defaultManifestPath} with ${allAssets.length} assets', Context.currentPos()); + } catch (e) { + Context.warning('Failed to write default manifest: ${defaultManifestPath}. Error: ${e}', Context.currentPos()); + } + } + + // Helper functions + + private static function getOutputDir():String { + // Try to parse the --cpp argument from the build.hxml + var args = Sys.args(); + for (i in 0...args.length) { + if (args[i] == "--cpp" && i < args.length - 1) { + return args[i + 1]; + } + } + return "bin/cpp"; // Default output directory + } + + private static function createDirectory(path:String) { + if (!FileSystem.exists(path)) { + try { + FileSystem.createDirectory(path); + } catch (e) { + Context.warning('Failed to create directory: ${path}. Error: ${e}', Context.currentPos()); + } + } + } + + private static function findAssetsMeta(metaAccess:Array):MetadataEntry { + if (metaAccess == null) return null; + + for (meta in metaAccess) { + if (meta.name == ":Assets" || meta.name == "Assets") { + return meta; + } + } + return null; + } + + private static function extractString(expr:Expr):String { + switch (expr.expr) { + case EConst(CString(s)): return s; + default: Context.error("Expected string literal", expr.pos); + } + return null; + } + + private static function getAssetType(path:String):String { + var ext = path.split(".").pop().toLowerCase(); + return (switch (ext) { + case "jpg", "jpeg", "png", "gif", "bmp": "image"; + case "mp3", "ogg", "wav": "sound"; + case "ttf", "otf": "font"; + case "json", "xml", "txt": "text"; + default: "binary"; + }).toUpperCase(); + } +} \ No newline at end of file diff --git a/standalone-openfl-app/compile.sh b/standalone-openfl-app/compile.sh index 7c1595d1..a3576f0a 100755 --- a/standalone-openfl-app/compile.sh +++ b/standalone-openfl-app/compile.sh @@ -1,15 +1,9 @@ #!/bin/bash +set -e # Compile the project using the Haxe compiler with the specified build.hxml configuration haxe build.hxml -# Check if the compilation was successful -if [ $? -eq 0 ]; then - echo "Compilation successful!" -else - echo "Compilation failed!" -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) \ No newline at end of file +# Run beside the staged manifest, assets and native libraries. +(cd ./bin/cpp; ./ApplicationMain) \ No newline at end of file diff --git a/standalone-openfl-app/src/Main.hx b/standalone-openfl-app/src/Main.hx index ce75fdcf..074e95cf 100644 --- a/standalone-openfl-app/src/Main.hx +++ b/standalone-openfl-app/src/Main.hx @@ -9,8 +9,6 @@ class Main extends Sprite { public function new() { super(); - Lib.current.addChild(this); - // Draw background to verify app is running graphics.beginFill(0x3498db, 1); graphics.drawRect(0, 0, 800, 600); diff --git a/standalone-openfl-app/src/ManifestResources.hx b/standalone-openfl-app/src/ManifestResources.hx index 9c9048ae..9e30ec77 100644 --- a/standalone-openfl-app/src/ManifestResources.hx +++ b/standalone-openfl-app/src/ManifestResources.hx @@ -1,10 +1,8 @@ package; -#if sys import haxe.io.Path; -import openfl.utils.AssetLibrary; -import openfl.utils.AssetManifest; -import openfl.utils.Assets as LimeAssets; +import lime.utils.AssetLibrary; +import lime.utils.Assets; import sys.FileSystem; @:access(lime.utils.Assets) @@ -14,44 +12,32 @@ import sys.FileSystem; public static var rootPath:String; public static function init(config:Dynamic):Void { - rootPath = ""; + preloadLibraries = []; + preloadLibraryNames = []; + rootPath = null; + + if (config != null && Reflect.hasField(config, "rootPath")) { + rootPath = Reflect.field(config, "rootPath"); + if (!StringTools.endsWith(rootPath, "/")) rootPath += "/"; + } + if (rootPath == null) rootPath = lime.system.System.applicationDirectory; + + var manifestDirPath = Path.join([rootPath, "manifest"]); + if (!FileSystem.exists(manifestDirPath) || !FileSystem.isDirectory(manifestDirPath)) { + throw 'Asset manifest directory not found: $manifestDirPath'; + } - var manifestDirPath = Path.join(["manifest"]); var manifests = FileSystem.readDirectory(manifestDirPath); + manifests.sort(Reflect.compare); for (manifest in manifests) { - trace(manifest); if (StringTools.endsWith(manifest, ".json")) { - var manifestPath = Path.join([manifestDirPath, manifest]); - loadManifest(manifestPath, manifest.split(".json")[0]); - } - else{ - throw("we eating shit tonight"); - - } - } - } + var manifestName = Path.withoutExtension(manifest); + Assets.libraryPaths[manifestName] = Path.join([manifestDirPath, manifest]); - public static function loadManifest(manifestPath, manifestName) { - if (!FileSystem.exists(manifestPath)) { - trace("Manifest file not found: " + manifestPath); - return; - } - try { - var manifest = AssetManifest.fromFile(manifestPath); - if (manifest != null) { - var library = AssetLibrary.fromManifest(manifest); - if (library != null) { - trace("Library: `"+library+"`"); - trace("manifestName: `"+manifestName+"`"); - preloadLibraries.push(library); - preloadLibraryNames.push(manifestName); - // LimeAssets.registerLibrary(manifestName, library); - openfl.utils.Assets.registerLibrary(manifestName, library); - } + var library = Assets.getLibrary(manifestName); + if (library != null) preloadLibraries.push(library); + else preloadLibraryNames.push(manifestName); } - } catch (e:Dynamic) { - trace("Error loading asset manifest: " + e); } } } -#end diff --git a/standalone-openfl-app/src/macros/AssetMacro.hx b/standalone-openfl-app/src/macros/AssetMacro.hx index 2f3a85f2..a659d1a8 100644 --- a/standalone-openfl-app/src/macros/AssetMacro.hx +++ b/standalone-openfl-app/src/macros/AssetMacro.hx @@ -25,6 +25,9 @@ class AssetMacro { Context.warning("AssetMacro is running", Context.currentPos()); #end + // A compilation server can retain macro statics between builds. + assetMap = new Map(); + // Get current build context var fields = Context.getBuildFields(); var localClass = Context.getLocalClass().get(); @@ -41,6 +44,7 @@ class AssetMacro { // Create directories if they don't exist createDirectory(manifestDir); createDirectory(assetsOutputDir); + copyLimeNativeLibrary(outputDir); // Process asset fields for (field in fields) { @@ -237,6 +241,44 @@ class AssetMacro { } } + /** Stage the Lime CFFI module that matches the selected haxelib version. */ + private static function copyLimeNativeLibrary(outputDir:String):Void { + var platformDirectory = if (Context.defined("linux")) { + "Linux64"; + } else if (Context.defined("windows")) { + "Windows64"; + } else if (Context.defined("mac")) { + Context.defined("arm64") ? "MacArm64" : "Mac64"; + } else { + return; + } + + var limeRoot:String = null; + for (classPath in Context.getClassPath()) { + var normalized = classPath.split("\\").join("/"); + if (normalized.indexOf("/lime/") != -1 && normalized.endsWith("/src/")) { + limeRoot = normalized.substr(0, normalized.length - 4); + break; + } + } + if (limeRoot == null) { + Context.warning("Could not locate Lime native library from the compiler class path", Context.currentPos()); + return; + } + + var source = limeRoot + "ndll/" + platformDirectory + "/lime.ndll"; + if (!FileSystem.exists(source)) { + Context.warning("Lime native library not found: " + source, Context.currentPos()); + return; + } + + try { + File.copy(source, outputDir + "/lime.ndll"); + } catch (e:Dynamic) { + Context.error("Failed to copy Lime native library: " + e, Context.currentPos()); + } + } + private static function findAssetsMeta(metaAccess:Array):MetadataEntry { if (metaAccess == null) return null;