123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- package hrt.impl;
- import haxe.macro.Context;
- import haxe.macro.Expr;
- import haxe.macro.Type;
- class Macros {
- public static function enumOrNullByName<T>(e:Enum<T>, constr:String, ?params:Array<Dynamic>):T {
- var value = try {
- haxe.EnumTools.createByName(e, constr, params);
- } catch (_) {
- null;
- };
- if (value == null) {
- var defaultConstructors = Type.allEnums(e);
- if (defaultConstructors.length > 0) value = defaultConstructors[0];
- }
- return value;
- }
- // Get the field in the specified field path or null if any element of the path is not null
- static function getOrDefault(path: Array<String>, ?startIndex: Int, ?defaultValue: Expr) : Expr {
- function recursive(path: Array<String>, index : Int, defaultValue: Expr) : Expr {
- if (index == path.length - 1) {
- return macro $p{path};
- }
- else {
- var subpath = path.slice(0, index+1);
- return macro $p{subpath} != null ? ${recursive(path, index+1, defaultValue)} : $defaultValue;
- }
- }
- return recursive(path, startIndex != null ? startIndex : 0, defaultValue != null ? defaultValue : macro null);
- }
- static function forEachFieldInType(t: Type, path: Array<String>, pos, func: (t: Type, path : Array<String>, pos: Position) -> Void) : Void {
- switch(t) {
- case TAnonymous(a):
- for (f in a.get().fields) {
- path.push(f.name);
- forEachFieldInType(f.type, path, pos, func);
- path.pop();
- }
- default:
- func(t, path, pos);
- }
- }
- static function getTypeExpression(t : Type, path : Array<String>, pos:Position) : Expr {
- switch(t) {
- case TAnonymous(a):
- return createAnonDecl(a, path, pos);
- case TEnum(_,_):
- var objFields : Array<ObjectField> = [];
- return macro {
- var name = haxe.EnumTools.EnumValueTools.getName($p{path});
- var params = haxe.EnumTools.EnumValueTools.getParameters($p{path});
- if (params.length == 0) {
- (name:Dynamic);
- } else {
- ({
- "name" : name,
- "params" : params
- }:Dynamic);
- }
- };
- default:
- return macro $p{path};
- }
- }
- static function createAnonDecl(anonType: Ref<AnonType>, path: Array<String>, pos:Position) {
- var objFields : Array<ObjectField> = [];
- for (f in anonType.get().fields) {
- path.push(f.name);
- var e = getTypeExpression(f.type, path, pos);
- path.pop();
- objFields.push({field : f.name, expr : e});
- }
- return {expr: EObjectDecl(objFields), pos : pos};
- }
- public static macro function serializeValue(val : Expr) {
- var type = Context.typeof(val);
- if (type == null) throw "assert";
- var name = "";
- switch(val.expr) {
- case EField(_, n):
- name = n;
- default:
- throw "assert";
- }
- var expr = getTypeExpression(type, ["this", name], val.pos);
- return expr;
- }
- public static macro function fixupEnumUnserialise(original : Expr, val : Expr) {
- var exprs = new Array<Expr>();
- var type = Context.typeof(original);
- var pos = original.pos;
- var name = "";
- switch(val.expr) {
- case EField(_, n):
- name = n;
- default:
- throw "assert";
- }
- forEachFieldInType(type, ["obj", name], pos, function(t: Type, path: Array<String>, pos: Position) : Void
- {
- switch(t) {
- case TEnum(enumRef,_): {
- var name = path.copy(); name.push("name");
- var params = path.copy(); params.push("parameters");
- var parentPath = path.copy(); parentPath.pop();
- var expr = macro @:pos(pos) {
- var objNullCheck = ${getOrDefault(parentPath)};
- var isString = Std.is($p{path}, String);
- if (objNullCheck != null)
- $p{path} = hrt.impl.Macros.enumOrNullByName($i{enumRef.get().name}, isString ? $p{path} : ${getOrDefault(name, parentPath.length)}, isString ? null : ${getOrDefault(params, parentPath.length)});
- };
- exprs.push(expr);
- }
- default: {
- }
- }
- });
- return macro $b{exprs};
- }
- #if macro
- public static function buildPrefab() {
- var fields = Context.getBuildFields();
- var toSerialize = [], toCopy = [];
- var isRoot = Context.getLocalClass().toString() == "hrt.prefab.Prefab";
- var localType = haxe.macro.Tools.TTypeTools.toComplexType(Context.getLocalType());
- var changed = false;
- for( f in fields ) {
- if( f.name == "copy" && !isRoot ) {
- // inject auto cast to copy parameter
- switch( f.kind ) {
- case FFun(f) if( f.args.length == 1 && f.expr != null ):
- var name = f.args[0].name;
- var expr = f.expr;
- f.expr = macro @:pos(f.expr.pos) { var $name : $localType = cast $i{name}; $expr; }
- changed = true;
- default:
- }
- }
- if( f.meta == null ) continue;
- for( m in f.meta ) {
- switch( m.name ) {
- case ":s":
- toSerialize.push(f);
- case ":c":
- toCopy.push(f.name);
- default:
- }
- }
- }
- if( toSerialize.length + toCopy.length == 0 )
- return changed ? fields : null;
- var ser = [], unser = [], copy = [];
- var pos = Context.currentPos();
- for( f in toSerialize ) {
- switch( f.kind ) {
- case FProp(_, _, t, e), FVar(t,e):
- var name = f.name;
- var serCond = null;
- if( e == null ) {
- var setDef = true;
- var c : Constant = switch( t ) {
- case null: Context.error("Invalid var decl", f.pos);
- case TPath({ pack : [], name : "Int"|"Float" }): CInt("0");
- case TPath({ pack : [], name : "Bool" }): CIdent("false");
- //case TPath(p): setDef = false; trace(p); CIdent("null");
- default: setDef = false; CIdent("null");
- }
- e = { expr : EConst(c), pos : f.pos };
- if( setDef ) {
- f.kind = switch( f.kind ) {
- case FVar(t,_): FVar(t,e);
- case FProp(get,set,t,_): FProp(get,set,t,e);
- default: throw "assert";
- }
- }
- } else {
- var echeck = e;
- if( e.expr.match(EArrayDecl([])) )
- serCond = macro @:pos(f.pos) this.$name.length != 0;
- }
- if( serCond == null ) {
- var defVal = e.expr.match(EConst(_) | EBinop(_) | EUnop(_)) ? e : macro @:pos(f.pos) null;
- serCond = macro @:pos(pos) this.$name != $defVal;
- }
- ser.push(macro @:pos(pos) if( $serCond ) obj.$name = hrt.impl.Macros.serializeValue(this.$name));
- unser.push(macro @:pos(pos) hrt.impl.Macros.fixupEnumUnserialise(this.$name,obj.$name));
-
- unser.push(macro @:pos(pos) this.$name = obj.$name == null ? $e : obj.$name);
- copy.push(macro @:pos(pos) this.$name = p.$name);
- default:
- Context.error("Invalid serialization field", f.pos);
- }
- }
- for( name in toCopy ) {
- copy.push(macro @:pos(pos) this.$name = p.$name);
- }
- if( !isRoot ) {
- ser.unshift(macro @:pos(pos) super.saveSerializedFields(obj));
- unser.unshift(macro @:pos(pos) super.loadSerializedFields(obj));
- copy.unshift(macro @:pos(pos) var p : $localType = cast p);
- copy.unshift(macro @:pos(pos) super.copySerializedFields(p));
- }
- function makeFun(name,block) : Field {
- return {
- name : name,
- kind : FFun({
- ret : null,
- expr : { expr : EBlock(block), pos : pos },
- args : [{ name : "obj", type : macro : Dynamic }],
- }),
- meta : [{ name : ":noCompletion", pos : pos }],
- access : isRoot ? [] : [AOverride],
- pos : pos,
- };
- }
- if( toSerialize.length > 0 ) {
- fields.push(makeFun("saveSerializedFields",ser));
- fields.push(makeFun("loadSerializedFields",unser));
- }
- fields.push({
- name : "copySerializedFields",
- kind : FFun({
- ret : null,
- expr : { expr : EBlock(copy), pos : pos },
- args : [{ name : "p", type : macro : hrt.prefab.Prefab }],
- }),
- meta : [{ name : ":noCompletion", pos : pos }],
- access : isRoot ? [] : [AOverride],
- pos : pos,
- });
- return fields;
- }
- #end
- }
|