Browse Source

initial work on resource management (in progress)

ncannasse 12 years ago
parent
commit
109ebf1296
12 changed files with 550 additions and 0 deletions
  1. 10 0
      hxd/Res.hx
  2. 41 0
      hxd/res/Any.hx
  3. 23 0
      hxd/res/Directory.hx
  4. 30 0
      hxd/res/FileEntry.hx
  5. 7 0
      hxd/res/FileSystem.hx
  6. 185 0
      hxd/res/FileTree.hx
  7. 4 0
      hxd/res/Font.hx
  8. 61 0
      hxd/res/Loader.hx
  9. 167 0
      hxd/res/LocalFileSystem.hx
  10. 9 0
      hxd/res/Model.hx
  11. 4 0
      hxd/res/Sound.hx
  12. 9 0
      hxd/res/Texture.hx

+ 10 - 0
hxd/Res.hx

@@ -0,0 +1,10 @@
+package hxd;
+
+@:build(hxd.res.FileTree.build())
+class Res {
+	
+	public static function load(name:String) {
+		return loader.load(name);
+	}
+	
+}

+ 41 - 0
hxd/res/Any.hx

@@ -0,0 +1,41 @@
+package hxd.res;
+
+class Any {
+	
+	var path : String;
+	var r : Dynamic;
+	
+	public function new(path, r) {
+		this.path = path;
+		this.r = r;
+	}
+	
+	public function toModel() {
+		var m = Std.instance(r, Model);
+		if( m == null ) throw path + " is not a model";
+		return m.get();
+	}
+
+	public function toTexture() {
+		var t = Std.instance(r, Texture);
+		if( t == null ) throw path + " is not a texture";
+		return t.load();
+	}
+
+	public function toSound() {
+		var s = Std.instance(r, Sound);
+		if( s == null ) throw path + " is not a sound";
+		return s;
+	}
+	
+	public function toDir() {
+		var d = Std.instance(r, Directory);
+		if( d == null ) throw path + " is not a directory";
+		return d;
+	}
+
+	public function get() {
+		return r;
+	}
+	
+}

+ 23 - 0
hxd/res/Directory.hx

@@ -0,0 +1,23 @@
+package hxd.res;
+
+class Directory {
+	
+	var loader : Loader;
+	var path : String;
+	var dir : {};
+	
+	function new(loader, path, dir) {
+		this.loader = loader;
+		this.path = path;
+		this.dir = dir;
+	}
+	
+	public inline function iterator() {
+		return new hxd.impl.ArrayIterator([for( f in Reflect.fields(dir) ) loader.load(path+"/"+f)]);
+	}
+	
+	public function exists( name : String ) {
+		return loader.load(path + "/" + name);
+	}
+	
+}

+ 30 - 0
hxd/res/FileEntry.hx

@@ -0,0 +1,30 @@
+package hxd.res;
+
+class FileEntry {
+	
+	public var name(default, null) : String;
+	public var path(get, never) : String;
+	public var size(get, never) : Int;
+	public var isDirectory(get, never) : Bool;
+	public var isAvailable(get, never) : Bool;
+	
+	public function getBytes() : haxe.io.Bytes return null;
+	
+	public function open() {}
+	public function read( out : haxe.io.Bytes, pos : Int, size : Int ) {}
+	public function close() {}
+	
+	public function load( ?onReady : Void -> Void ) : Void {}
+	public function loadBitmap( onLoaded : hxd.BitmapData -> Void ) : Void {}
+
+	public function exists( name : String ) : Bool return false;
+	public function get( name : String ) : FileEntry return null;
+	
+	public function iterator() : hxd.impl.ArrayIterator<FileEntry> return null;
+	
+	function get_isAvailable() return true;
+	function get_isDirectory() return false;
+	function get_size() return 0;
+	function get_path() return name;
+	
+}

+ 7 - 0
hxd/res/FileSystem.hx

@@ -0,0 +1,7 @@
+package hxd.res;
+
+interface FileSystem {
+	public function getRoot() : FileEntry;
+	public function get( path : String ) : FileEntry;
+	public function exists( path : String ) : Bool;
+}

+ 185 - 0
hxd/res/FileTree.hx

