670 lines
19 KiB
GDScript
670 lines
19 KiB
GDScript
class_name ThreadedLuaVM
|
|
extends RefCounted
|
|
|
|
signal script_completed(result: Dictionary)
|
|
signal script_error(error: String)
|
|
signal print_output(print_data: Dictionary)
|
|
signal dom_operation_request(operation: Dictionary)
|
|
|
|
var lua_thread: Thread
|
|
var lua_vm: LuauVM
|
|
var lua_api: LuaAPI
|
|
var dom_parser: HTMLParser
|
|
var command_queue: Array = []
|
|
var queue_mutex: Mutex
|
|
var should_exit: bool = false
|
|
var thread_semaphore: Semaphore
|
|
|
|
# Sleep system
|
|
var sleep_mutex: Mutex
|
|
var sleep_condition: bool = false
|
|
var sleep_end_time: float = 0.0
|
|
|
|
func _init():
|
|
queue_mutex = Mutex.new()
|
|
sleep_mutex = Mutex.new()
|
|
thread_semaphore = Semaphore.new()
|
|
|
|
func start_lua_thread(dom_parser_ref: HTMLParser, lua_api_ref: LuaAPI) -> bool:
|
|
if lua_thread and lua_thread.is_alive():
|
|
return false
|
|
|
|
dom_parser = dom_parser_ref
|
|
lua_api = lua_api_ref
|
|
should_exit = false
|
|
|
|
lua_thread = Thread.new()
|
|
var error = lua_thread.start(_lua_thread_worker)
|
|
|
|
if error != OK:
|
|
return false
|
|
|
|
return true
|
|
|
|
func stop_lua_thread():
|
|
if not lua_thread or not lua_thread.is_alive():
|
|
return
|
|
|
|
should_exit = true
|
|
thread_semaphore.post() # Wake up BRO
|
|
|
|
# short time to exit gracefully
|
|
var timeout_start = Time.get_ticks_msec()
|
|
while lua_thread.is_alive() and (Time.get_ticks_msec() - timeout_start) < 500:
|
|
OS.delay_msec(10)
|
|
|
|
lua_thread.wait_to_finish()
|
|
lua_thread = null
|
|
|
|
func execute_script_async(script_code: String, chunk_name: String = "dostring"):
|
|
queue_mutex.lock()
|
|
command_queue.append({
|
|
"type": "execute_script",
|
|
"code": script_code,
|
|
"chunk_name": chunk_name
|
|
})
|
|
queue_mutex.unlock()
|
|
thread_semaphore.post()
|
|
|
|
func execute_callback_async(callback_ref: int, args: Array = []):
|
|
if not lua_thread or not lua_thread.is_alive():
|
|
return
|
|
|
|
queue_mutex.lock()
|
|
command_queue.append({
|
|
"type": "execute_callback",
|
|
"callback_ref": callback_ref,
|
|
"args": args
|
|
})
|
|
queue_mutex.unlock()
|
|
thread_semaphore.post()
|
|
|
|
func execute_timeout_callback_async(timeout_id: int):
|
|
if not lua_thread or not lua_thread.is_alive():
|
|
return
|
|
|
|
queue_mutex.lock()
|
|
command_queue.append({
|
|
"type": "execute_timeout",
|
|
"timeout_id": timeout_id
|
|
})
|
|
queue_mutex.unlock()
|
|
thread_semaphore.post()
|
|
|
|
func sleep_lua(duration_seconds: float):
|
|
sleep_mutex.lock()
|
|
sleep_end_time = Time.get_ticks_msec() / 1000.0 + duration_seconds
|
|
sleep_condition = true
|
|
sleep_mutex.unlock()
|
|
|
|
while true:
|
|
sleep_mutex.lock()
|
|
var current_time = Time.get_ticks_msec() / 1000.0
|
|
var should_continue = sleep_condition and current_time < sleep_end_time
|
|
sleep_mutex.unlock()
|
|
|
|
if not should_continue:
|
|
break
|
|
# Yield to allow other threads to run
|
|
OS.delay_msec(1)
|
|
|
|
func _lua_thread_worker():
|
|
lua_vm = LuauVM.new()
|
|
|
|
lua_vm.open_libraries([lua_vm.LUA_BASE_LIB, lua_vm.LUA_BIT32_LIB,
|
|
lua_vm.LUA_COROUTINE_LIB, lua_vm.LUA_MATH_LIB, lua_vm.LUA_UTF8_LIB,
|
|
lua_vm.LUA_TABLE_LIB, lua_vm.LUA_STRING_LIB, lua_vm.LUA_VECTOR_LIB])
|
|
|
|
lua_vm.lua_pushcallable(_print_handler, "print")
|
|
lua_vm.lua_setglobal("print")
|
|
|
|
# Setup threaded Time.sleep function
|
|
lua_vm.lua_newtable()
|
|
lua_vm.lua_pushcallable(_time_sleep_handler, "Time.sleep")
|
|
lua_vm.lua_setfield(-2, "sleep")
|
|
lua_vm.lua_setglobal("Time")
|
|
|
|
_setup_threaded_gurt_api()
|
|
_setup_additional_lua_apis()
|
|
|
|
while not should_exit:
|
|
if thread_semaphore.try_wait():
|
|
_process_command_queue()
|
|
else:
|
|
OS.delay_msec(10)
|
|
|
|
lua_vm = null
|
|
|
|
func _process_command_queue():
|
|
queue_mutex.lock()
|
|
var commands_to_process = command_queue.duplicate()
|
|
command_queue.clear()
|
|
queue_mutex.unlock()
|
|
|
|
for command in commands_to_process:
|
|
match command.type:
|
|
"execute_script":
|
|
_execute_script_in_thread(command.code, command.get("chunk_name", "dostring"))
|
|
"execute_callback":
|
|
_execute_callback_in_thread(command.callback_ref, command.args)
|
|
"execute_timeout":
|
|
_execute_timeout_in_thread(command.timeout_id)
|
|
|
|
func _execute_script_in_thread(script_code: String, chunk_name: String = "dostring"):
|
|
if not lua_vm:
|
|
call_deferred("_emit_script_error", "Lua VM not initialized")
|
|
return
|
|
|
|
# Use load_string with custom chunk name, then lua_pcall
|
|
var load_result = lua_vm.load_string(script_code, chunk_name)
|
|
|
|
if load_result == lua_vm.LUA_OK:
|
|
var call_result = lua_vm.lua_pcall(0, 0, 0)
|
|
if call_result == lua_vm.LUA_OK:
|
|
call_deferred("_emit_script_completed", {"success": true})
|
|
else:
|
|
var error_msg = lua_vm.lua_tostring(-1)
|
|
lua_vm.lua_pop(1)
|
|
call_deferred("_emit_script_error", error_msg)
|
|
else:
|
|
var error_msg = lua_vm.lua_tostring(-1)
|
|
lua_vm.lua_pop(1)
|
|
call_deferred("_emit_script_error", error_msg)
|
|
|
|
func _call_lua_function_with_args(args: Array) -> bool:
|
|
# Push arguments
|
|
for arg in args:
|
|
lua_vm.lua_pushvariant(arg)
|
|
|
|
# Execute the callback with proper error handling
|
|
if lua_vm.lua_pcall(args.size(), 0, 0) != lua_vm.LUA_OK:
|
|
var error_msg = lua_vm.lua_tostring(-1)
|
|
lua_vm.lua_pop(1)
|
|
call_deferred("_emit_script_error", "Callback error: " + error_msg)
|
|
return false
|
|
return true
|
|
|
|
func _execute_callback_in_thread(callback_ref: int, args: Array):
|
|
if not lua_vm:
|
|
return
|
|
|
|
lua_vm.lua_pushstring("THREADED_CALLBACKS")
|
|
lua_vm.lua_rawget(lua_vm.LUA_REGISTRYINDEX)
|
|
if not lua_vm.lua_isnil(-1):
|
|
lua_vm.lua_pushinteger(callback_ref)
|
|
lua_vm.lua_rawget(-2)
|
|
if lua_vm.lua_isfunction(-1):
|
|
lua_vm.lua_remove(-2) # Remove the table, keep the function
|
|
if _call_lua_function_with_args(args):
|
|
return
|
|
else:
|
|
lua_vm.lua_pop(1) # Pop non-function value
|
|
|
|
lua_vm.lua_pop(1) # Pop the table
|
|
|
|
# Fallback to regular registry lookup
|
|
lua_vm.lua_rawgeti(lua_vm.LUA_REGISTRYINDEX, callback_ref)
|
|
if lua_vm.lua_isfunction(-1):
|
|
_call_lua_function_with_args(args)
|
|
else:
|
|
lua_vm.lua_pop(1)
|
|
|
|
func _execute_timeout_in_thread(timeout_id: int):
|
|
if not lua_vm:
|
|
return
|
|
|
|
# Check if this is an interval by looking at the timeout manager
|
|
var timeout_info = lua_api.timeout_manager.active_timeouts.get(timeout_id, null)
|
|
var is_interval = timeout_info != null and timeout_info.is_interval
|
|
|
|
# Retrieve timeout callback from the special timeout registry
|
|
lua_vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
|
lua_vm.lua_rawget(lua_vm.LUA_REGISTRYINDEX)
|
|
if not lua_vm.lua_isnil(-1):
|
|
lua_vm.lua_pushinteger(timeout_id)
|
|
lua_vm.lua_rawget(-2)
|
|
if lua_vm.lua_isfunction(-1):
|
|
lua_vm.lua_remove(-2) # Remove the table, keep the function
|
|
if _call_lua_function_with_args([]):
|
|
# Only clean up the callback if it's a timeout (not an interval)
|
|
if not is_interval:
|
|
lua_vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
|
lua_vm.lua_rawget(lua_vm.LUA_REGISTRYINDEX)
|
|
if not lua_vm.lua_isnil(-1):
|
|
lua_vm.lua_pushinteger(timeout_id)
|
|
lua_vm.lua_pushnil()
|
|
lua_vm.lua_rawset(-3)
|
|
lua_vm.lua_pop(1)
|
|
return
|
|
else:
|
|
lua_vm.lua_pop(1) # Pop non-function value
|
|
|
|
lua_vm.lua_pop(1) # Pop the table
|
|
|
|
func _print_handler(vm: LuauVM) -> int:
|
|
var message_parts: Array = []
|
|
var num_args = vm.lua_gettop()
|
|
|
|
for i in range(1, num_args + 1):
|
|
var lua_type = vm.lua_type(i)
|
|
if lua_type == vm.LUA_TTABLE:
|
|
var table_data = vm.lua_todictionary(i)
|
|
message_parts.append({
|
|
"type": "table",
|
|
"data": table_data
|
|
})
|
|
else:
|
|
var value_str = LuaPrintUtils.lua_value_to_string(vm, i)
|
|
message_parts.append({
|
|
"type": "primitive",
|
|
"data": value_str
|
|
})
|
|
|
|
var print_data = {
|
|
"parts": message_parts,
|
|
"count": message_parts.size()
|
|
}
|
|
|
|
# Also call trace.log with the formatted message
|
|
var message_strings: Array[String] = []
|
|
for part in message_parts:
|
|
if part.type == "table":
|
|
message_strings.append(str(part.data))
|
|
else:
|
|
message_strings.append(part.data)
|
|
var final_message = "\t".join(message_strings)
|
|
call_deferred("_emit_trace_message", final_message, "log")
|
|
|
|
call_deferred("_emit_print_output", print_data)
|
|
|
|
return 0
|
|
|
|
func _time_sleep_handler(vm: LuauVM) -> int:
|
|
vm.luaL_checknumber(1)
|
|
var seconds = vm.lua_tonumber(1)
|
|
|
|
if seconds > 0:
|
|
sleep_lua(seconds)
|
|
|
|
return 0
|
|
|
|
func _trace_log_handler(vm: LuauVM) -> int:
|
|
var message = vm.luaL_checkstring(1)
|
|
call_deferred("_emit_trace_message", message, "lua")
|
|
return 0
|
|
|
|
func _trace_warning_handler(vm: LuauVM) -> int:
|
|
var message = vm.luaL_checkstring(1)
|
|
call_deferred("_emit_trace_message", message, "warning")
|
|
return 0
|
|
|
|
func _trace_error_handler(vm: LuauVM) -> int:
|
|
var message = vm.luaL_checkstring(1)
|
|
call_deferred("_emit_trace_message", message, "error")
|
|
return 0
|
|
|
|
func _setup_threaded_gurt_api():
|
|
lua_vm.lua_pushcallable(_print_handler, "print")
|
|
lua_vm.lua_setglobal("print")
|
|
|
|
# Setup trace functions
|
|
lua_vm.lua_pushcallable(_trace_log_handler, "_trace_log")
|
|
lua_vm.lua_setglobal("_trace_log")
|
|
|
|
lua_vm.lua_pushcallable(_trace_warning_handler, "_trace_warning")
|
|
lua_vm.lua_setglobal("_trace_warning")
|
|
|
|
lua_vm.lua_pushcallable(_trace_error_handler, "_trace_error")
|
|
lua_vm.lua_setglobal("_trace_error")
|
|
|
|
LuaTimeUtils.setup_time_api(lua_vm)
|
|
|
|
lua_vm.lua_getglobal("Time")
|
|
if not lua_vm.lua_isnil(-1):
|
|
lua_vm.lua_pushcallable(_time_sleep_handler, "Time.sleep")
|
|
lua_vm.lua_setfield(-2, "sleep")
|
|
lua_vm.lua_pop(1)
|
|
|
|
lua_vm.lua_newtable()
|
|
|
|
lua_vm.lua_pushcallable(_gurt_select_handler, "gurt.select")
|
|
lua_vm.lua_setfield(-2, "select")
|
|
|
|
lua_vm.lua_pushcallable(_gurt_select_all_handler, "gurt.selectAll")
|
|
lua_vm.lua_setfield(-2, "selectAll")
|
|
|
|
lua_vm.lua_pushcallable(_gurt_create_handler, "gurt.create")
|
|
lua_vm.lua_setfield(-2, "create")
|
|
|
|
# Add timer functions as global functions
|
|
lua_vm.lua_pushcallable(_set_timeout_handler, "setTimeout")
|
|
lua_vm.lua_setglobal("setTimeout")
|
|
|
|
lua_vm.lua_pushcallable(lua_api._gurt_clear_timeout_handler, "clearTimeout")
|
|
lua_vm.lua_setglobal("clearTimeout")
|
|
|
|
lua_vm.lua_pushcallable(_set_interval_handler, "setInterval")
|
|
lua_vm.lua_setglobal("setInterval")
|
|
|
|
lua_vm.lua_pushcallable(lua_api._gurt_clear_interval_handler, "clearInterval")
|
|
lua_vm.lua_setglobal("clearInterval")
|
|
|
|
lua_vm.lua_newtable()
|
|
lua_vm.lua_pushcallable(lua_api._gurt_location_reload_handler, "gurt.location.reload")
|
|
lua_vm.lua_setfield(-2, "reload")
|
|
lua_vm.lua_pushcallable(lua_api._gurt_location_goto_handler, "gurt.location.goto")
|
|
lua_vm.lua_setfield(-2, "goto")
|
|
|
|
var current_href = get_current_href()
|
|
lua_vm.lua_pushstring(current_href)
|
|
lua_vm.lua_setfield(-2, "href")
|
|
|
|
lua_vm.lua_newtable()
|
|
lua_vm.lua_pushcallable(lua_api._gurt_location_query_get_handler, "gurt.location.query.get")
|
|
lua_vm.lua_setfield(-2, "get")
|
|
lua_vm.lua_pushcallable(lua_api._gurt_location_query_has_handler, "gurt.location.query.has")
|
|
lua_vm.lua_setfield(-2, "has")
|
|
lua_vm.lua_pushcallable(lua_api._gurt_location_query_getAll_handler, "gurt.location.query.getAll")
|
|
lua_vm.lua_setfield(-2, "getAll")
|
|
lua_vm.lua_setfield(-2, "query")
|
|
|
|
lua_vm.lua_setfield(-2, "location")
|
|
|
|
var body_element = dom_parser.find_first("body")
|
|
if body_element:
|
|
LuaDOMUtils.create_element_wrapper(lua_vm, body_element, lua_api)
|
|
lua_vm.lua_pushcallable(_body_on_handler, "body.on")
|
|
lua_vm.lua_setfield(-2, "on")
|
|
lua_vm.lua_setfield(-2, "body")
|
|
|
|
lua_vm.lua_pushcallable(_gurt_get_width_handler, "gurt.width")
|
|
lua_vm.lua_setfield(-2, "width")
|
|
|
|
lua_vm.lua_pushcallable(_gurt_get_height_handler, "gurt.height")
|
|
lua_vm.lua_setfield(-2, "height")
|
|
|
|
lua_vm.lua_setglobal("gurt")
|
|
|
|
func _setup_additional_lua_apis():
|
|
lua_vm.lua_getglobal("table")
|
|
if lua_vm.lua_isnil(-1):
|
|
lua_vm.lua_pop(1)
|
|
lua_vm.lua_newtable()
|
|
lua_vm.lua_setglobal("table")
|
|
lua_vm.lua_getglobal("table")
|
|
|
|
lua_vm.lua_pushcallable(_table_tostring_handler, "table.tostring")
|
|
lua_vm.lua_setfield(-2, "tostring")
|
|
lua_vm.lua_pop(1)
|
|
|
|
LuaSignalUtils.setup_signal_api(lua_vm)
|
|
LuaClipboardUtils.setup_clipboard_api(lua_vm)
|
|
LuaNetworkUtils.setup_network_api(lua_vm)
|
|
LuaJSONUtils.setup_json_api(lua_vm)
|
|
LuaWebSocketUtils.setup_websocket_api(lua_vm)
|
|
LuaAudioUtils.setup_audio_api(lua_vm)
|
|
LuaDownloadUtils.setup_download_api(lua_vm)
|
|
LuaCrumbsUtils.setup_crumbs_api(lua_vm)
|
|
LuaRegexUtils.setup_regex_api(lua_vm)
|
|
LuaURLUtils.setup_url_api(lua_vm)
|
|
Trace.setup_trace_api(lua_vm)
|
|
|
|
func _table_tostring_handler(vm: LuauVM) -> int:
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
var table_string = LuaPrintUtils.table_to_string(vm, 1)
|
|
vm.lua_pushstring(table_string)
|
|
return 1
|
|
|
|
func _emit_script_completed(result: Dictionary):
|
|
script_completed.emit(result)
|
|
|
|
func _emit_script_error(error: String):
|
|
script_error.emit(error)
|
|
|
|
func _emit_print_output(print_data: Dictionary):
|
|
print_output.emit(print_data)
|
|
|
|
func _gurt_select_all_handler(vm: LuauVM) -> int:
|
|
var selector: String = vm.luaL_checkstring(1)
|
|
|
|
if not dom_parser or not dom_parser.parse_result:
|
|
vm.lua_newtable()
|
|
return 1
|
|
|
|
var elements = SelectorUtils.find_all_matching(selector, dom_parser.parse_result.all_elements)
|
|
|
|
vm.lua_newtable()
|
|
for i in range(elements.size()):
|
|
vm.lua_pushinteger(i + 1)
|
|
LuaDOMUtils.create_element_wrapper(vm, elements[i], lua_api)
|
|
vm.lua_rawset(-3)
|
|
|
|
return 1
|
|
|
|
func _gurt_create_handler(vm: LuauVM) -> int:
|
|
var tag_name: String = vm.luaL_checkstring(1)
|
|
var attributes = {}
|
|
|
|
if vm.lua_gettop() >= 2 and not vm.lua_isnil(2):
|
|
vm.luaL_checktype(2, vm.LUA_TTABLE)
|
|
attributes = vm.lua_todictionary(2)
|
|
|
|
var new_element = HTMLParser.HTMLElement.new(tag_name)
|
|
|
|
for attr_name in attributes:
|
|
if attr_name == "text":
|
|
new_element.text_content = str(attributes[attr_name])
|
|
else:
|
|
new_element.set_attribute(attr_name, str(attributes[attr_name]))
|
|
|
|
var element_id = lua_api.get_or_assign_element_id(new_element)
|
|
new_element.set_attribute("id", element_id)
|
|
|
|
dom_parser.parse_result.all_elements.append(new_element)
|
|
|
|
LuaDOMUtils.create_element_wrapper(vm, new_element, lua_api)
|
|
return 1
|
|
|
|
func _set_timeout_handler(vm: LuauVM) -> int:
|
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
|
var delay_ms: int = vm.luaL_checkint(2)
|
|
|
|
var timeout_id = lua_api.timeout_manager.next_timeout_id
|
|
lua_api.timeout_manager.next_timeout_id += 1
|
|
|
|
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
|
if vm.lua_isnil(-1):
|
|
vm.lua_pop(1)
|
|
vm.lua_newtable()
|
|
vm.lua_pushstring("GURT_THREADED_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)
|
|
|
|
call_deferred("_create_threaded_timeout", timeout_id, delay_ms)
|
|
|
|
vm.lua_pushinteger(timeout_id)
|
|
return 1
|
|
|
|
func _clear_timeout_handler(vm: LuauVM) -> int:
|
|
return lua_api._gurt_clear_timeout_handler(vm)
|
|
|
|
func _set_interval_handler(vm: LuauVM) -> int:
|
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
|
var delay_ms: int = vm.luaL_checkint(2)
|
|
|
|
var interval_id = lua_api.timeout_manager.next_timeout_id
|
|
lua_api.timeout_manager.next_timeout_id += 1
|
|
|
|
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
|
if vm.lua_isnil(-1):
|
|
vm.lua_pop(1)
|
|
vm.lua_newtable()
|
|
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
|
vm.lua_pushvalue(-2)
|
|
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
|
|
|
vm.lua_pushinteger(interval_id)
|
|
vm.lua_pushvalue(1)
|
|
vm.lua_rawset(-3)
|
|
vm.lua_pop(1)
|
|
|
|
call_deferred("_create_threaded_interval", interval_id, delay_ms)
|
|
|
|
vm.lua_pushinteger(interval_id)
|
|
return 1
|
|
|
|
func get_current_href() -> String:
|
|
var main_node = Engine.get_main_loop().current_scene
|
|
|
|
if main_node == null:
|
|
return ""
|
|
|
|
return main_node.current_domain
|
|
|
|
func _gurt_get_width_handler(vm: LuauVM) -> int:
|
|
var result = [0.0]
|
|
call_deferred("_get_width_sync", result)
|
|
while result[0] == 0.0:
|
|
OS.delay_msec(1)
|
|
vm.lua_pushnumber(result[0])
|
|
return 1
|
|
|
|
func _gurt_get_height_handler(vm: LuauVM) -> int:
|
|
var result = [0.0]
|
|
call_deferred("_get_height_sync", result)
|
|
while result[0] == 0.0:
|
|
OS.delay_msec(1)
|
|
vm.lua_pushnumber(result[0])
|
|
return 1
|
|
|
|
func _get_width_sync(result: Array):
|
|
var main_node = Engine.get_main_loop().current_scene
|
|
if main_node:
|
|
var active_tab = main_node.get_active_tab()
|
|
if active_tab and active_tab.website_container:
|
|
result[0] = active_tab.website_container.size.x
|
|
else:
|
|
result[0] = 1024.0
|
|
else:
|
|
result[0] = 1024.0
|
|
|
|
func _get_height_sync(result: Array):
|
|
var main_node = Engine.get_main_loop().current_scene
|
|
if main_node:
|
|
var active_tab = main_node.get_active_tab()
|
|
if active_tab and active_tab.website_container:
|
|
result[0] = active_tab.website_container.size.y
|
|
else:
|
|
result[0] = 768.0
|
|
else:
|
|
result[0] = 768.0
|
|
|
|
|
|
func _gurt_select_handler(vm: LuauVM) -> int:
|
|
var selector: String = vm.luaL_checkstring(1)
|
|
|
|
if not dom_parser or not dom_parser.parse_result:
|
|
vm.lua_pushnil()
|
|
return 1
|
|
|
|
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
|
if element:
|
|
LuaDOMUtils.create_element_wrapper(vm, element, lua_api)
|
|
return 1
|
|
else:
|
|
vm.lua_pushnil()
|
|
return 1
|
|
|
|
func _body_on_handler(vm: LuauVM) -> int:
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
var event_name: String = vm.luaL_checkstring(2)
|
|
vm.luaL_checktype(3, vm.LUA_TFUNCTION)
|
|
|
|
vm.lua_pushstring("THREADED_CALLBACKS")
|
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
|
if vm.lua_isnil(-1):
|
|
vm.lua_pop(1)
|
|
vm.lua_newtable()
|
|
vm.lua_pushstring("THREADED_CALLBACKS")
|
|
vm.lua_pushvalue(-2)
|
|
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
|
|
|
var callback_ref = lua_api.next_callback_ref
|
|
lua_api.next_callback_ref += 1
|
|
|
|
var subscription_id = lua_api.next_subscription_id
|
|
lua_api.next_subscription_id += 1
|
|
|
|
vm.lua_pushinteger(callback_ref)
|
|
vm.lua_pushvalue(3)
|
|
vm.lua_rawset(-3)
|
|
vm.lua_pop(1)
|
|
|
|
var operation = {
|
|
"type": "register_body_event",
|
|
"event_name": event_name,
|
|
"callback_ref": callback_ref,
|
|
"subscription_id": subscription_id
|
|
}
|
|
|
|
call_deferred("_emit_dom_operation_request", operation)
|
|
|
|
vm.lua_newtable()
|
|
vm.lua_pushinteger(subscription_id)
|
|
vm.lua_setfield(-2, "_subscription_id")
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._unsubscribe_wrapper, "subscription.unsubscribe")
|
|
vm.lua_setfield(-2, "unsubscribe")
|
|
return 1
|
|
|
|
func _emit_dom_operation_request(operation: Dictionary):
|
|
dom_operation_request.emit(operation)
|
|
|
|
func _create_threaded_timeout(timeout_id: int, delay_ms: int):
|
|
lua_api._ensure_timeout_manager()
|
|
|
|
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(timeout_id, timeout_id, lua_vm, lua_api.timeout_manager, false, delay_ms)
|
|
lua_api.timeout_manager.active_timeouts[timeout_id] = timeout_info
|
|
lua_api.timeout_manager.threaded_vm = self
|
|
|
|
var timer = Timer.new()
|
|
timer.wait_time = delay_ms / 1000.0
|
|
timer.one_shot = true
|
|
timer.timeout.connect(lua_api.timeout_manager._on_timeout_triggered.bind(timeout_info))
|
|
|
|
timeout_info.timer = timer
|
|
lua_api.add_child(timer)
|
|
timer.start()
|
|
|
|
func _create_threaded_interval(interval_id: int, delay_ms: int):
|
|
lua_api._ensure_timeout_manager()
|
|
|
|
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(interval_id, interval_id, lua_vm, lua_api.timeout_manager, true, delay_ms)
|
|
lua_api.timeout_manager.active_timeouts[interval_id] = timeout_info
|
|
lua_api.timeout_manager.threaded_vm = self
|
|
|
|
var timer = Timer.new()
|
|
timer.wait_time = delay_ms / 1000.0
|
|
timer.one_shot = false
|
|
timer.timeout.connect(lua_api.timeout_manager._on_timeout_triggered.bind(timeout_info))
|
|
|
|
timeout_info.timer = timer
|
|
lua_api.add_child(timer)
|
|
timer.start()
|
|
|
|
func _emit_trace_message(message: String, level: String):
|
|
match level:
|
|
"lua", "log":
|
|
Trace.trace_log(message)
|
|
"warning":
|
|
Trace.trace_warning(message)
|
|
"error":
|
|
Trace.trace_error(message)
|