224 lines
6.6 KiB
GDScript
224 lines
6.6 KiB
GDScript
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, signal_table_ref: int = -1) -> void:
|
|
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:
|
|
print("GURT ERROR in Signal callback: ", vm.lua_tostring(-1))
|
|
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, lua_signal.signal_table_ref)
|
|
|
|
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")
|