@@ -0,0 +1,185 @@
+package hxd.res;
+import haxe.macro.Context;
+import haxe.macro.Expr;
+
+typedef FileEntry = { e : Expr, t : ComplexType, ?d : Dynamic };
+
+class FileTree {
+	
+	var path : String;
+	var currentModule : String;
+	var pos : Position;
+	var loaderType : ComplexType;
+	
+	function new(dir) {
+		this.path = resolvePath(dir);
+		currentModule = Std.string(Context.getLocalClass());
+		pos = Context.currentPos();
+	}
+	
+	function resolvePath(dir:Null<String>) {
+		var resolve = true;
+		if( dir == null ) {
+			dir = Context.definedValue("resourcesPath");
+			if( dir == null ) dir = "res" else resolve = false;
+		}
+		var pos = Context.currentPos();
+		if( resolve )
+			dir = try Context.resolvePath(dir) catch( e : Dynamic ) Context.error("Resource directory not found in classpath '" + dir + "'", pos);
+		var path = sys.FileSystem.fullPath(dir);
+		if( !sys.FileSystem.exists(path) || !sys.FileSystem.isDirectory(path) )
+			Context.error("Resource directory does not exists '" + path + "'", pos);
+		return path;
+	}
+	
+	function scan() {
+		var fields = Context.getBuildFields();
+		var dict = new Map();
+		for( f in fields ) {
+			if( Lambda.has(f.access,AStatic) ) {
+				dict.set(f.name, "class declaration");
+				if( f.name == "loader" )
+					loaderType = switch( f.kind ) {
+					case FVar(t, _), FProp(_, _, t, _): t;
+					default: null;
+					}
+			}
+		}
+		if( loaderType == null ) {
+			loaderType = macro : hxd.res.Loader;
+			dict.set("loader", "reserved identifier");
+			fields.push({
+				name : "loader",
+				access : [APublic, AStatic],
+				kind : FVar(loaderType),
+				pos : pos,
+			});
+		}
+		var data = scanRec("", fields, dict);
+		
+		fields.push({
+			name : "_ROOT",
+			access : [AStatic],
+			kind : FVar(null, { expr : EConst(CString(haxe.Serializer.run(data))), pos : pos } ),
+			pos : pos,
+		});
+		
+		return fields;
+	}
+	
+	function scanRec( relPath : String, fields : Array<Field>, dict : Map<String,String> ) {
+		var dir = this.path + relPath;
+		var data = { };
+		// make sure to rescan if one of the directories content has changed (file added or deleted)
+		Context.registerModuleDependency(currentModule, dir);
+		for( f in sys.FileSystem.readDirectory(dir) ) {
+			var path = dir + "/" + f;
+			var fileName = f;
+			var field = null;
+			var ext = null;
+			if( sys.FileSystem.isDirectory(path) ) {
+				switch( f ) {
+				case ".svn", ".git":
+					// don't look into these
+				default:
+					field = handleDir(f, relPath+"/"+f, path);
+				}
+			} else {
+				var extParts = f.split(".");
+				var noExt = extParts.shift();
+				ext = extParts.join(".");
+				field = handleFile(f, ext, relPath + "/" + f, path);
+				f = noExt;
+			}
+			if( field != null ) {
+				var other = dict.get(f);
+				if( other != null ) {
+					Context.warning("Resource " + relPath + "/" + f + " is used by both " + relPath + "/" + fileName + " and " + other, pos);
+					continue;
+				}
+				dict.set(f, relPath + "/" + fileName);
+				fields.push({
+					name : f,
+					pos : pos,
+					kind : FProp("get","never",field.t),
+					access : [AStatic, APublic],
+				});
+				fields.push({
+					name : "get_" + f,
+					pos : pos,
+					kind : FFun({
+						args : [],
+						params : [],
+						ret : field.t,
+						expr : { expr : EMeta({ name : ":privateAccess", params : [], pos : pos }, { expr : EReturn(field.e), pos : pos }), pos : pos },
+					}),
+					meta : [ { name:":extern", pos:pos, params:[] } ],
+					access : [AStatic, AInline],
+				});
+				if( field.d == null && ext != null )
+					field.d = ext;
+				else if( ext != null )
+					field.d._e = ext;
+				Reflect.setField(data, f, field.d);
+			}
+		}
+		return data;
+	}
+	
+	function handleDir( dir : String, relPath : String, fullPath : String ) : FileEntry {
+		var ofields = [];
+		var dict = new Map();
+		dict.set("loader", "reserved identifier");
+		var data = scanRec(relPath, ofields, dict);
+		if( ofields.length == 0 )
+			return null;
+		var name = "R" + (~/[^A-Za-z0-9_]/g.replace(fullPath, "_"));
+		for( f in ofields )
+			f.access.remove(AStatic);
+		var def = macro class {
+			public inline function new(loader) this = loader;
+			var loader(get,never) : $loaderType;
+			inline function get_loader() : $loaderType return this;
+		};
+		for( f in def.fields )
+			ofields.push(f);
+		Context.defineType( {
+			pack : ["hxd", "_res"],
+			name : name,
+			pos : pos,
+			meta : [{ name : ":dce", params : [], pos : pos }],
+			isExtern : false,
+			fields : ofields,
+			params : [],
+			kind : TDAbstract(loaderType),
+		});
+		var tpath = { pack : ["hxd", "_res"], name : name, params : [] };
+		return {
+			t : TPath(tpath),
+			e : { expr : ENew(tpath, [macro loader]), pos : pos },
+			d : data,
+		};
+	}
+	
+	function handleFile( file : String, ext : String, relPath : String, fullPath : String ) : FileEntry {
+		var epath = { expr : EConst(CString(relPath)), pos : pos };
+		switch( ext.toLowerCase() ) {
+		case "jpg", "png":
+			return { e : macro loader.loadTexture($epath), t : macro : hxd.res.Texture };
+		case "fbx", "xbx":
+			return { e : macro loader.loadModel($epath), t : macro : hxd.res.Model };
+		case "ttf":
+			return { e : macro loader.loadFont($epath), t : macro : hxd.res.Font };
+		case "wav", "mp3":
+			return { e : macro loader.loadSound($epath), t : macro : hxd.res.Sound };
+		default:
+			Context.warning("File extension not supported '." + ext + "'", Context.makePosition( { min : 0, max : 0, file : fullPath } ));
+		}
+		return null;
+	}
+	
+	public static function build( ?dir : String ) {
+		return new FileTree(dir).scan();
+	}
+	
+}

