package engine.macros; #if macro import haxe.macro.Expr; import haxe.macro.Context; import haxe.macro.Type; import haxe.macro.Expr.Field; import haxe.macro.Expr.Function; import haxe.macro.Expr.Access; private typedef CVarMetadata = { name:Expr, type:String, value:Expr, flags:Expr, help:Expr, ?callback:Expr } class ConVarDecorators { static function extractCVarMetadata(params:Array):CVarMetadata { if (params == null || params.length == 0) return null; var metadata:CVarMetadata = { name: macro "", type: "CString", value: macro 0, flags: macro "FCVAR_NONE", help: macro "", callback: macro null }; switch (params[0].expr) { case EObjectDecl(fields): var fieldMap = [ "name" => function(expr) metadata.name = expr, "type" => function(expr) metadata.type = switch (expr.expr) { case EConst(CString(s)): s; case _: "CString"; }, "value" => function(expr) metadata.value = expr, "flags" => function(expr) metadata.flags = expr, "help" => function(expr) metadata.help = expr, "callback" => function(expr) metadata.callback = expr ]; for (field in fields) { if (fieldMap.exists(field.field)) { fieldMap[field.field](field.expr); } else { Context.error("Unexpected field: " + field.field, field.expr.pos); } } case _: } return metadata; } // Process command fields private static function processCommandField(field:Field, cmdMeta:MetadataEntry, extraFields:Array):Void { if (!isFieldKind(field, "FFun")) { Context.error("The @:concmd metadata can only be applied to functions", field.pos); return; } var pos = Context.currentPos(); var functionReference = getFunctionFromField(field); if (functionReference == null) return; var name = cmdMeta.params.length > 0 ? getStringFromExpr(cmdMeta.params[0]) ?? field.name : field.name; var functionExpression = macro function(args:Array) ${functionReference.expr}; var initExpr = macro { var cmd = engine.ConVar.registerCCmd($v{name}, $functionExpression); cmd; }; var initName = "__init__cmd_" + name; extraFields.push(createStaticInitField(initName, initExpr, pos)); } // Process convar fields private static function processConVarField(field:Field, cvarMeta:MetadataEntry, extraFields:Array):Void { if (!isFieldKind(field, "FVar")) { Context.error("The @:convar metadata can only be applied to variables", field.pos); return; } var pos = Context.currentPos(); var meta = extractCVarMetadata(cvarMeta.params); if (meta == null) { Context.error("Metadata extraction failed for field: " + field.name, field.pos); return; } var cvarName = getStringFromMetaExpr(meta.name) ?? field.name; var fieldName = field.name; var isStatic = hasAccess(field, AStatic); var varType = getVarType(field); if (isStatic) { // Handle static fields var staticInitExpr = macro { var cvar = $i{fieldName} = engine.ConVar.registerCVar( ${meta.name}, ${Context.parse('engine.enums.console.CVarType.' + meta.type, pos)}, ${meta.value}, ${Context.parse('engine.enums.console.CVarFlag.' + switch (meta.flags.expr) { case EConst(CString(s)): s; case _: "FCVAR_NONE"; }, pos)}, ${meta.help}, ${meta.callback ?? macro null} ); trace($v{"Registered CVar: "} + $e{meta.name}); cvar; }; extraFields.push(createStaticInitField("__init__" + cvarName, staticInitExpr, pos)); } else { // Handle instance fields updateFieldInitialValue(field, macro null); var instanceInitializerExpression = macro { if (Reflect.field(this, $v{fieldName}) == null) { Reflect.setField(this, $v{fieldName}, engine.ConVar.registerCVar( ${meta.name}, ${Context.parse('engine.enums.console.CVarType.' + meta.type, pos)}, ${meta.value}, ${Context.parse('engine.enums.console.CVarFlag.' + switch (meta.flags.expr) { case EConst(CString(s)): s; case _: "FCVAR_NONE"; }, pos)}, ${meta.help}, ${meta.callback ?? macro null} )); trace($v{"Registered CVar: "} + $e{meta.name}); } }; ensureConstructorWithInit(extraFields, instanceInitializerExpression, pos); } } // Map to check field kinds private static final fieldKindMap:MapBool> = [ "FFun" => function(ft) return switch(ft) { case FFun(_): true; case _: false; }, "FVar" => function(ft) return switch(ft) { case FVar(_, _): true; case _: false; }, "FProp" => function(ft) return switch(ft) { case FProp(_, _, _, _): true; case _: false; } ]; // Utility functions private static function isFieldKind(field:Field, kind:String):Bool { return fieldKindMap.exists(kind) ? fieldKindMap.get(kind)(field.kind) : false; } private static function getFunctionFromField(field:Field):Function { return switch (field.kind) { case FFun(f): f; case _: null; } } private static function getVarType(field:Field):ComplexType { return switch (field.kind) { case FVar(t, _): t; case _: null; } } private static function updateFieldInitialValue(field:Field, expr:Expr):Void { switch (field.kind) { case FVar(t, _): field.kind = FVar(t, expr); case _: } } private static function getStringFromExpr(expr:Expr):String { return switch (expr.expr) { case EConst(CString(s)): s; case _: null; } } private static function getStringFromMetaExpr(expr:Expr):String { return switch (expr.expr) { case EConst(CString(s)): s; case _: null; } } private static function hasAccess(field:Field, access:Access):Bool { return field.access != null && field.access.indexOf(access) >= 0; } private static function createStaticInitField(name:String, initExpr:Expr, pos:Position):Field { return { name: name, access: [AStatic], kind: FVar(null, initExpr), pos: pos, doc: null, meta: [{ name: ":keep", params: [], pos: pos }] }; } private static function ensureConstructorWithInit(fields:Array, initExpr:Expr, pos:Position):Void { var cls = Context.getLocalClass().get(); var hasSuperClass = cls.superClass != null; var constructor = Lambda.find(fields, function(f) return f.name == "new"); if (constructor == null) { constructor = { name: "new", access: [APublic], kind: FFun({ args: [], expr: if (hasSuperClass) macro { super(); $initExpr; } else macro { $initExpr; }, ret: null }), pos: pos, meta: [] }; fields.push(constructor); } else { switch (constructor.kind) { case FFun(f): switch(f.expr.expr) { case EBlock(exprs): exprs.push(initExpr); case _: f.expr = macro { ${f.expr}; $initExpr; } } case _: } } } public static macro function build():Array { var fields = Context.getBuildFields(); var extraFields:Array = []; for (field in fields) { if (field.meta == null) continue; var cmdMeta = Lambda.find(field.meta, function(m) return m.name == ":concmd"); var cvarMeta = Lambda.find(field.meta, function(m) return m.name == ":convar"); if (cmdMeta != null) { processCommandField(field, cmdMeta, extraFields); } else if (cvarMeta != null && cvarMeta.params.length > 0) { processConVarField(field, cvarMeta, extraFields); } } return fields.concat(extraFields); } } #end