2025-08-04 16:31:16 +03:00
|
|
|
class_name LuaTimeoutManager
|
|
|
|
|
extends RefCounted
|
|
|
|
|
|
|
|
|
|
var active_timeouts: Dictionary = {}
|
|
|
|
|
var next_timeout_id: int = 1
|
2025-08-08 16:51:21 +03:00
|
|
|
var threaded_vm: ThreadedLuaVM = null
|
2025-08-04 16:31:16 +03:00
|
|
|
|
|
|
|
|
class TimeoutInfo:
|
|
|
|
|
var id: int
|
|
|
|
|
var callback_ref: int
|
|
|
|
|
var vm: LuauVM
|
|
|
|
|
var timer: Timer
|
|
|
|
|
var timeout_manager: LuaTimeoutManager
|
2025-08-09 14:57:29 +03:00
|
|
|
var is_interval: bool = false
|
|
|
|
|
var delay_ms: int = 0
|
2025-08-04 16:31:16 +03:00
|
|
|
|
2025-08-09 14:57:29 +03:00
|
|
|
func _init(timeout_id: int, cb_ref: int, lua_vm: LuauVM, manager: LuaTimeoutManager, interval: bool = false, delay: int = 0):
|
2025-08-04 16:31:16 +03:00
|
|
|
id = timeout_id
|
|
|
|
|
callback_ref = cb_ref
|
|
|
|
|
vm = lua_vm
|
|
|
|
|
timeout_manager = manager
|
2025-08-09 14:57:29 +03:00
|
|
|
is_interval = interval
|
|
|
|
|
delay_ms = delay
|
2025-08-04 16:31:16 +03:00
|
|
|
|
|
|
|
|
func set_timeout_handler(vm: LuauVM, parent_node: Node) -> int:
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
|
|
|
|
var delay_ms: int = vm.luaL_checkint(2)
|
|
|
|
|
|
|
|
|
|
var timeout_id = next_timeout_id
|
|
|
|
|
next_timeout_id += 1
|
|
|
|
|
|
2025-08-04 17:04:35 +03:00
|
|
|
# Store callback in isolated registry table
|
|
|
|
|
vm.lua_pushstring("GURT_TIMEOUTS")
|
|
|
|
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
|
|
|
|
if vm.lua_isnil(-1):
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
vm.lua_pushstring("GURT_TIMEOUTS")
|
|
|
|
|
vm.lua_pushvalue(-2)
|
|
|
|
|
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
|
|
|
|
|
|
|
|
|
vm.lua_pushinteger(timeout_id)
|
2025-08-04 16:31:16 +03:00
|
|
|
vm.lua_pushvalue(1)
|
2025-08-04 17:04:35 +03:00
|
|
|
vm.lua_rawset(-3)
|
|
|
|
|
vm.lua_pop(1)
|
2025-08-04 16:31:16 +03:00
|
|
|
|
|
|
|
|
# Create timeout info
|
2025-08-04 17:04:35 +03:00
|
|
|
var timeout_info = TimeoutInfo.new(timeout_id, timeout_id, vm, self)
|
2025-08-04 16:31:16 +03:00
|
|
|
|
|
|
|
|
# Create and configure timer
|
|
|
|
|
var timer = Timer.new()
|
|
|
|
|
timer.wait_time = delay_ms / 1000.0
|
|
|
|
|
timer.one_shot = true
|
|
|
|
|
timer.timeout.connect(_on_timeout_triggered.bind(timeout_info))
|
|
|
|
|
|
|
|
|
|
timeout_info.timer = timer
|
|
|
|
|
active_timeouts[timeout_id] = timeout_info
|
|
|
|
|
|
|
|
|
|
# Add timer to scene tree
|
|
|
|
|
parent_node.add_child(timer)
|
|
|
|
|
timer.start()
|
|
|
|
|
|
|
|
|
|
vm.lua_pushinteger(timeout_id)
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-09 14:57:29 +03:00
|
|
|
func set_interval_handler(vm: LuauVM, parent_node: Node) -> int:
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
|
|
|
|
var delay_ms: int = vm.luaL_checkint(2)
|
|
|
|
|
|
|
|
|
|
var timeout_id = next_timeout_id
|
|
|
|
|
next_timeout_id += 1
|
|
|
|
|
|
|
|
|
|
# Store callback in isolated registry table
|
|
|
|
|
vm.lua_pushstring("GURT_TIMEOUTS")
|
|
|
|
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
|
|
|
|
if vm.lua_isnil(-1):
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
vm.lua_pushstring("GURT_TIMEOUTS")
|
|
|
|
|
vm.lua_pushvalue(-2)
|
|
|
|
|
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
|
|
|
|
|
|
|
|
|
vm.lua_pushinteger(timeout_id)
|
|
|
|
|
vm.lua_pushvalue(1)
|
|
|
|
|
vm.lua_rawset(-3)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
# Create interval info
|
|
|
|
|
var timeout_info = TimeoutInfo.new(timeout_id, timeout_id, vm, self, true, delay_ms)
|
|
|
|
|
|
|
|
|
|
# Create and configure timer for intervals
|
|
|
|
|
var timer = Timer.new()
|
|
|
|
|
timer.wait_time = delay_ms / 1000.0
|
|
|
|
|
timer.one_shot = false # This is the key difference - repeating timer
|
|
|
|
|
timer.timeout.connect(_on_timeout_triggered.bind(timeout_info))
|
|
|
|
|
|
|
|
|
|
timeout_info.timer = timer
|
|
|
|
|
active_timeouts[timeout_id] = timeout_info
|
|
|
|
|
|
|
|
|
|
# Add timer to scene tree
|
|
|
|
|
parent_node.add_child(timer)
|
|
|
|
|
timer.start()
|
|
|
|
|
|
|
|
|
|
vm.lua_pushinteger(timeout_id)
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
func set_threaded_timeout_handler(vm: LuauVM, parent_node: Node, threaded_vm_ref: ThreadedLuaVM) -> int:
|
|
|
|
|
threaded_vm = threaded_vm_ref
|
|
|
|
|
return set_timeout_handler(vm, parent_node)
|
|
|
|
|
|
2025-08-09 14:57:29 +03:00
|
|
|
func set_threaded_interval_handler(vm: LuauVM, parent_node: Node, threaded_vm_ref: ThreadedLuaVM) -> int:
|
|
|
|
|
threaded_vm = threaded_vm_ref
|
|
|
|
|
return set_interval_handler(vm, parent_node)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
func _create_timer_on_main_thread(timeout_info: TimeoutInfo, delay_ms: int, parent_node: Node):
|
|
|
|
|
var timer = Timer.new()
|
|
|
|
|
timer.wait_time = delay_ms / 1000.0
|
|
|
|
|
timer.one_shot = true
|
|
|
|
|
timer.timeout.connect(_on_timeout_triggered.bind(timeout_info))
|
|
|
|
|
|
|
|
|
|
timeout_info.timer = timer
|
|
|
|
|
parent_node.add_child(timer)
|
|
|
|
|
timer.start()
|
|
|
|
|
|
2025-08-04 16:31:16 +03:00
|
|
|
func clear_timeout_handler(vm: LuauVM) -> int:
|
|
|
|
|
var timeout_id: int = vm.luaL_checkint(1)
|
|
|
|
|
|
2025-08-09 14:57:29 +03:00
|
|
|
# If we have a threaded VM, defer the cleanup to main thread
|
|
|
|
|
if threaded_vm:
|
|
|
|
|
call_deferred("_cleanup_timeout_on_main_thread", timeout_id)
|
|
|
|
|
else:
|
|
|
|
|
_cleanup_timeout_immediately(timeout_id)
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
func _cleanup_timeout_on_main_thread(timeout_id: int):
|
|
|
|
|
# This runs on the main thread - safe to access timers
|
|
|
|
|
_cleanup_timeout_immediately(timeout_id)
|
|
|
|
|
|
|
|
|
|
func _cleanup_timeout_immediately(timeout_id: int):
|
2025-08-04 16:31:16 +03:00
|
|
|
var timeout_info = active_timeouts.get(timeout_id, null)
|
|
|
|
|
if timeout_info:
|
|
|
|
|
# Stop and remove timer
|
|
|
|
|
if timeout_info.timer:
|
|
|
|
|
timeout_info.timer.stop()
|
|
|
|
|
timeout_info.timer.queue_free()
|
|
|
|
|
|
|
|
|
|
# Remove from active timeouts
|
|
|
|
|
active_timeouts.erase(timeout_id)
|
2025-08-09 14:57:29 +03:00
|
|
|
|
|
|
|
|
func clear_interval_handler(vm: LuauVM) -> int:
|
|
|
|
|
return clear_timeout_handler(vm)
|
2025-08-04 16:31:16 +03:00
|
|
|
|
|
|
|
|
func _on_timeout_triggered(timeout_info: TimeoutInfo) -> void:
|
|
|
|
|
if not active_timeouts.has(timeout_info.id):
|
|
|
|
|
return
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
if threaded_vm:
|
|
|
|
|
_execute_threaded_timeout_callback(timeout_info.id)
|
2025-08-04 17:04:35 +03:00
|
|
|
|
2025-08-09 14:57:29 +03:00
|
|
|
# For intervals, don't clean up - let them keep running
|
|
|
|
|
# For timeouts, clean up after execution
|
|
|
|
|
if not timeout_info.is_interval:
|
|
|
|
|
timeout_info.timer.queue_free()
|
|
|
|
|
active_timeouts.erase(timeout_info.id)
|
2025-08-04 16:31:16 +03:00
|
|
|
|
|
|
|
|
func cleanup_all_timeouts():
|
|
|
|
|
# Clean up all active timeouts
|
|
|
|
|
for timeout_id in active_timeouts:
|
|
|
|
|
var timeout_info = active_timeouts[timeout_id]
|
|
|
|
|
if timeout_info.timer:
|
|
|
|
|
timeout_info.timer.stop()
|
|
|
|
|
timeout_info.timer.queue_free()
|
|
|
|
|
|
|
|
|
|
# Release Lua callback reference
|
|
|
|
|
if timeout_info.vm and timeout_info.callback_ref:
|
2025-08-04 17:04:35 +03:00
|
|
|
timeout_info.vm.lua_pushstring("GURT_TIMEOUTS")
|
|
|
|
|
timeout_info.vm.lua_rawget(timeout_info.vm.LUA_REGISTRYINDEX)
|
|
|
|
|
if not timeout_info.vm.lua_isnil(-1):
|
|
|
|
|
timeout_info.vm.lua_pushinteger(timeout_info.callback_ref)
|
|
|
|
|
timeout_info.vm.lua_pushnil()
|
|
|
|
|
timeout_info.vm.lua_rawset(-3)
|
|
|
|
|
timeout_info.vm.lua_pop(1)
|
|
|
|
|
active_timeouts.clear()
|
2025-08-08 16:51:21 +03:00
|
|
|
|
|
|
|
|
func _execute_threaded_timeout_callback(timeout_id: int) -> void:
|
|
|
|
|
if threaded_vm and threaded_vm.lua_thread and threaded_vm.lua_thread.is_alive():
|
|
|
|
|
threaded_vm.execute_timeout_callback_async(timeout_id)
|
|
|
|
|
else:
|
|
|
|
|
active_timeouts.erase(timeout_id)
|