+ 4 - 0
hxd/res/Font.hx

@@ -0,0 +1,4 @@
+package hxd.res;
+
+class Font {
+}

+ 61 - 0
hxd/res/Loader.hx

@@ -0,0 +1,61 @@
+package hxd.res;
+
+class Loader {
+	
+	var root : Dynamic;
+	
+	public function new(?root) {
+		if( root == null )
+			root = @:privateAccess haxe.Unserializer.run(hxd.Res._ROOT);
+		this.root = root;
+	}
+
+	function get(path:String) : Dynamic {
+		var r = root;
+		for( p in path.split("/") )
+			r = Reflect.field(r, p);
+		return r;
+	}
+	
+	public function exists( path : String ) : Bool {
+		return get(path) != null;
+	}
+	
+	function resolveDynamic( path : String, ext : String ) : Dynamic {
+		return switch( ext.toLowerCase() ) {
+		case "fbx", "xbx": loadModel(path);
+		case "png", "jpg": loadTexture(path);
+		case "ttf": loadFont(path);
+		case "wav", "mp3": loadSound(path);
+		default: throw "Unknown extension " + ext;
+		};
+	}
+	
+	public function load( path : String ) : Any {
+		var inf : Dynamic = get(path);
+		if( inf == null ) throw "Resource not found '" + path + "'";
+		if( Std.is(inf, String) )
+			return new Any(path, resolveDynamic(path, inf));
+		var ext = inf._e;
+		if( ext != null )
+			return new Any(path, resolveDynamic(path, ext));
+		return new Any(path, @:privateAccess new Directory(this,path,inf));
+	}
+	
+	function loadModel( path : String ) : Model {
+		return null;
+	}
+	
+	function loadTexture( path : String ) : Texture {
+		return null;
+	}
+	
+	function loadFont( path : String ) : Font {
+		return null;
+	}
+	
+	function loadSound( path : String ) : Sound {
+		return null;
+	}
+
+}

+ 167 - 0
hxd/res/LocalFileSystem.hx

