diff --git a/Project.xml b/Project.xml index 03373c78..38d1404a 100644 --- a/Project.xml +++ b/Project.xml @@ -125,17 +125,23 @@ + + + + + + diff --git a/buildnumber.txt b/buildnumber.txt index 40a31ff8..f55a6f7c 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -2071 \ No newline at end of file +2107 \ No newline at end of file diff --git a/hmm.json b/hmm.json index a20ce299..2b4c7816 100644 --- a/hmm.json +++ b/hmm.json @@ -53,6 +53,13 @@ "ref": "master", "url": "https://github.com/Aidan63/linc_discord-rpc" }, + { + "name": "linc_luajit", + "type": "git", + "dir": null, + "ref": "master", + "url": "https://github.com/nebulazorua/linc_luajit" + }, { "name": "markdown", "type": "haxelib", diff --git a/source/funkin/scripting/LuaScript.hx b/source/funkin/scripting/LuaScript.hx new file mode 100644 index 00000000..18866d45 --- /dev/null +++ b/source/funkin/scripting/LuaScript.hx @@ -0,0 +1,208 @@ +package funkin.scripting; + +#if ENABLE_LUA +import llua.Lua; +import llua.LuaL; +import llua.State; +import llua.Convert; + +import openfl.utils.Assets; + +using llua.Lua; +using llua.LuaL; +using llua.Convert; + +class LuaScript extends Script { + public var state:State = null; + + public var lastStackID:Int = 0; + public var stack:Map = []; + + public var luaCallbacks:Map = []; + + public override function onCreate(path:String) { + super.onCreate(path); + + state = LuaL.newstate(); + Lua.set_callbacks_function(cpp.Callable.fromStaticFunction(callback_handler)); + LuaL.openlibs(state); + Lua.register_hxtrace_func(cpp.Callable.fromStaticFunction(print_function)); + state.register_hxtrace_lib(); + + luaCallbacks["__onPointerIndex"] = onPointerIndex; + luaCallbacks["__onPointerNewIndex"] = onPointerNewIndex; + luaCallbacks["__gc"] = onGarbageCollection; + + state.newmetatable("__funkinMetaTable"); + + state.pushstring('__index'); + state.pushcfunction(cpp.Callable.fromStaticFunction(__index)); + state.settable(-3); + + state.pushstring('__newindex'); + state.pushcfunction(cpp.Callable.fromStaticFunction(__newindex)); + state.settable(-3); + } + + public override function onLoad() { + state.dostring(Assets.getText(path)); + } + + public static var callbackReturnVariables = []; + + public override function onCall(funcName:String, args:Array):Dynamic { + state.settop(0); + state.getglobal(funcName); + + if (state.type(-1) != Lua.LUA_TFUNCTION) + return null; + + for (k=>val in args) + pushArg(val); + + if (state.pcall(args.length, 1, 0) != 0) { + this.error('${state.tostring(-1)}'); + return null; + } + + return fromLua(state.gettop()); + } + + public override function set(variable:String, value:Dynamic) { + pushArg(value); + state.setglobal(variable); + } + + public override function onDestroy() { + super.onDestroy(); + + if (state != null) { + Lua.close(state); + state = null; + } + } + public override function reload() { + Logs.trace('Hot-reloading is currently not supported on Lua.', WARNING); + } + + // UTILS + #if REGION + + static inline function print_function(s:String) : Int { + if (Script.curScript != null) + Script.curScript.trace(s); + return 0; + } + + public function fromLua(stackPos:Int):Dynamic { + var v:Dynamic = state.fromLua(stackPos); + if (v is Dynamic && Reflect.hasField(v, "__stack_id")) { + // is a "pointer"! convert it back. + var pos:Int = Reflect.field(v, "__stack_id"); + return stack[pos]; + } + return v; + } + public function pushArg(val:Dynamic) { + switch (Type.typeof(val)) { + case Type.ValueType.TNull: + state.pushnil(); + case Type.ValueType.TBool: + state.pushboolean(val); + case Type.ValueType.TInt: + state.pushinteger(cast(val, Int)); + case Type.ValueType.TFloat: + state.pushnumber(val); + case Type.ValueType.TClass(String): + state.pushstring(cast(val, String)); + case Type.ValueType.TClass(Array): + state.arrayToLua(val); + case Type.ValueType.TObject: + @:privateAccess + state.objectToLua(val); // {} + default: + + var p = { + __stack_id: lastStackID++, + }; + state.toLua(p); + state.getmetatable("__funkinMetaTable"); + state.setmetatable(-2); + + state.pushstring('__gc'); + state.pushcfunction(cpp.Callable.fromStaticFunction(__gc)); + state.settable(-3); + + stack[p.__stack_id] = val; + } + } + public static function __index(state:StatePointer):Int { + return callback_handler(cast cpp.Pointer.fromRaw(state).ref, "__onPointerIndex"); + } + public static function __newindex(state:StatePointer):Int { + return callback_handler(cast cpp.Pointer.fromRaw(state).ref, "__onPointerNewIndex"); + } + public static function __gc(state:StatePointer):Int { + // callbackPreventAutoConvert = true; + var v = callback_handler(cast cpp.Pointer.fromRaw(state).ref, "__gc"); + // callbackPreventAutoConvert = false; + return v; + } + + public function onPointerIndex(obj:Dynamic, key:String) { + if (obj != null) + return Reflect.getProperty(obj, key); + return null; + } + + public function onPointerNewIndex(obj:Dynamic, key:String, val:Dynamic) { + if (obj != null) + Reflect.setProperty(obj, key, val); + } + + public function onGarbageCollection(obj:Dynamic) { + trace(obj); + if (Reflect.hasField(obj, "__stack_id")) { + trace('Clearing item ID: ${obj.__stack_id} from stack due to garbage collection'); + stack.remove(obj.__stack_id); + } + } + + private static var callbackPreventAutoConvert:Bool = false; + public static inline function callback_handler(l:State, fname:String):Int { + + if (!(Script.curScript is LuaScript)) + return 0; + var curLua:LuaScript = cast Script.curScript; + + var cbf = curLua.luaCallbacks.get(fname); + callbackReturnVariables = []; + + if (cbf == null || !Reflect.isFunction(cbf)) + return 0; + + var nparams:Int = Lua.gettop(l); + var args:Array = callbackPreventAutoConvert ? [for(i in 0...nparams) l.fromLua(i + 1)] : [for(i in 0...nparams) curLua.fromLua(i + 1)]; + + var ret:Dynamic = null; + + try { + ret = (nparams > 0) ? Reflect.callMethod(null, cbf, args) : cbf(); + } catch(e) { + curLua.error(e.details()); // for super cool mega logging!!! + throw e; + } + Lua.settop(l, 0); + + if (callbackReturnVariables.length <= 0) + callbackReturnVariables.push(ret); + for(e in callbackReturnVariables) + curLua.pushArg(e); + + /* return the number of results */ + return callbackReturnVariables.length; + + } + #end +} +#end \ No newline at end of file diff --git a/source/funkin/scripting/Script.hx b/source/funkin/scripting/Script.hx index 843b2dc4..9fd154f0 100644 --- a/source/funkin/scripting/Script.hx +++ b/source/funkin/scripting/Script.hx @@ -110,13 +110,14 @@ class Script extends FlxBasic implements IFlxDestroyable { "SOFTCODED_STATES" => #if SOFTCODED_STATES true #else false #end, "USE_SOURCE_ASSETS" => #if USE_SOURCE_ASSETS true #else false #end, "USE_ADAPTED_ASSETS" => #if USE_ADAPTED_ASSETS true #else false #end, + "ENABLE_LUA" => #if ENABLE_LUA true #else false #end, ]; } /** * All available script extensions */ public static var scriptExtensions:Array = [ - "hx", "hscript", "hsc", "hxs", "lua" /** ACTUALLY NOT SUPPORTED, ONLY FOR THE MESSAGE **/ + "hx", "hscript", "hsc", "hxs", "lua" ]; /** @@ -144,8 +145,7 @@ class Script extends FlxBasic implements IFlxDestroyable { case "hx" | "hscript" | "hsc" | "hxs": new HScript(path); case "lua": - Logs.trace("Lua is not supported in this engine. Use HScript instead.", ERROR); - new DummyScript(path); + new LuaScript(path); default: new DummyScript(path); } @@ -160,6 +160,10 @@ class Script extends FlxBasic implements IFlxDestroyable { public function new(path:String) { super(); + // #if cpp + // cpp.vm.Gc.setFinalizer(this, cpp.Callable.fromStaticFunction(onFinalize)); + // #end + fileName = Path.withoutDirectory(path); this.path = path; onCreate(path); @@ -263,10 +267,20 @@ class Script extends FlxBasic implements IFlxDestroyable { public function onLoad() {} - public function onDestroy() {}; + public function onDestroy() {} public override function destroy() { super.destroy(); onDestroy(); } + + private static var __scriptType:Class