Signal and Time API

This commit is contained in:
Face
2025-08-05 19:45:32 +03:00
parent 92b7976256
commit f68f31d526
6 changed files with 666 additions and 0 deletions

View File

@@ -27,6 +27,12 @@ static func setup_gurt_api(vm: LuauVM, lua_api, dom_parser: HTMLParser) -> void:
vm.lua_setfield(-2, "tostring")
vm.lua_pop(1) # Pop table from stack
# Setup Signal API
LuaSignalUtils.setup_signal_api(vm)
# Setup Time API
LuaTimeUtils.setup_time_api(vm)
vm.lua_newtable()
vm.lua_pushcallable(LuaPrintUtils.lua_print, "gurt.log")

View File

@@ -0,0 +1,223 @@
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")

View File

@@ -0,0 +1 @@
uid://drb1qy7epbtwe

View File

@@ -0,0 +1,244 @@
class_name LuaTimeUtils
extends RefCounted
static func time_now_handler(vm: LuauVM) -> int:
var current_time = Time.get_unix_time_from_system()
vm.lua_pushnumber(current_time)
return 1
static func time_format_handler(vm: LuauVM) -> int:
var format_string: String = "%H:%M:%S"
var datetime: Dictionary
if vm.lua_gettop() >= 1 and vm.lua_isnumber(1):
var timestamp: float = vm.lua_tonumber(1)
var local_now_dict = Time.get_datetime_dict_from_system(false)
var utc_now_dict = Time.get_datetime_dict_from_system(true)
var offset_seconds = Time.get_unix_time_from_datetime_dict(local_now_dict) - Time.get_unix_time_from_datetime_dict(utc_now_dict)
datetime = Time.get_datetime_dict_from_unix_time(int(timestamp) + offset_seconds)
else:
datetime = Time.get_datetime_dict_from_system(false)
if vm.lua_gettop() >= 2 and vm.lua_isstring(2):
format_string = vm.lua_tostring(2)
var formatted_time = format_datetime(datetime, format_string)
vm.lua_pushstring(formatted_time)
return 1
static func time_date_handler(vm: LuauVM) -> int:
var datetime: Dictionary
if vm.lua_gettop() >= 1 and vm.lua_isnumber(1):
var timestamp = vm.lua_tonumber(1)
var local_now_dict = Time.get_datetime_dict_from_system(false)
var utc_now_dict = Time.get_datetime_dict_from_system(true)
var offset_seconds = Time.get_unix_time_from_datetime_dict(local_now_dict) - Time.get_unix_time_from_datetime_dict(utc_now_dict)
datetime = Time.get_datetime_dict_from_unix_time(int(timestamp) + offset_seconds)
else:
datetime = Time.get_datetime_dict_from_system(false)
vm.lua_newtable()
vm.lua_pushinteger(datetime.year)
vm.lua_setfield(-2, "year")
vm.lua_pushinteger(datetime.month)
vm.lua_setfield(-2, "month")
vm.lua_pushinteger(datetime.day)
vm.lua_setfield(-2, "day")
vm.lua_pushinteger(datetime.hour)
vm.lua_setfield(-2, "hour")
vm.lua_pushinteger(datetime.minute)
vm.lua_setfield(-2, "minute")
vm.lua_pushinteger(datetime.second)
vm.lua_setfield(-2, "second")
vm.lua_pushinteger(datetime.weekday)
vm.lua_setfield(-2, "weekday")
return 1
static func time_sleep_handler(vm: LuauVM) -> int:
vm.luaL_checknumber(1)
var seconds = vm.lua_tonumber(1)
var milliseconds = int(seconds * 1000)
# TODO: implement a proper sleep function
vm.lua_pushnumber(seconds)
return 1
static func time_benchmark_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
var start_time = Time.get_ticks_msec()
vm.lua_pushvalue(1)
if vm.lua_pcall(0, 0, 0) != vm.LUA_OK:
var error_msg = vm.lua_tostring(-1)
vm.lua_pop(1)
var end_time = Time.get_ticks_msec()
var elapsed_ms = end_time - start_time
vm.lua_pushnumber(elapsed_ms / 1000.0)
vm.lua_pushstring("Error: " + error_msg)
return 2
var end_time = Time.get_ticks_msec()
var elapsed_ms = end_time - start_time
vm.lua_pushnumber(elapsed_ms / 1000.0)
return 1
static func time_timer_handler(vm: LuauVM) -> int:
var start_time = Time.get_ticks_msec()
vm.lua_newtable()
vm.lua_pushnumber(start_time)
vm.lua_setfield(-2, "_start_time")
vm.lua_pushcallable(timer_elapsed_handler, "timer:elapsed")
vm.lua_setfield(-2, "elapsed")
vm.lua_pushcallable(timer_reset_handler, "timer:reset")
vm.lua_setfield(-2, "reset")
return 1
static func timer_elapsed_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_start_time")
var start_time = vm.lua_tonumber(-1)
vm.lua_pop(1)
var current_time = Time.get_ticks_msec()
var elapsed = (current_time - start_time) / 1000.0
vm.lua_pushnumber(elapsed)
return 1
static func timer_reset_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var current_time = Time.get_ticks_msec()
vm.lua_pushnumber(current_time)
vm.lua_setfield(1, "_start_time")
return 0
static func time_delay_handler(vm: LuauVM) -> int:
vm.luaL_checknumber(1)
var seconds = vm.lua_tonumber(1)
var end_time = Time.get_ticks_msec() + (seconds * 1000)
vm.lua_newtable()
vm.lua_pushnumber(end_time)
vm.lua_setfield(-2, "_end_time")
vm.lua_pushcallable(delay_is_complete_handler, "delay:complete")
vm.lua_setfield(-2, "complete")
vm.lua_pushcallable(delay_remaining_handler, "delay:remaining")
vm.lua_setfield(-2, "remaining")
return 1
static func delay_is_complete_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_end_time")
var end_time = vm.lua_tonumber(-1)
vm.lua_pop(1)
var current_time = Time.get_ticks_msec()
var is_complete = current_time >= end_time
vm.lua_pushboolean(is_complete)
return 1
static func delay_remaining_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_end_time")
var end_time = vm.lua_tonumber(-1)
vm.lua_pop(1)
var current_time = Time.get_ticks_msec()
var remaining = max(0.0, (end_time - current_time) / 1000.0)
vm.lua_pushnumber(remaining)
return 1
static func format_datetime(datetime: Dictionary, format_string: String) -> String:
var result = format_string
var local_datetime = datetime
if not local_datetime.has("weekday"):
var unix_time = Time.get_unix_time_from_datetime_dict(local_datetime)
local_datetime = Time.get_datetime_dict_from_unix_time(unix_time)
var ampm = ""
var hour12 = local_datetime.hour
if local_datetime.hour >= 12:
ampm = "pm"
if local_datetime.hour > 12:
hour12 = local_datetime.hour - 12
else:
ampm = "am"
if local_datetime.hour == 0:
hour12 = 12
result = result.replace("%Y", "%04d" % local_datetime.year)
result = result.replace("%y", "%02d" % (local_datetime.year % 100))
result = result.replace("%m", "%02d" % local_datetime.month)
result = result.replace("%d", "%02d" % local_datetime.day)
result = result.replace("%H", "%02d" % local_datetime.hour)
result = result.replace("%I", "%02d" % hour12)
result = result.replace("%M", "%02d" % local_datetime.minute)
result = result.replace("%S", "%02d" % local_datetime.second)
result = result.replace("%p", ampm)
var weekday_names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
var weekday_abbrev = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
if local_datetime.weekday >= 0 and local_datetime.weekday <= 6:
result = result.replace("%A", weekday_names[local_datetime.weekday])
result = result.replace("%a", weekday_abbrev[local_datetime.weekday])
var month_names = ["January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", "December"]
var month_abbrev = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
if local_datetime.month >= 1 and local_datetime.month <= 12:
result = result.replace("%B", month_names[local_datetime.month - 1])
result = result.replace("%b", month_abbrev[local_datetime.month - 1])
return result
static func setup_time_api(vm: LuauVM) -> void:
vm.lua_newtable()
vm.lua_pushcallable(time_now_handler, "Time.now")
vm.lua_setfield(-2, "now")
vm.lua_pushcallable(time_format_handler, "Time.format")
vm.lua_setfield(-2, "format")
vm.lua_pushcallable(time_date_handler, "Time.date")
vm.lua_setfield(-2, "date")
vm.lua_pushcallable(time_sleep_handler, "Time.sleep")
vm.lua_setfield(-2, "sleep")
vm.lua_pushcallable(time_benchmark_handler, "Time.benchmark")
vm.lua_setfield(-2, "benchmark")
vm.lua_pushcallable(time_timer_handler, "Time.timer")
vm.lua_setfield(-2, "timer")
vm.lua_pushcallable(time_delay_handler, "Time.delay")
vm.lua_setfield(-2, "delay")
vm.lua_setglobal("Time")

View File

@@ -0,0 +1 @@
uid://ntey7mujnbo6