@@ -0,0 +1,167 @@
+package hxd.res;
+
+@:allow(hxd.res.LocalFileSystem)
+@:access(hxd.res.LocalFileSystem)
+private class LocalEntry extends FileEntry {
+	
+	var fs : LocalFileSystem;
+	var relPath : String;
+	#if air3
+	var file : flash.filesystem.File;
+	var fread : flash.filesystem.FileStream;
+	#else
+	var file : Dynamic;
+	#end
+
+	function new(fs, name, relPath, file) {
+		this.name = name;
+		this.relPath = relPath;
+		this.file = file;
+	}
+
+	override function getBytes() : haxe.io.Bytes {
+		#if air3
+		var fs = new flash.filesystem.FileStream();
+		fs.open(file, flash.filesystem.FileMode.READ);
+		var bytes = haxe.io.Bytes.alloc(fs.bytesAvailable);
+		fs.readBytes(bytes.getData());
+		fs.close();
+		return bytes;
+		#else
+		return null;
+		#end
+	}
+	
+	override function open() {
+		#if air3
+		if( fread != null ) fread.close();
+		fread = new flash.filesystem.FileStream();
+		fread.open(file, flash.filesystem.FileMode.READ);
+		#end
+	}
+	
+	override function read( out : haxe.io.Bytes, pos : Int, size : Int ) : Void {
+		#if air3
+		if( fread == null ) throw "File not opened";
+		fread.readBytes(out.getData(), pos, size);
+		#end
+	}
+
+	override function close() {
+		#if air3
+		if( fread != null ) {
+			fread.close();
+			fread = null;
+		}
+		#end
+	}
+	
+	override function load( ?onReady : Void -> Void ) : Void {
+		if( onReady != null ) haxe.Timer.delay(onReady, 1);
+	}
+	
+	override function loadBitmap( onLoaded : hxd.BitmapData -> Void ) : Void {
+		#if flash
+		var loader = new flash.display.Loader();
+		loader.contentLoaderInfo.addEventListener(flash.events.IOErrorEvent.IO_ERROR, function(e:flash.events.IOErrorEvent) {
+			throw Std.string(e) + " while loading " + relPath;
+		});
+		loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, function(_) {
+			var content : flash.display.Bitmap = cast loader.content;
+			onLoaded(hxd.BitmapData.fromNative(content.bitmapData));
+			loader.unload();
+		});
+		loader.load(new flash.net.URLRequest(file.url));
+		#else
+		#end
+	}
+	
+	override function get_path() {
+		return relPath == null ? "<root>" : relPath;
+	}
+	
+	override function exists( name : String ) {
+		return fs.exists(relPath == null ? name : relPath + "/" + name);
+	}
+	
+	override function get( name : String ) {
+		return fs.get(relPath == null ? name : relPath + "/" + name);
+	}
+	
+	override function get_size() {
+		#if air3
+		return Std.int(file.size);
+		#else
+		return 0;
+		#end
+	}
+
+	override function iterator() {
+		#if air3
+		var arr = new Array<FileEntry>();
+		for( f in file.getDirectoryListing() )
+			switch( f.name ) {
+			case ".svn", ".git" if( f.isDirectory ):
+				continue;
+			default:
+				arr.push(new LocalEntry(fs, f.name, relPath == null ? f.name : relPath + "/" + f.name, f));
+			}
+		return new hxd.impl.ArrayIterator(arr);
+		#else
+		#end
+	}
+	
+}
+
+class LocalFileSystem implements FileSystem {
+	
+	var baseDir : String;
+	var root : FileEntry;
+	
+	public function new( baseDir : String ) {
+		this.baseDir = baseDir;
+		#if air3
+		var froot = new flash.filesystem.File(flash.filesystem.File.applicationDirectory.nativePath + "/" + baseDir);
+		if( !froot.exists ) throw "Could not find dir " + baseDir;
+		this.baseDir = froot.nativePath;
+		this.baseDir = this.baseDir.split("\\").join("/");
+		if( !StringTools.endsWith(this.baseDir, "/") ) this.baseDir += "/";
+		root = new LocalEntry(this, "root", null, froot);
+		#end
+	}
+	
+	public function getRoot() : FileEntry {
+		return root;
+	}
+
+	#if air3
+	function open( path : String ) {
+		var f = new flash.filesystem.File(baseDir + path);
+		// ensure exact case / no relative path
+		if( f.nativePath.split("\\").join("/") != baseDir + path )
+			return null;
+		return f;
+	}
+	#end
+	
+	public function exists( path : String ) {
+		#if air3
+		var f = open(path);
+		return f != null && f.exists;
+		#else
+		return false;
+		#end
+	}
+	
+	public function get( path : String ) {
+		#if air3
+		var f = open(path);
+		if( f == null || !f.exists )
+			throw "File not found " + path;
+		return new LocalEntry(this, path.split("/").pop(), path, f);
+		#else
+		return null;
+		#end
+	}
+	
+}

+ 9 - 0
hxd/res/Model.hx

@@ -0,0 +1,9 @@
+package hxd.res;
+
+class Model {
+	
+	public function get() : h3d.fbx.Library {
+		return null;
+	}
+	
+}

+ 4 - 0
hxd/res/Sound.hx

@@ -0,0 +1,4 @@
+package hxd.res;
+
+class Sound {
+}

+ 9 - 0
hxd/res/Texture.hx

@@ -0,0 +1,9 @@
+package hxd.res;
+
+class Texture {
+	
+	public function load( ?hasAlpha : Bool ) : h3d.mat.Texture {
+		return null;
+	}
+	
+}