Wip controls macro

This commit is contained in:
Ne_Eo
2024-06-25 01:08:57 +02:00
parent e7ddb10c64
commit c3580cf141
@@ -0,0 +1,312 @@
package funkin.backend.system.macros;
import haxe.macro.Context;
import haxe.macro.Expr;
using StringTools;
using haxe.macro.Tools;
// Macro made by Ne_Eo
class ControlsMacro
{
static var _allControls: Array<String> = null;
static var _allInternalControls: Array<String> = null;
static var _currentControls: Map<String, Array<Expr>> = null;
static var _currentGamepadControls: Map<String, Expr> = null;
static var _keySet: Map<String, String> = null;
public static macro function build(): Array<Field>
{
var fields = Context.getBuildFields();
var clRef = Context.getLocalClass();
if (clRef == null)
return fields;
var cl = clRef.get();
_allControls = [];
_allInternalControls = [];
_currentControls = [];
_currentGamepadControls = [];
_keySet = [];
var fields = Context.getBuildFields();
for (field in fields.copy())
{
switch (field.kind)
{
case FProp(g, s, t, e):
var controlFields = handleControl(field, g, s, t, e);
for (field in controlFields)
if (field != null)
fields.push(field);
default:
}
}
// Public fields
fields.push({
name: "addDefaultGamepad",
access: [APublic],
kind: FFun({
ret: macro : Void,
params: [],
expr: generateGamepadCode(),
args: [{name: "id", type: macro : Int}]
}),
pos: Context.currentPos(),
doc: null,
meta: []
});
// Private fields, are prefixed with macro_, for readability
fields.push({
name: "macro_addKeysToActions",
access: [APublic, AInline],
kind: FFun({
ret: macro : Void,
params: [],
expr: macro $b{_allInternalControls.map((s) -> macro add($i{s}))},
args: []
}),
pos: Context.currentPos(),
doc: null,
meta: []
});
fields.push({
name: "macro_bindControls",
access: [],
kind: FFun({
ret: macro : Void,
params: [],
expr: macro $b{_allControls.map((s) -> macro addKeys(Control.$s, $v{_keySet.get(s)}))},
args: []
}),
pos: Context.currentPos(),
doc: null,
meta: []
});
fields.push({
name: "macro_forEachBound",
access: [],
kind: FFun({
ret: macro : Void,
params: [],
expr: generateForEachBoundCode(),
args: [
{
name: "control",
type: macro : Control
},
{
name: "func",
type: macro : FlxActionDigital -> FlxInputState -> Void
}
]
}),
pos: Context.currentPos(),
doc: null,
meta: []
});
// for (field in fields)
// trace(new haxe.macro.Printer().printField(field));
_allControls = null;
_allInternalControls = null;
_currentControls = null;
_currentGamepadControls = null;
_keySet = null;
return fields;
}
static function generateForEachBoundCode(): Expr
{
var cases: Array<Case> = [];
for (field => code in _currentControls)
{
cases.push({
values: [macro Control.$field],
expr: macro $b{code}
});
}
return {
expr: ESwitch(macro control, cases, macro {}),
pos: Context.currentPos()
}
}
static function generateGamepadCode(): Expr
{
var map: Array<Expr> = [];
for (name in _allControls)
{
var expr = _currentGamepadControls.get(name);
map.push(macro @:pos(expr.pos) Control.$name => ${expr});
}
return macro addGamepad(id, $a{map});
}
static function extractString(e: Expr): String
{
switch (e.expr)
{
case EConst(CString(s)):
return s;
default:
Context.error("Expected a string", e.pos);
throw "Expected a String";
}
}
static function camelCase(s: String): String
{
s = s.split("_").map((s) ->
{
return s.charAt(0).toUpperCase() + s.substr(1).toLowerCase();
}).join("");
return s.charAt(0).toLowerCase() + s.substr(1);
}
static function swapAB(e: Expr): Expr
{
return switch (e.expr)
{
case EConst(CIdent('A')): macro B;
case EConst(CIdent('B')): macro A;
default: e.map(swapAB);
}
}
static function handleControl(field: Field, get: String, set: String, _: ComplexType, e: Expr): Array<Field>
{
var name = field.name;
var shortName = name;
var type = "";
if (shortName.endsWith(type = "_P"))
shortName = shortName.substr(0, shortName.length - 2);
else if (shortName.endsWith(type = "_R"))
shortName = shortName.substr(0, shortName.length - 2);
else if (shortName.endsWith(type = "_H"))
shortName = shortName.substr(0, shortName.length - 2);
else
type = "";
var internalName = "_" + camelCase(shortName);
if (type != "")
internalName += type.substr(1);
var keyset: Null<String> = null;
var expr: Expr = null;
var metasToRemove = [];
for (meta in field.meta)
{
var shouldRemove = true;
switch (meta.name)
{
case ":gamepad":
if (!_currentGamepadControls.exists(shortName))
{
var expr = meta.params[0];
if (Context.defined("switch"))
{
expr = swapAB(expr);
}
_currentGamepadControls.set(shortName, expr);
}
case ":pressed":
keyset = extractString(meta.params[0]);
expr = macro func($i{internalName}, PRESSED);
case ":justPressed":
keyset = extractString(meta.params[0]);
expr = macro func($i{internalName}, JUST_PRESSED);
case ":released":
keyset = extractString(meta.params[0]);
expr = macro func($i{internalName}, RELEASED);
case ":justReleased":
keyset = extractString(meta.params[0]);
expr = macro func($i{internalName}, JUST_RELEASED);
default:
shouldRemove = false;
}
if (shouldRemove)
metasToRemove.push(meta);
}
for (meta in metasToRemove)
field.meta.remove(meta);
if (_currentControls.exists(shortName))
_currentControls.get(shortName).push(expr);
else
_currentControls.set(shortName, [expr]);
if (!_allInternalControls.contains(internalName))
_allInternalControls.push(internalName);
if (!_allControls.contains(shortName))
_allControls.push(shortName);
_keySet.set(shortName, keyset);
// Generated Code: var _uiUp = new FlxActionDigital("_uiUp");
var internalField: Field = {
name: internalName,
access: [APrivate],
kind: FVar(macro : FlxActionDigital, macro new FlxActionDigital($v{internalName})),
pos: field.pos,
doc: null,
meta: []
};
// Generated Code:
// inline function get_UI_UP(): Bool
// return _uiUp.check();
var getField: Field = {
name: "get_" + name,
access: [APrivate, AInline],
kind: FFun({
ret: macro : Bool,
params: [],
expr: macro
{
return $i{internalName}.check();
},
args: []
}),
pos: field.pos,
doc: field.doc,
meta: []
};
// Generated Code:
// inline function set_UI_UP(val: Bool): Bool
// return @:privateAccess _uiUp._checked = val;
var setField: Field = {
name: "set_" + name,
access: [APrivate, AInline],
kind: FFun({
ret: macro : Bool,
params: [],
expr: macro
{
return @:privateAccess $i{internalName}._checked = val;
},
args: [{name: "val", type: macro : Bool}]
}),
pos: field.pos,
doc: field.doc,
meta: []
};
return [internalField, getField, set == "set" ? setField : null];
}
}