Files
leonwww/flumi/Scripts/Utils/Lua/Signal.gd

223 lines
6.4 KiB
GDScript3
Raw Normal View History

2025-08-05 19:45:32 +03:00
class_name LuaSignalUtils
extends RefCounted
# Signal API for Lua - provides custom signal functionality
# Usage: local mySignal = Signal.new(), mySignal:connect(callback), mySignal:fire(args...)
class LuaSignal:
var connections: Array[Dictionary] = []
var next_connection_id: int = 1
var signal_table_ref: int = -1
func connect_callback(callback_ref: int, vm: LuauVM) -> int:
var connection_id = next_connection_id
next_connection_id += 1
var connection = {
"id": connection_id,
"callback_ref": callback_ref,
"vm": vm
}
connections.append(connection)
return connection_id
func disconnect_callback(connection_id: int, vm: LuauVM) -> void:
for i in range(connections.size() - 1, -1, -1):
var connection = connections[i]
if connection.id == connection_id:
# Clean up the Lua reference from custom storage
vm.lua_pushstring("SIGNAL_CALLBACKS")
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
vm.lua_pushinteger(connection.callback_ref)
vm.lua_pushnil()
vm.lua_rawset(-3) # Set callbacks[callback_ref] = nil
vm.lua_pop(1) # Pop callbacks table
connections.remove_at(i)
break
func disconnect_all(vm: LuauVM) -> void:
# Clean up all Lua references from custom storage
vm.lua_pushstring("SIGNAL_CALLBACKS")
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
for connection in connections:
vm.lua_pushinteger(connection.callback_ref)
vm.lua_pushnil()
vm.lua_rawset(-3) # Set callbacks[callback_ref] = nil
vm.lua_pop(1) # Pop callbacks table
connections.clear()
func fire_signal(args: Array) -> void:
2025-08-05 19:45:32 +03:00
for connection in connections:
var vm = connection.vm as LuauVM
# Get the callback function from our custom storage
vm.lua_pushstring("SIGNAL_CALLBACKS")
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
vm.lua_pushinteger(connection.callback_ref)
vm.lua_rawget(-2)
if vm.lua_isfunction(-1):
# Push the arguments directly (don't pass self)
for arg in args:
vm.lua_pushvariant(arg)
# Call the function
if vm.lua_pcall(args.size(), 0, 0) != vm.LUA_OK:
vm.lua_pop(1)
# Pop the callbacks table
vm.lua_pop(1)
else:
vm.lua_pop(2) # Pop both the non-function and the callbacks table
static var signals_registry: Dictionary = {}
static var next_signal_id: int = 1
static var next_callback_ref: int = 1
# Signal.new() constructor
static func signal_new_handler(vm: LuauVM) -> int:
var signal_id = next_signal_id
next_signal_id += 1
var lua_signal = LuaSignal.new()
signals_registry[signal_id] = lua_signal
# Create the signal table
vm.lua_newtable()
vm.lua_pushinteger(signal_id)
vm.lua_setfield(-2, "_signal_id")
# Store a reference to this signal table for : syntax
vm.lua_pushvalue(-1) # Duplicate the signal table
var signal_table_ref = vm.luaL_ref(vm.LUA_REGISTRYINDEX)
lua_signal.signal_table_ref = signal_table_ref
# Add methods
vm.lua_pushcallable(signal_connect_handler, "signal:connect")
vm.lua_setfield(-2, "connect")
vm.lua_pushcallable(signal_fire_handler, "signal:fire")
vm.lua_setfield(-2, "fire")
vm.lua_pushcallable(signal_disconnect_handler, "signal:disconnect")
vm.lua_setfield(-2, "disconnect")
return 1
# signal:connect(callback) method
static func signal_connect_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.luaL_checktype(2, vm.LUA_TFUNCTION)
# Get signal ID
vm.lua_getfield(1, "_signal_id")
var signal_id: int = vm.lua_tointeger(-1)
vm.lua_pop(1)
var lua_signal = signals_registry.get(signal_id) as LuaSignal
if not lua_signal:
vm.lua_pushnil()
return 1
# Store callback in a custom registry table instead of using luaL_ref
# Get or create our custom callback storage table
vm.lua_pushstring("SIGNAL_CALLBACKS")
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
if vm.lua_isnil(-1):
vm.lua_pop(1) # Pop nil
vm.lua_newtable() # Create new table
vm.lua_pushstring("SIGNAL_CALLBACKS")
vm.lua_pushvalue(-2) # Duplicate the table
vm.lua_rawset(vm.LUA_REGISTRYINDEX) # Set SIGNAL_CALLBACKS = table
# Now store the callback function
var callback_ref = next_callback_ref
next_callback_ref += 1
vm.lua_pushinteger(callback_ref) # Key
vm.lua_pushvalue(2) # Value (the callback function)
vm.lua_rawset(-3) # Set callbacks[callback_ref] = function
vm.lua_pop(1) # Pop the callbacks table
# Connect the callback
var connection_id = lua_signal.connect_callback(callback_ref, vm)
# Return connection object
vm.lua_newtable()
vm.lua_pushinteger(connection_id)
vm.lua_setfield(-2, "_connection_id")
vm.lua_pushinteger(signal_id)
vm.lua_setfield(-2, "_signal_id")
vm.lua_pushcallable(connection_disconnect_handler, "connection:disconnect")
vm.lua_setfield(-2, "disconnect")
return 1
# signal:fire(...) method
static func signal_fire_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
# Get signal ID
vm.lua_getfield(1, "_signal_id")
var signal_id: int = vm.lua_tointeger(-1)
vm.lua_pop(1)
var lua_signal = signals_registry.get(signal_id) as LuaSignal
if not lua_signal:
return 0
# Collect arguments (everything after the signal table)
var args: Array = []
var arg_count = vm.lua_gettop() - 1 # Subtract 1 for the signal table itself
for i in range(2, arg_count + 2): # Start from index 2 (after signal table)
args.append(vm.lua_tovariant(i))
# Fire the signal with the signal table reference
lua_signal.fire_signal(args)
2025-08-05 19:45:32 +03:00
return 0
# signal:disconnect() method - disconnects all connections
static func signal_disconnect_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
# Get signal ID
vm.lua_getfield(1, "_signal_id")
var signal_id: int = vm.lua_tointeger(-1)
vm.lua_pop(1)
var lua_signal = signals_registry.get(signal_id) as LuaSignal
if lua_signal:
lua_signal.disconnect_all(vm)
return 0
# connection:disconnect() method - disconnects specific connection
static func connection_disconnect_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
# Get connection ID and signal ID
vm.lua_getfield(1, "_connection_id")
var connection_id: int = vm.lua_tointeger(-1)
vm.lua_pop(1)
vm.lua_getfield(1, "_signal_id")
var signal_id: int = vm.lua_tointeger(-1)
vm.lua_pop(1)
var lua_signal = signals_registry.get(signal_id) as LuaSignal
if lua_signal:
lua_signal.disconnect_callback(connection_id, vm)
return 0
static func setup_signal_api(vm: LuauVM) -> void:
# Create Signal table
vm.lua_newtable()
# Add Signal.new constructor
vm.lua_pushcallable(signal_new_handler, "Signal.new")
vm.lua_setfield(-2, "new")
# Set as global Signal
vm.lua_setglobal("Signal")