threaded Lua VM
This commit is contained in:
@@ -354,8 +354,8 @@ func get_all_scripts() -> Array[String]:
|
|||||||
return get_attribute_values("script", "src")
|
return get_attribute_values("script", "src")
|
||||||
|
|
||||||
func process_scripts(lua_api: LuaAPI, lua_vm) -> void:
|
func process_scripts(lua_api: LuaAPI, lua_vm) -> void:
|
||||||
if not lua_api or not lua_vm:
|
if not lua_api:
|
||||||
print("Warning: Lua API or VM not available for script processing")
|
print("Warning: Lua API not available for script processing")
|
||||||
return
|
return
|
||||||
|
|
||||||
lua_api.dom_parser = self
|
lua_api.dom_parser = self
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
class_name LuaAPI
|
class_name LuaAPI
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
var threaded_vm: ThreadedLuaVM
|
||||||
|
var script_start_time: float = 0.0
|
||||||
|
|
||||||
class EventSubscription:
|
class EventSubscription:
|
||||||
var id: int
|
var id: int
|
||||||
var element_id: String
|
var element_id: String
|
||||||
@@ -17,12 +20,15 @@ var event_subscriptions: Dictionary = {}
|
|||||||
var next_subscription_id: int = 1
|
var next_subscription_id: int = 1
|
||||||
var next_callback_ref: int = 1
|
var next_callback_ref: int = 1
|
||||||
|
|
||||||
var timeout_manager: LuaTimeoutManager
|
|
||||||
var element_id_counter: int = 1
|
var element_id_counter: int = 1
|
||||||
var element_id_registry: Dictionary = {}
|
var element_id_registry: Dictionary = {}
|
||||||
|
|
||||||
func _init():
|
func _init():
|
||||||
timeout_manager = LuaTimeoutManager.new()
|
timeout_manager = LuaTimeoutManager.new()
|
||||||
|
threaded_vm = ThreadedLuaVM.new()
|
||||||
|
threaded_vm.script_completed.connect(_on_threaded_script_completed)
|
||||||
|
threaded_vm.dom_operation_request.connect(_handle_dom_operation)
|
||||||
|
threaded_vm.print_output.connect(_on_print_output)
|
||||||
|
|
||||||
func get_or_assign_element_id(element: HTMLParser.HTMLElement) -> String:
|
func get_or_assign_element_id(element: HTMLParser.HTMLElement) -> String:
|
||||||
var existing_id = element.get_attribute("id")
|
var existing_id = element.get_attribute("id")
|
||||||
@@ -57,7 +63,7 @@ func _gurt_select_handler(vm: LuauVM) -> int:
|
|||||||
vm.lua_pushstring(element.tag_name)
|
vm.lua_pushstring(element.tag_name)
|
||||||
vm.lua_setfield(-2, "_tag_name")
|
vm.lua_setfield(-2, "_tag_name")
|
||||||
|
|
||||||
add_element_methods(vm)
|
LuaDOMUtils.add_element_methods(vm, self)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# selectAll() function to find multiple elements
|
# selectAll() function to find multiple elements
|
||||||
@@ -79,7 +85,7 @@ func _gurt_select_all_handler(vm: LuauVM) -> int:
|
|||||||
vm.lua_pushstring(element.tag_name)
|
vm.lua_pushstring(element.tag_name)
|
||||||
vm.lua_setfield(-2, "_tag_name")
|
vm.lua_setfield(-2, "_tag_name")
|
||||||
|
|
||||||
add_element_methods(vm)
|
LuaDOMUtils.add_element_methods(vm, self)
|
||||||
|
|
||||||
# Add to array at index
|
# Add to array at index
|
||||||
vm.lua_rawseti(-2, index)
|
vm.lua_rawseti(-2, index)
|
||||||
@@ -119,355 +125,22 @@ func _gurt_create_handler(vm: LuauVM) -> int:
|
|||||||
vm.lua_pushboolean(true)
|
vm.lua_pushboolean(true)
|
||||||
vm.lua_setfield(-2, "_is_dynamic")
|
vm.lua_setfield(-2, "_is_dynamic")
|
||||||
|
|
||||||
add_element_methods(vm)
|
LuaDOMUtils.add_element_methods(vm, self)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
func add_element_methods(vm: LuauVM, index: String = "element") -> void:
|
var timeout_manager: LuaTimeoutManager
|
||||||
# Add methods directly to element table first
|
|
||||||
vm.lua_pushcallable(_element_on_event_handler, index + ".on")
|
|
||||||
vm.lua_setfield(-2, "on")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_element_append_handler, index + ".append")
|
func _ensure_timeout_manager():
|
||||||
vm.lua_setfield(-2, "append")
|
if not timeout_manager:
|
||||||
|
timeout_manager = LuaTimeoutManager.new()
|
||||||
vm.lua_pushcallable(_element_remove_handler, index + ".remove")
|
|
||||||
vm.lua_setfield(-2, "remove")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_element_get_attribute_handler, index + ".getAttribute")
|
|
||||||
vm.lua_setfield(-2, "getAttribute")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_element_set_attribute_handler, index + ".setAttribute")
|
|
||||||
vm.lua_setfield(-2, "setAttribute")
|
|
||||||
|
|
||||||
LuaDOMUtils.add_enhanced_element_methods(vm, self, index)
|
|
||||||
|
|
||||||
vm.lua_newtable()
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_index_handler, index + ".__index")
|
|
||||||
vm.lua_setfield(-2, "__index")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_element_newindex_handler, index + ".__newindex")
|
|
||||||
vm.lua_setfield(-2, "__newindex")
|
|
||||||
|
|
||||||
vm.lua_setmetatable(-2)
|
|
||||||
|
|
||||||
func _index_handler(vm: LuauVM) -> int:
|
|
||||||
return LuaDOMUtils._index_handler(vm, self)
|
|
||||||
|
|
||||||
func _element_index_handler(vm: LuauVM) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
||||||
var key: String = vm.luaL_checkstring(2)
|
|
||||||
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
var element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
match key:
|
|
||||||
"text":
|
|
||||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
||||||
var text = ""
|
|
||||||
|
|
||||||
var text_node = get_dom_node(dom_node, "text")
|
|
||||||
if text_node:
|
|
||||||
if text_node.has_method("get_text"):
|
|
||||||
text = text_node.get_text()
|
|
||||||
else:
|
|
||||||
text = text_node.text
|
|
||||||
|
|
||||||
vm.lua_pushstring(text)
|
|
||||||
return 1
|
|
||||||
"children":
|
|
||||||
# Find the element
|
|
||||||
var element: HTMLParser.HTMLElement = null
|
|
||||||
if element_id == "body":
|
|
||||||
element = dom_parser.find_first("body")
|
|
||||||
else:
|
|
||||||
element = dom_parser.find_by_id(element_id)
|
|
||||||
|
|
||||||
vm.lua_newtable()
|
|
||||||
var index = 1
|
|
||||||
|
|
||||||
if element:
|
|
||||||
for child in element.children:
|
|
||||||
vm.lua_newtable()
|
|
||||||
vm.lua_pushstring(child.tag_name)
|
|
||||||
vm.lua_setfield(-2, "tagName")
|
|
||||||
vm.lua_pushstring(child.get_text_content())
|
|
||||||
vm.lua_setfield(-2, "text")
|
|
||||||
|
|
||||||
vm.lua_rawseti(-2, index)
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
return 1
|
|
||||||
"classList":
|
|
||||||
# Create classList object with add, remove, toggle methods
|
|
||||||
vm.lua_newtable()
|
|
||||||
|
|
||||||
# Add methods to classList using the utility class
|
|
||||||
vm.lua_pushcallable(_element_classlist_add_wrapper, "classList.add")
|
|
||||||
vm.lua_setfield(-2, "add")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_element_classlist_remove_wrapper, "classList.remove")
|
|
||||||
vm.lua_setfield(-2, "remove")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(_element_classlist_toggle_wrapper, "classList.toggle")
|
|
||||||
vm.lua_setfield(-2, "toggle")
|
|
||||||
|
|
||||||
# Store element reference for the classList methods
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
vm.lua_setfield(-2, "_element_id")
|
|
||||||
|
|
||||||
return 1
|
|
||||||
_:
|
|
||||||
# Fall back to checking the original table for methods
|
|
||||||
vm.lua_pushvalue(1) # Push the original table
|
|
||||||
vm.lua_pushstring(key) # Push the key
|
|
||||||
vm.lua_rawget(-2) # Get table[key] without triggering metamethods
|
|
||||||
vm.lua_remove(-2) # Remove the table, leaving just the result
|
|
||||||
return 1
|
|
||||||
|
|
||||||
func _element_newindex_handler(vm: LuauVM) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
||||||
var key: String = vm.luaL_checkstring(2)
|
|
||||||
var value = vm.lua_tovariant(3)
|
|
||||||
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
var element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
match key:
|
|
||||||
"text":
|
|
||||||
var text: String = str(value)
|
|
||||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
||||||
var text_node = get_dom_node(dom_node, "text")
|
|
||||||
if text_node:
|
|
||||||
text_node.text = text
|
|
||||||
_:
|
|
||||||
# Ignore unknown properties
|
|
||||||
pass
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# append() function to add a child element
|
|
||||||
func _element_append_handler(vm: LuauVM) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
||||||
vm.luaL_checktype(2, vm.LUA_TTABLE)
|
|
||||||
|
|
||||||
# Get parent element info
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
var parent_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Get child element info
|
|
||||||
vm.lua_getfield(2, "_element_id")
|
|
||||||
var child_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
vm.lua_getfield(2, "_is_dynamic")
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find parent element
|
|
||||||
var parent_element: HTMLParser.HTMLElement = null
|
|
||||||
if parent_element_id == "body":
|
|
||||||
parent_element = dom_parser.find_first("body")
|
|
||||||
else:
|
|
||||||
parent_element = dom_parser.find_by_id(parent_element_id)
|
|
||||||
|
|
||||||
if not parent_element:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Find child element
|
|
||||||
var child_element = dom_parser.find_by_id(child_element_id)
|
|
||||||
if not child_element:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Add child to parent in DOM tree
|
|
||||||
child_element.parent = parent_element
|
|
||||||
parent_element.children.append(child_element)
|
|
||||||
|
|
||||||
# If the parent is already rendered, we need to create and add the visual node
|
|
||||||
var parent_dom_node: Node = null
|
|
||||||
if parent_element_id == "body":
|
|
||||||
var main_scene = get_node("/root/Main")
|
|
||||||
if main_scene:
|
|
||||||
parent_dom_node = main_scene.website_container
|
|
||||||
else:
|
|
||||||
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_element_id, null)
|
|
||||||
|
|
||||||
if parent_dom_node:
|
|
||||||
_render_new_element.call_deferred(child_element, parent_dom_node)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# remove() function to remove an element
|
|
||||||
func _element_remove_handler(vm: LuauVM) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
||||||
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
var element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find the element in DOM
|
|
||||||
var element = dom_parser.find_by_id(element_id)
|
|
||||||
if not element:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Remove from parent's children array
|
|
||||||
if element.parent:
|
|
||||||
var parent_children = element.parent.children
|
|
||||||
var idx = parent_children.find(element)
|
|
||||||
if idx >= 0:
|
|
||||||
parent_children.remove_at(idx)
|
|
||||||
|
|
||||||
# Remove the visual node
|
|
||||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
||||||
if dom_node:
|
|
||||||
dom_node.queue_free()
|
|
||||||
dom_parser.parse_result.dom_nodes.erase(element_id)
|
|
||||||
|
|
||||||
# Remove from all_elements array
|
|
||||||
var all_elements = dom_parser.parse_result.all_elements
|
|
||||||
var index = all_elements.find(element)
|
|
||||||
if index >= 0:
|
|
||||||
all_elements.remove_at(index)
|
|
||||||
|
|
||||||
# Remove from element_id_registry to avoid memory leaks
|
|
||||||
if element_id_registry.has(element):
|
|
||||||
element_id_registry.erase(element)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# getAttribute() function to get element attribute
|
|
||||||
func _element_get_attribute_handler(vm: LuauVM) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
||||||
var attribute_name: String = vm.luaL_checkstring(2)
|
|
||||||
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
var element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find the element
|
|
||||||
var element: HTMLParser.HTMLElement = null
|
|
||||||
if element_id == "body":
|
|
||||||
element = dom_parser.find_first("body")
|
|
||||||
else:
|
|
||||||
element = dom_parser.find_by_id(element_id)
|
|
||||||
|
|
||||||
if not element:
|
|
||||||
vm.lua_pushnil()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Get the attribute value
|
|
||||||
var attribute_value = element.get_attribute(attribute_name)
|
|
||||||
if attribute_value.is_empty():
|
|
||||||
vm.lua_pushnil()
|
|
||||||
else:
|
|
||||||
vm.lua_pushstring(attribute_value)
|
|
||||||
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# setAttribute() function to set element attribute
|
|
||||||
func _element_set_attribute_handler(vm: LuauVM) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
||||||
var attribute_name: String = vm.luaL_checkstring(2)
|
|
||||||
var attribute_value: String = vm.luaL_checkstring(3)
|
|
||||||
|
|
||||||
vm.lua_getfield(1, "_element_id")
|
|
||||||
var element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find the element
|
|
||||||
var element: HTMLParser.HTMLElement = null
|
|
||||||
if element_id == "body":
|
|
||||||
element = dom_parser.find_first("body")
|
|
||||||
else:
|
|
||||||
element = dom_parser.find_by_id(element_id)
|
|
||||||
|
|
||||||
if not element:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if attribute_value == "":
|
|
||||||
element.attributes.erase(attribute_name)
|
|
||||||
else:
|
|
||||||
element.set_attribute(attribute_name, attribute_value)
|
|
||||||
|
|
||||||
# Trigger visual update by calling init() again
|
|
||||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
||||||
if dom_node and dom_node.has_method("init"):
|
|
||||||
dom_node.init(element, dom_parser)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
func _element_classlist_add_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaClassListUtils.element_classlist_add_handler(vm, dom_parser)
|
|
||||||
|
|
||||||
func _element_classlist_remove_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaClassListUtils.element_classlist_remove_handler(vm, dom_parser)
|
|
||||||
|
|
||||||
func _element_classlist_toggle_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaClassListUtils.element_classlist_toggle_handler(vm, dom_parser)
|
|
||||||
|
|
||||||
# DOM manipulation wrapper functions
|
|
||||||
func _element_insert_before_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaDOMUtils.insert_before_handler(vm, dom_parser, self)
|
|
||||||
|
|
||||||
func _element_insert_after_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaDOMUtils.insert_after_handler(vm, dom_parser, self)
|
|
||||||
|
|
||||||
func _element_replace_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaDOMUtils.replace_handler(vm, dom_parser, self)
|
|
||||||
|
|
||||||
func _element_clone_wrapper(vm: LuauVM) -> int:
|
|
||||||
return LuaDOMUtils.clone_handler(vm, dom_parser, self)
|
|
||||||
|
|
||||||
# DOM traversal property wrapper functions
|
|
||||||
func _get_element_parent_wrapper(vm: LuauVM, lua_api: LuaAPI) -> int:
|
|
||||||
return LuaDOMUtils.get_element_parent_handler(vm, dom_parser, lua_api)
|
|
||||||
|
|
||||||
func _get_element_next_sibling_wrapper(vm: LuauVM, lua_api: LuaAPI) -> int:
|
|
||||||
return LuaDOMUtils.get_element_next_sibling_handler(vm, dom_parser, lua_api)
|
|
||||||
|
|
||||||
func _get_element_previous_sibling_wrapper(vm: LuauVM, lua_api: LuaAPI) -> int:
|
|
||||||
return LuaDOMUtils.get_element_previous_sibling_handler(vm, dom_parser, lua_api)
|
|
||||||
|
|
||||||
func _get_element_first_child_wrapper(vm: LuauVM, lua_api: LuaAPI) -> int:
|
|
||||||
return LuaDOMUtils.get_element_first_child_handler(vm, dom_parser, lua_api)
|
|
||||||
|
|
||||||
func _get_element_last_child_wrapper(vm: LuauVM, lua_api: LuaAPI) -> int:
|
|
||||||
return LuaDOMUtils.get_element_last_child_handler(vm, dom_parser, lua_api)
|
|
||||||
|
|
||||||
func _render_new_element(element: HTMLParser.HTMLElement, parent_node: Node) -> void:
|
|
||||||
# Get reference to main scene for rendering
|
|
||||||
var main_scene = get_node("/root/Main")
|
|
||||||
if not main_scene:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create the visual node for the element
|
|
||||||
var element_node = await main_scene.create_element_node(element, dom_parser)
|
|
||||||
if not element_node:
|
|
||||||
LuaPrintUtils.lua_print_direct("Failed to create visual node for element: " + str(element))
|
|
||||||
return
|
|
||||||
|
|
||||||
# Set metadata so ul/ol can detect dynamically added li elements
|
|
||||||
element_node.set_meta("html_element", element)
|
|
||||||
|
|
||||||
# Register the DOM node
|
|
||||||
dom_parser.register_dom_node(element, element_node)
|
|
||||||
|
|
||||||
# Add to parent - handle body special case
|
|
||||||
var container_node = parent_node
|
|
||||||
if parent_node is MarginContainer and parent_node.get_child_count() > 0:
|
|
||||||
container_node = parent_node.get_child(0)
|
|
||||||
elif parent_node == main_scene.website_container:
|
|
||||||
container_node = parent_node
|
|
||||||
|
|
||||||
main_scene.safe_add_child(container_node, element_node)
|
|
||||||
|
|
||||||
# Timeout management handlers
|
# Timeout management handlers
|
||||||
func _gurt_set_timeout_handler(vm: LuauVM) -> int:
|
func _gurt_set_timeout_handler(vm: LuauVM) -> int:
|
||||||
return timeout_manager.set_timeout_handler(vm, self)
|
_ensure_timeout_manager()
|
||||||
|
return timeout_manager.set_threaded_timeout_handler(vm, self, threaded_vm)
|
||||||
|
|
||||||
func _gurt_clear_timeout_handler(vm: LuauVM) -> int:
|
func _gurt_clear_timeout_handler(vm: LuauVM) -> int:
|
||||||
|
_ensure_timeout_manager()
|
||||||
return timeout_manager.clear_timeout_handler(vm)
|
return timeout_manager.clear_timeout_handler(vm)
|
||||||
|
|
||||||
# Event system handlers
|
# Event system handlers
|
||||||
@@ -480,20 +153,22 @@ func _element_on_event_handler(vm: LuauVM) -> int:
|
|||||||
var element_id: String = vm.lua_tostring(-1)
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
vm.lua_pop(1)
|
vm.lua_pop(1)
|
||||||
|
|
||||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
# Create a proper subscription with real ID
|
||||||
if not dom_node:
|
|
||||||
vm.lua_pushnil()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
var subscription = _create_subscription(vm, element_id, event_name)
|
var subscription = _create_subscription(vm, element_id, event_name)
|
||||||
event_subscriptions[subscription.id] = subscription
|
event_subscriptions[subscription.id] = subscription
|
||||||
|
|
||||||
var signal_node = get_dom_node(dom_node, "signal")
|
# Register the event on main thread
|
||||||
var success = LuaEventUtils.connect_element_event(signal_node, event_name, subscription)
|
call_deferred("_register_event_on_main_thread", element_id, event_name, subscription.callback_ref, subscription.id)
|
||||||
if not success:
|
|
||||||
print("ERROR: Failed to connect ", event_name, " event for ", element_id)
|
|
||||||
|
|
||||||
return _handle_subscription_result(vm, subscription, success)
|
# Return subscription with proper unsubscribe method
|
||||||
|
vm.lua_newtable()
|
||||||
|
vm.lua_pushinteger(subscription.id)
|
||||||
|
vm.lua_setfield(-2, "_subscription_id")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(_subscription_unsubscribe_handler, "subscription.unsubscribe")
|
||||||
|
vm.lua_setfield(-2, "unsubscribe")
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
func _body_on_event_handler(vm: LuauVM) -> int:
|
func _body_on_event_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
@@ -565,13 +240,7 @@ func _on_event_triggered(subscription: EventSubscription) -> void:
|
|||||||
if not event_subscriptions.has(subscription.id):
|
if not event_subscriptions.has(subscription.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
subscription.vm.lua_rawgeti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref)
|
_execute_lua_callback(subscription)
|
||||||
if subscription.vm.lua_isfunction(-1):
|
|
||||||
if subscription.vm.lua_pcall(0, 0, 0) != subscription.vm.LUA_OK:
|
|
||||||
print("GURT ERROR in event callback: ", subscription.vm.lua_tostring(-1))
|
|
||||||
subscription.vm.lua_pop(1)
|
|
||||||
else:
|
|
||||||
subscription.vm.lua_pop(1)
|
|
||||||
|
|
||||||
func _on_gui_input_click(event: InputEvent, subscription: EventSubscription) -> void:
|
func _on_gui_input_click(event: InputEvent, subscription: EventSubscription) -> void:
|
||||||
if not event_subscriptions.has(subscription.id):
|
if not event_subscriptions.has(subscription.id):
|
||||||
@@ -618,31 +287,18 @@ func _on_focus_gui_input(event: InputEvent, subscription: EventSubscription) ->
|
|||||||
if subscription.event_name == "focusin":
|
if subscription.event_name == "focusin":
|
||||||
_execute_lua_callback(subscription)
|
_execute_lua_callback(subscription)
|
||||||
|
|
||||||
func _on_body_mouse_enter(subscription: EventSubscription) -> void:
|
func _handle_body_event(subscription: EventSubscription, event_name: String, event_data: Dictionary = {}) -> void:
|
||||||
if not event_subscriptions.has(subscription.id):
|
if event_subscriptions.has(subscription.id) and subscription.event_name == event_name:
|
||||||
return
|
_execute_lua_callback(subscription, [event_data])
|
||||||
|
|
||||||
if subscription.event_name == "mouseenter":
|
func _on_body_mouse_enter(subscription: EventSubscription) -> void:
|
||||||
_execute_lua_callback(subscription)
|
_handle_body_event(subscription, "mouseenter", {})
|
||||||
|
|
||||||
func _on_body_mouse_exit(subscription: EventSubscription) -> void:
|
func _on_body_mouse_exit(subscription: EventSubscription) -> void:
|
||||||
if not event_subscriptions.has(subscription.id):
|
_handle_body_event(subscription, "mouseexit", {})
|
||||||
return
|
|
||||||
|
|
||||||
if subscription.event_name == "mouseexit":
|
|
||||||
_execute_lua_callback(subscription)
|
|
||||||
|
|
||||||
func _execute_lua_callback(subscription: EventSubscription, args: Array = []) -> void:
|
func _execute_lua_callback(subscription: EventSubscription, args: Array = []) -> void:
|
||||||
subscription.vm.lua_rawgeti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref)
|
threaded_vm.execute_callback_async(subscription.callback_ref, args)
|
||||||
if subscription.vm.lua_isfunction(-1):
|
|
||||||
for arg in args:
|
|
||||||
subscription.vm.lua_pushvariant(arg)
|
|
||||||
|
|
||||||
if subscription.vm.lua_pcall(args.size(), 0, 0) != subscription.vm.LUA_OK:
|
|
||||||
print("GURT ERROR in callback: ", subscription.vm.lua_tostring(-1))
|
|
||||||
subscription.vm.lua_pop(1)
|
|
||||||
else:
|
|
||||||
subscription.vm.lua_pop(1)
|
|
||||||
|
|
||||||
func _execute_input_event_callback(subscription: EventSubscription, event_data: Dictionary) -> void:
|
func _execute_input_event_callback(subscription: EventSubscription, event_data: Dictionary) -> void:
|
||||||
if not event_subscriptions.has(subscription.id):
|
if not event_subscriptions.has(subscription.id):
|
||||||
@@ -814,13 +470,18 @@ func get_dom_node(node: Node, purpose: String = "general") -> Node:
|
|||||||
if not node:
|
if not node:
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if node is MarginContainer:
|
if node is MarginContainer and node.get_child_count() > 0:
|
||||||
node = node.get_child(0)
|
node = node.get_child(0)
|
||||||
|
|
||||||
|
if not node:
|
||||||
|
return null
|
||||||
|
|
||||||
match purpose:
|
match purpose:
|
||||||
"signal":
|
"signal":
|
||||||
if node is HTMLButton:
|
if node is HTMLButton:
|
||||||
return node.get_node_or_null("ButtonNode")
|
return node.get_node_or_null("ButtonNode")
|
||||||
|
elif node is HBoxContainer and node.get_node_or_null("ButtonNode"):
|
||||||
|
return node.get_node_or_null("ButtonNode")
|
||||||
elif node is RichTextLabel:
|
elif node is RichTextLabel:
|
||||||
return node
|
return node
|
||||||
elif node.has_method("get") and node.get("rich_text_label"):
|
elif node.has_method("get") and node.get("rich_text_label"):
|
||||||
@@ -860,12 +521,185 @@ func get_dom_node(node: Node, purpose: String = "general") -> Node:
|
|||||||
|
|
||||||
# Main execution function
|
# Main execution function
|
||||||
func execute_lua_script(code: String, vm: LuauVM):
|
func execute_lua_script(code: String, vm: LuauVM):
|
||||||
vm.open_libraries([vm.LUA_BASE_LIB, vm.LUA_BIT32_LIB,
|
if not threaded_vm.lua_thread or not threaded_vm.lua_thread.is_alive():
|
||||||
vm.LUA_COROUTINE_LIB, vm.LUA_MATH_LIB, vm.LUA_UTF8_LIB,
|
# Start the thread if it's not running
|
||||||
vm.LUA_TABLE_LIB, vm.LUA_STRING_LIB, vm.LUA_VECTOR_LIB])
|
threaded_vm.start_lua_thread(dom_parser, self)
|
||||||
|
|
||||||
LuaFunctionUtils.setup_gurt_api(vm, self, dom_parser)
|
script_start_time = Time.get_ticks_msec() / 1000.0
|
||||||
|
threaded_vm.execute_script_async(code)
|
||||||
|
|
||||||
if vm.lua_dostring(code) != vm.LUA_OK:
|
|
||||||
print("LUA ERROR: ", vm.lua_tostring(-1))
|
|
||||||
vm.lua_pop(1)
|
func _on_threaded_script_completed(result: Dictionary):
|
||||||
|
var execution_time = (Time.get_ticks_msec() / 1000.0) - script_start_time
|
||||||
|
|
||||||
|
func _on_print_output(message: String):
|
||||||
|
LuaPrintUtils.lua_print_direct(message)
|
||||||
|
|
||||||
|
func kill_script_execution():
|
||||||
|
threaded_vm.stop_lua_thread()
|
||||||
|
# Restart a fresh thread for future scripts
|
||||||
|
threaded_vm.start_lua_thread(dom_parser, self)
|
||||||
|
|
||||||
|
func is_script_hanging() -> bool:
|
||||||
|
return threaded_vm.lua_thread != null and threaded_vm.lua_thread.is_alive()
|
||||||
|
|
||||||
|
func get_script_runtime() -> float:
|
||||||
|
if script_start_time > 0 and is_script_hanging():
|
||||||
|
return (Time.get_ticks_msec() / 1000.0) - script_start_time
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
func _handle_dom_operation(operation: Dictionary):
|
||||||
|
match operation.type:
|
||||||
|
"register_event":
|
||||||
|
_handle_event_registration(operation)
|
||||||
|
"register_body_event":
|
||||||
|
_handle_body_event_registration(operation)
|
||||||
|
"set_text":
|
||||||
|
_handle_text_setting(operation)
|
||||||
|
"get_text":
|
||||||
|
_handle_text_getting(operation)
|
||||||
|
"append_element":
|
||||||
|
LuaDOMUtils.handle_element_append(operation, dom_parser, self)
|
||||||
|
"add_class":
|
||||||
|
LuaClassListUtils.handle_add_class(operation, dom_parser)
|
||||||
|
"remove_class":
|
||||||
|
LuaClassListUtils.handle_remove_class(operation, dom_parser)
|
||||||
|
"toggle_class":
|
||||||
|
LuaClassListUtils.handle_toggle_class(operation, dom_parser)
|
||||||
|
"remove_element":
|
||||||
|
LuaDOMUtils.handle_element_remove(operation, dom_parser)
|
||||||
|
"insert_before":
|
||||||
|
LuaDOMUtils.handle_insert_before(operation, dom_parser, self)
|
||||||
|
"insert_after":
|
||||||
|
LuaDOMUtils.handle_insert_after(operation, dom_parser, self)
|
||||||
|
"replace_child":
|
||||||
|
LuaDOMUtils.handle_replace_child(operation, dom_parser, self)
|
||||||
|
_:
|
||||||
|
pass # Unknown operation type, ignore
|
||||||
|
|
||||||
|
func _handle_event_registration(operation: Dictionary):
|
||||||
|
var selector: String = operation.selector
|
||||||
|
var event_name: String = operation.event_name
|
||||||
|
var callback_ref: int = operation.callback_ref
|
||||||
|
|
||||||
|
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
||||||
|
if not element:
|
||||||
|
return
|
||||||
|
|
||||||
|
var element_id = get_or_assign_element_id(element)
|
||||||
|
|
||||||
|
# Create subscription for threaded callback
|
||||||
|
var subscription = EventSubscription.new()
|
||||||
|
subscription.id = next_subscription_id
|
||||||
|
next_subscription_id += 1
|
||||||
|
subscription.element_id = element_id
|
||||||
|
subscription.event_name = event_name
|
||||||
|
subscription.callback_ref = callback_ref
|
||||||
|
subscription.vm = threaded_vm.lua_vm if threaded_vm else null
|
||||||
|
subscription.lua_api = self
|
||||||
|
|
||||||
|
event_subscriptions[subscription.id] = subscription
|
||||||
|
|
||||||
|
# Connect to DOM element
|
||||||
|
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||||
|
if dom_node:
|
||||||
|
var signal_node = get_dom_node(dom_node, "signal")
|
||||||
|
LuaEventUtils.connect_element_event(signal_node, event_name, subscription)
|
||||||
|
|
||||||
|
func _handle_text_setting(operation: Dictionary):
|
||||||
|
var selector: String = operation.selector
|
||||||
|
var text: String = operation.text
|
||||||
|
|
||||||
|
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
||||||
|
if element:
|
||||||
|
# Always update the HTML element's text content first
|
||||||
|
element.text_content = text
|
||||||
|
|
||||||
|
# If the element has a DOM node, update it too
|
||||||
|
var element_id = get_or_assign_element_id(element)
|
||||||
|
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||||
|
if dom_node:
|
||||||
|
var text_node = get_dom_node(dom_node, "text")
|
||||||
|
if text_node:
|
||||||
|
if text_node.has_method("set_text"):
|
||||||
|
text_node.set_text(text)
|
||||||
|
elif "text" in text_node:
|
||||||
|
text_node.text = text
|
||||||
|
|
||||||
|
func _handle_text_getting(operation: Dictionary):
|
||||||
|
var selector: String = operation.selector
|
||||||
|
|
||||||
|
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
||||||
|
if element:
|
||||||
|
# Return the element's cached text content from the HTML element
|
||||||
|
# This avoids the need for a callback system since we have the text cached
|
||||||
|
return element.text_content
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func _handle_body_event_registration(operation: Dictionary):
|
||||||
|
var event_name: String = operation.event_name
|
||||||
|
var callback_ref: int = operation.callback_ref
|
||||||
|
var subscription_id: int = operation.get("subscription_id", -1)
|
||||||
|
|
||||||
|
# Use provided subscription_id or generate a new one
|
||||||
|
if subscription_id == -1:
|
||||||
|
subscription_id = next_subscription_id
|
||||||
|
next_subscription_id += 1
|
||||||
|
|
||||||
|
# Create subscription for threaded callback
|
||||||
|
var subscription = EventSubscription.new()
|
||||||
|
subscription.id = subscription_id
|
||||||
|
subscription.element_id = "body"
|
||||||
|
subscription.event_name = event_name
|
||||||
|
subscription.callback_ref = callback_ref
|
||||||
|
subscription.vm = threaded_vm.lua_vm if threaded_vm else null
|
||||||
|
subscription.lua_api = self
|
||||||
|
|
||||||
|
event_subscriptions[subscription.id] = subscription
|
||||||
|
|
||||||
|
# Connect to body events
|
||||||
|
LuaEventUtils.connect_body_event(event_name, subscription, self)
|
||||||
|
|
||||||
|
func _register_event_on_main_thread(element_id: String, event_name: String, callback_ref: int, subscription_id: int = -1):
|
||||||
|
# This runs on the main thread - safe to access DOM nodes
|
||||||
|
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||||
|
if not dom_node:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use provided subscription_id or generate a new one
|
||||||
|
if subscription_id == -1:
|
||||||
|
subscription_id = next_subscription_id
|
||||||
|
next_subscription_id += 1
|
||||||
|
|
||||||
|
# Create subscription using the threaded VM's callback reference
|
||||||
|
var subscription = EventSubscription.new()
|
||||||
|
subscription.id = subscription_id
|
||||||
|
subscription.element_id = element_id
|
||||||
|
subscription.event_name = event_name
|
||||||
|
subscription.callback_ref = callback_ref
|
||||||
|
subscription.vm = threaded_vm.lua_vm if threaded_vm else null
|
||||||
|
subscription.lua_api = self
|
||||||
|
|
||||||
|
event_subscriptions[subscription.id] = subscription
|
||||||
|
|
||||||
|
var signal_node = get_dom_node(dom_node, "signal")
|
||||||
|
LuaEventUtils.connect_element_event(signal_node, event_name, subscription)
|
||||||
|
|
||||||
|
func _unsubscribe_event_on_main_thread(subscription_id: int):
|
||||||
|
# This runs on the main thread - safe to cleanup event subscriptions
|
||||||
|
var subscription = event_subscriptions.get(subscription_id, null)
|
||||||
|
if subscription:
|
||||||
|
LuaEventUtils.disconnect_subscription(subscription, self)
|
||||||
|
event_subscriptions.erase(subscription_id)
|
||||||
|
|
||||||
|
# Clean up Lua callback reference
|
||||||
|
if subscription.callback_ref and subscription.vm:
|
||||||
|
subscription.vm.lua_pushnil()
|
||||||
|
subscription.vm.lua_rawseti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref)
|
||||||
|
|
||||||
|
func _notification(what: int):
|
||||||
|
if what == NOTIFICATION_PREDELETE:
|
||||||
|
if timeout_manager:
|
||||||
|
timeout_manager.cleanup_all_timeouts()
|
||||||
|
threaded_vm.stop_lua_thread()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func init(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
|
|||||||
|
|
||||||
apply_button_styles(element, parser)
|
apply_button_styles(element, parser)
|
||||||
|
|
||||||
parser.register_dom_node(element, button_node)
|
parser.register_dom_node(element, self)
|
||||||
|
|
||||||
func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
|
func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
|
||||||
if not element or not parser:
|
if not element or not parser:
|
||||||
|
|||||||
@@ -16,17 +16,22 @@ static func element_classlist_add_handler(vm: LuauVM, dom_parser: HTMLParser) ->
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Get classes
|
# Get classes
|
||||||
|
var classes_to_add = [css_class]
|
||||||
|
|
||||||
var current_style = element.get_attribute("style", "")
|
var current_style = element.get_attribute("style", "")
|
||||||
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
||||||
|
|
||||||
# Add new css_class if not already present
|
# Add new css_class if not already present
|
||||||
if css_class not in style_classes:
|
var changed = false
|
||||||
style_classes.append(css_class)
|
for class_to_add in classes_to_add:
|
||||||
|
if class_to_add not in style_classes:
|
||||||
|
style_classes.append(class_to_add)
|
||||||
|
changed = true
|
||||||
|
|
||||||
|
if changed:
|
||||||
var new_style_attr = " ".join(style_classes)
|
var new_style_attr = " ".join(style_classes)
|
||||||
element.set_attribute("style", new_style_attr)
|
element.set_attribute("style", new_style_attr)
|
||||||
trigger_element_restyle(element, dom_parser)
|
trigger_element_restyle(element, dom_parser)
|
||||||
else:
|
|
||||||
print("DEBUG: classList.add - Class already exists")
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -43,25 +48,26 @@ static func element_classlist_remove_handler(vm: LuauVM, dom_parser: HTMLParser)
|
|||||||
if not element:
|
if not element:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Get style attribute
|
var classes_to_remove = [css_class]
|
||||||
|
|
||||||
var current_style = element.get_attribute("style", "")
|
var current_style = element.get_attribute("style", "")
|
||||||
if current_style.length() == 0:
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
||||||
return 0
|
|
||||||
|
|
||||||
var style_classes = CSSParser.smart_split_utility_classes(current_style)
|
# Remove classes if present
|
||||||
var clean_classes = []
|
var changed = false
|
||||||
for style_cls in style_classes:
|
for class_to_remove in classes_to_remove:
|
||||||
if style_cls != css_class:
|
if class_to_remove in style_classes:
|
||||||
clean_classes.append(style_cls)
|
style_classes.erase(class_to_remove)
|
||||||
|
changed = true
|
||||||
|
|
||||||
# Update style attribute
|
if changed:
|
||||||
if clean_classes.size() > 0:
|
var new_style_attr = " ".join(style_classes) if style_classes.size() > 0 else ""
|
||||||
var new_style_attr = " ".join(clean_classes)
|
if new_style_attr.length() > 0:
|
||||||
element.set_attribute("style", new_style_attr)
|
element.set_attribute("style", new_style_attr)
|
||||||
else:
|
else:
|
||||||
element.attributes.erase("style")
|
element.attributes.erase("style")
|
||||||
|
trigger_element_restyle(element, dom_parser)
|
||||||
|
|
||||||
trigger_element_restyle(element, dom_parser)
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
static func element_classlist_toggle_handler(vm: LuauVM, dom_parser: HTMLParser) -> int:
|
static func element_classlist_toggle_handler(vm: LuauVM, dom_parser: HTMLParser) -> int:
|
||||||
@@ -78,34 +84,111 @@ static func element_classlist_toggle_handler(vm: LuauVM, dom_parser: HTMLParser)
|
|||||||
vm.lua_pushboolean(false)
|
vm.lua_pushboolean(false)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Get style attribute
|
var utility_classes = [css_class]
|
||||||
|
|
||||||
var current_style = element.get_attribute("style", "")
|
var current_style = element.get_attribute("style", "")
|
||||||
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
||||||
|
|
||||||
var has_css_class = css_class in style_classes
|
var has_all_classes = true
|
||||||
|
for utility_class in utility_classes:
|
||||||
if has_css_class:
|
if utility_class not in style_classes:
|
||||||
# Remove css_class
|
has_all_classes = false
|
||||||
var new_classes = []
|
break
|
||||||
for style_cls in style_classes:
|
|
||||||
if style_cls != css_class:
|
|
||||||
new_classes.append(style_cls)
|
|
||||||
|
|
||||||
if new_classes.size() > 0:
|
|
||||||
element.set_attribute("style", " ".join(new_classes))
|
|
||||||
else:
|
|
||||||
element.attributes.erase("style")
|
|
||||||
|
|
||||||
|
if has_all_classes:
|
||||||
|
# Remove all utility classes
|
||||||
|
for utility_class in utility_classes:
|
||||||
|
style_classes.erase(utility_class)
|
||||||
vm.lua_pushboolean(false)
|
vm.lua_pushboolean(false)
|
||||||
else:
|
else:
|
||||||
# Add css_class
|
for utility_class in utility_classes:
|
||||||
style_classes.append(css_class)
|
if utility_class not in style_classes:
|
||||||
element.set_attribute("style", " ".join(style_classes))
|
style_classes.append(utility_class)
|
||||||
vm.lua_pushboolean(true)
|
vm.lua_pushboolean(true)
|
||||||
|
|
||||||
|
if style_classes.size() > 0:
|
||||||
|
var new_style_attr = " ".join(style_classes)
|
||||||
|
element.set_attribute("style", new_style_attr)
|
||||||
|
else:
|
||||||
|
element.attributes.erase("style")
|
||||||
|
|
||||||
trigger_element_restyle(element, dom_parser)
|
trigger_element_restyle(element, dom_parser)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
# DOM operation handlers for class management
|
||||||
|
static func handle_add_class(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
||||||
|
var element_id: String = operation.element_id
|
||||||
|
var cls: String = operation.class_name
|
||||||
|
|
||||||
|
var element = dom_parser.find_by_id(element_id) if element_id != "body" else dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
# Use the original working logic from LuaClassListUtils
|
||||||
|
var current_style = element.get_attribute("style", "")
|
||||||
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
||||||
|
|
||||||
|
# Add new class if not already present
|
||||||
|
if cls not in style_classes:
|
||||||
|
style_classes.append(cls)
|
||||||
|
var new_style_attr = " ".join(style_classes)
|
||||||
|
element.set_attribute("style", new_style_attr)
|
||||||
|
trigger_element_restyle(element, dom_parser)
|
||||||
|
|
||||||
|
static func handle_remove_class(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
||||||
|
var element_id: String = operation.element_id
|
||||||
|
var cls: String = operation.class_name
|
||||||
|
|
||||||
|
var element = dom_parser.find_by_id(element_id) if element_id != "body" else dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
# Use the original working logic from LuaClassListUtils
|
||||||
|
var current_style = element.get_attribute("style", "")
|
||||||
|
if current_style.length() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var style_classes = CSSParser.smart_split_utility_classes(current_style)
|
||||||
|
var clean_classes = []
|
||||||
|
for style_cls in style_classes:
|
||||||
|
if style_cls != cls:
|
||||||
|
clean_classes.append(style_cls)
|
||||||
|
|
||||||
|
# Update style attribute
|
||||||
|
if clean_classes.size() > 0:
|
||||||
|
var new_style_attr = " ".join(clean_classes)
|
||||||
|
element.set_attribute("style", new_style_attr)
|
||||||
|
else:
|
||||||
|
element.attributes.erase("style")
|
||||||
|
|
||||||
|
trigger_element_restyle(element, dom_parser)
|
||||||
|
|
||||||
|
static func handle_toggle_class(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
||||||
|
var element_id: String = operation.element_id
|
||||||
|
var cls: String = operation.class_name
|
||||||
|
|
||||||
|
var element = dom_parser.find_by_id(element_id) if element_id != "body" else dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
# Use the original working logic from LuaClassListUtils
|
||||||
|
var current_style = element.get_attribute("style", "")
|
||||||
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
||||||
|
|
||||||
|
var has_class = cls in style_classes
|
||||||
|
|
||||||
|
if has_class:
|
||||||
|
# Remove class
|
||||||
|
var new_classes = []
|
||||||
|
for style_cls in style_classes:
|
||||||
|
if style_cls != cls:
|
||||||
|
new_classes.append(style_cls)
|
||||||
|
|
||||||
|
if new_classes.size() > 0:
|
||||||
|
element.set_attribute("style", " ".join(new_classes))
|
||||||
|
else:
|
||||||
|
element.attributes.erase("style")
|
||||||
|
else:
|
||||||
|
# Add class
|
||||||
|
style_classes.append(cls)
|
||||||
|
element.set_attribute("style", " ".join(style_classes))
|
||||||
|
|
||||||
|
trigger_element_restyle(element, dom_parser)
|
||||||
|
|
||||||
static func trigger_element_restyle(element: HTMLParser.HTMLElement, dom_parser: HTMLParser) -> void:
|
static func trigger_element_restyle(element: HTMLParser.HTMLElement, dom_parser: HTMLParser) -> void:
|
||||||
# Find DOM node for element
|
# Find DOM node for element
|
||||||
var element_id = element.get_attribute("id")
|
var element_id = element.get_attribute("id")
|
||||||
|
|||||||
@@ -94,40 +94,78 @@ static func get_element_last_child_handler(vm: LuauVM, dom_parser: HTMLParser, l
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
# DOM Manipulation Methods
|
# DOM Manipulation Methods
|
||||||
static func insert_before_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) -> int:
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
|
||||||
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
|
||||||
vm.luaL_checktype(3, vm.LUA_TTABLE) # reference_child
|
|
||||||
|
|
||||||
# Get parent element info
|
static func handle_element_append(operation: Dictionary, dom_parser: HTMLParser, lua_api) -> void:
|
||||||
vm.lua_getfield(1, "_element_id")
|
var parent_id: String = operation.parent_id
|
||||||
var parent_element_id: String = vm.lua_tostring(-1)
|
var child_id: String = operation.child_id
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Get new child element info
|
# Find the parent and child elements
|
||||||
vm.lua_getfield(2, "_element_id")
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
||||||
var new_child_element_id: String = vm.lua_tostring(-1)
|
var child_element = dom_parser.find_by_id(child_id) if child_id != "body" else dom_parser.find_first("body")
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Get reference child element info
|
if not parent_element or not child_element:
|
||||||
vm.lua_getfield(3, "_element_id")
|
return
|
||||||
var reference_child_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find elements
|
# Remove child from its current parent if it has one
|
||||||
var parent_element = find_element_by_id(parent_element_id, dom_parser)
|
if child_element.parent:
|
||||||
var new_child_element = find_element_by_id(new_child_element_id, dom_parser)
|
var current_parent = child_element.parent
|
||||||
var reference_child_element = find_element_by_id(reference_child_element_id, dom_parser)
|
var current_index = current_parent.children.find(child_element)
|
||||||
|
if current_index >= 0:
|
||||||
|
current_parent.children.remove_at(current_index)
|
||||||
|
|
||||||
|
# Append child to new parent
|
||||||
|
child_element.parent = parent_element
|
||||||
|
parent_element.children.append(child_element)
|
||||||
|
|
||||||
|
# Handle visual rendering if parent is already rendered
|
||||||
|
var parent_dom_node: Node = null
|
||||||
|
if parent_id == "body":
|
||||||
|
var main_scene = lua_api.get_node("/root/Main")
|
||||||
|
if main_scene:
|
||||||
|
parent_dom_node = main_scene.website_container
|
||||||
|
else:
|
||||||
|
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_id, null)
|
||||||
|
|
||||||
|
if parent_dom_node:
|
||||||
|
# Render the appended element
|
||||||
|
render_new_element.call_deferred(child_element, parent_dom_node, dom_parser, lua_api)
|
||||||
|
|
||||||
|
static func handle_element_remove(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
||||||
|
var element_id: String = operation.element_id
|
||||||
|
|
||||||
|
var element = dom_parser.find_by_id(element_id) if element_id != "body" else dom_parser.find_first("body")
|
||||||
|
if element and element.parent:
|
||||||
|
# Remove element from HTML parser tree
|
||||||
|
var parent_element = element.parent
|
||||||
|
var element_index = parent_element.children.find(element)
|
||||||
|
if element_index >= 0:
|
||||||
|
parent_element.children.remove_at(element_index)
|
||||||
|
element.parent = null
|
||||||
|
|
||||||
|
# Remove element from DOM tree
|
||||||
|
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||||
|
if dom_node and dom_node.get_parent():
|
||||||
|
dom_node.get_parent().remove_child(dom_node)
|
||||||
|
dom_node.queue_free()
|
||||||
|
dom_parser.parse_result.dom_nodes.erase(element_id)
|
||||||
|
|
||||||
|
# Remove from parser's all_elements list
|
||||||
|
var all_elements_index = dom_parser.parse_result.all_elements.find(element)
|
||||||
|
if all_elements_index >= 0:
|
||||||
|
dom_parser.parse_result.all_elements.remove_at(all_elements_index)
|
||||||
|
|
||||||
|
static func handle_insert_before(operation: Dictionary, dom_parser: HTMLParser, lua_api) -> void:
|
||||||
|
var parent_id: String = operation.parent_id
|
||||||
|
var new_child_id: String = operation.new_child_id
|
||||||
|
var reference_child_id: String = operation.reference_child_id
|
||||||
|
|
||||||
|
# Find the elements
|
||||||
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
||||||
|
var new_child_element = dom_parser.find_by_id(new_child_id) if new_child_id != "body" else dom_parser.find_first("body")
|
||||||
|
var reference_child_element = dom_parser.find_by_id(reference_child_id) if reference_child_id != "body" else dom_parser.find_first("body")
|
||||||
|
|
||||||
if not parent_element or not new_child_element or not reference_child_element:
|
if not parent_element or not new_child_element or not reference_child_element:
|
||||||
vm.lua_pushnil()
|
return
|
||||||
return 1
|
|
||||||
|
|
||||||
# Find reference child index in parent's children
|
|
||||||
var reference_index = parent_element.children.find(reference_child_element)
|
|
||||||
if reference_index < 0:
|
|
||||||
vm.lua_pushnil()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Remove new child from its current parent if it has one
|
# Remove new child from its current parent if it has one
|
||||||
if new_child_element.parent:
|
if new_child_element.parent:
|
||||||
@@ -136,51 +174,37 @@ static func insert_before_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) -
|
|||||||
if current_index >= 0:
|
if current_index >= 0:
|
||||||
current_parent.children.remove_at(current_index)
|
current_parent.children.remove_at(current_index)
|
||||||
|
|
||||||
# Insert new child before reference child
|
# Find reference child position in parent
|
||||||
new_child_element.parent = parent_element
|
var reference_index = parent_element.children.find(reference_child_element)
|
||||||
parent_element.children.insert(reference_index, new_child_element)
|
if reference_index >= 0:
|
||||||
|
# Insert new child before reference child
|
||||||
|
new_child_element.parent = parent_element
|
||||||
|
parent_element.children.insert(reference_index, new_child_element)
|
||||||
|
|
||||||
# Handle visual rendering if parent is already rendered
|
# Handle visual rendering
|
||||||
handle_visual_insertion_by_reference(parent_element_id, new_child_element, reference_child_element_id, true, dom_parser, lua_api)
|
var parent_dom_node: Node = null
|
||||||
|
if parent_id == "body":
|
||||||
|
var main_scene = lua_api.get_node("/root/Main")
|
||||||
|
if main_scene:
|
||||||
|
parent_dom_node = main_scene.website_container
|
||||||
|
else:
|
||||||
|
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_id, null)
|
||||||
|
|
||||||
# Return the new child
|
if parent_dom_node:
|
||||||
vm.lua_pushvalue(2)
|
handle_visual_insertion_by_reference(parent_id, new_child_element, reference_child_id, true, dom_parser, lua_api)
|
||||||
return 1
|
|
||||||
|
|
||||||
static func insert_after_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) -> int:
|
static func handle_insert_after(operation: Dictionary, dom_parser: HTMLParser, lua_api) -> void:
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
var parent_id: String = operation.parent_id
|
||||||
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
var new_child_id: String = operation.new_child_id
|
||||||
vm.luaL_checktype(3, vm.LUA_TTABLE) # reference_child
|
var reference_child_id: String = operation.reference_child_id
|
||||||
|
|
||||||
# Get parent element info
|
# Find the elements
|
||||||
vm.lua_getfield(1, "_element_id")
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
||||||
var parent_element_id: String = vm.lua_tostring(-1)
|
var new_child_element = dom_parser.find_by_id(new_child_id) if new_child_id != "body" else dom_parser.find_first("body")
|
||||||
vm.lua_pop(1)
|
var reference_child_element = dom_parser.find_by_id(reference_child_id) if reference_child_id != "body" else dom_parser.find_first("body")
|
||||||
|
|
||||||
# Get new child element info
|
|
||||||
vm.lua_getfield(2, "_element_id")
|
|
||||||
var new_child_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Get reference child element info
|
|
||||||
vm.lua_getfield(3, "_element_id")
|
|
||||||
var reference_child_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find elements
|
|
||||||
var parent_element = find_element_by_id(parent_element_id, dom_parser)
|
|
||||||
var new_child_element = find_element_by_id(new_child_element_id, dom_parser)
|
|
||||||
var reference_child_element = find_element_by_id(reference_child_element_id, dom_parser)
|
|
||||||
|
|
||||||
if not parent_element or not new_child_element or not reference_child_element:
|
if not parent_element or not new_child_element or not reference_child_element:
|
||||||
vm.lua_pushnil()
|
return
|
||||||
return 1
|
|
||||||
|
|
||||||
# Find reference child index in parent's children
|
|
||||||
var reference_index = parent_element.children.find(reference_child_element)
|
|
||||||
if reference_index < 0:
|
|
||||||
vm.lua_pushnil()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Remove new child from its current parent if it has one
|
# Remove new child from its current parent if it has one
|
||||||
if new_child_element.parent:
|
if new_child_element.parent:
|
||||||
@@ -189,51 +213,37 @@ static func insert_after_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) ->
|
|||||||
if current_index >= 0:
|
if current_index >= 0:
|
||||||
current_parent.children.remove_at(current_index)
|
current_parent.children.remove_at(current_index)
|
||||||
|
|
||||||
# Insert new child after reference child
|
# Find reference child position in parent
|
||||||
new_child_element.parent = parent_element
|
var reference_index = parent_element.children.find(reference_child_element)
|
||||||
parent_element.children.insert(reference_index + 1, new_child_element)
|
if reference_index >= 0:
|
||||||
|
# Insert new child after reference child
|
||||||
|
new_child_element.parent = parent_element
|
||||||
|
parent_element.children.insert(reference_index + 1, new_child_element)
|
||||||
|
|
||||||
# Handle visual rendering if parent is already rendered
|
# Handle visual rendering
|
||||||
handle_visual_insertion_by_reference(parent_element_id, new_child_element, reference_child_element_id, false, dom_parser, lua_api)
|
var parent_dom_node: Node = null
|
||||||
|
if parent_id == "body":
|
||||||
|
var main_scene = lua_api.get_node("/root/Main")
|
||||||
|
if main_scene:
|
||||||
|
parent_dom_node = main_scene.website_container
|
||||||
|
else:
|
||||||
|
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_id, null)
|
||||||
|
|
||||||
# Return the new child
|
if parent_dom_node:
|
||||||
vm.lua_pushvalue(2)
|
handle_visual_insertion_by_reference(parent_id, new_child_element, reference_child_id, false, dom_parser, lua_api)
|
||||||
return 1
|
|
||||||
|
|
||||||
static func replace_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) -> int:
|
static func handle_replace_child(operation: Dictionary, dom_parser: HTMLParser, lua_api) -> void:
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
var parent_id: String = operation.parent_id
|
||||||
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
var new_child_id: String = operation.new_child_id
|
||||||
vm.luaL_checktype(3, vm.LUA_TTABLE) # old_child
|
var old_child_id: String = operation.old_child_id
|
||||||
|
|
||||||
# Get parent element info
|
# Find the elements
|
||||||
vm.lua_getfield(1, "_element_id")
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
||||||
var parent_element_id: String = vm.lua_tostring(-1)
|
var new_child_element = dom_parser.find_by_id(new_child_id) if new_child_id != "body" else dom_parser.find_first("body")
|
||||||
vm.lua_pop(1)
|
var old_child_element = dom_parser.find_by_id(old_child_id) if old_child_id != "body" else dom_parser.find_first("body")
|
||||||
|
|
||||||
# Get new child element info
|
|
||||||
vm.lua_getfield(2, "_element_id")
|
|
||||||
var new_child_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Get old child element info
|
|
||||||
vm.lua_getfield(3, "_element_id")
|
|
||||||
var old_child_element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find elements
|
|
||||||
var parent_element = find_element_by_id(parent_element_id, dom_parser)
|
|
||||||
var new_child_element = find_element_by_id(new_child_element_id, dom_parser)
|
|
||||||
var old_child_element = find_element_by_id(old_child_element_id, dom_parser)
|
|
||||||
|
|
||||||
if not parent_element or not new_child_element or not old_child_element:
|
if not parent_element or not new_child_element or not old_child_element:
|
||||||
vm.lua_pushnil()
|
return
|
||||||
return 1
|
|
||||||
|
|
||||||
# Find old child index in parent's children
|
|
||||||
var old_index = parent_element.children.find(old_child_element)
|
|
||||||
if old_index < 0:
|
|
||||||
vm.lua_pushnil()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Remove new child from its current parent if it has one
|
# Remove new child from its current parent if it has one
|
||||||
if new_child_element.parent:
|
if new_child_element.parent:
|
||||||
@@ -242,46 +252,43 @@ static func replace_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) -> int:
|
|||||||
if current_index >= 0:
|
if current_index >= 0:
|
||||||
current_parent.children.remove_at(current_index)
|
current_parent.children.remove_at(current_index)
|
||||||
|
|
||||||
# Replace old child with new child
|
# Find old child position in parent
|
||||||
old_child_element.parent = null
|
var old_index = parent_element.children.find(old_child_element)
|
||||||
new_child_element.parent = parent_element
|
if old_index >= 0:
|
||||||
parent_element.children[old_index] = new_child_element
|
# Replace old child with new child
|
||||||
|
new_child_element.parent = parent_element
|
||||||
|
parent_element.children[old_index] = new_child_element
|
||||||
|
old_child_element.parent = null
|
||||||
|
|
||||||
# Handle visual updates
|
# Handle visual rendering
|
||||||
handle_visual_replacement(old_child_element_id, new_child_element, parent_element_id, dom_parser, lua_api)
|
handle_visual_replacement(old_child_id, new_child_element, parent_id, dom_parser, lua_api)
|
||||||
|
|
||||||
# Return the old child
|
static func render_new_element(element: HTMLParser.HTMLElement, parent_node: Node, dom_parser: HTMLParser, lua_api) -> void:
|
||||||
vm.lua_pushvalue(3)
|
# Get reference to main scene for rendering
|
||||||
return 1
|
var main_scene = lua_api.get_node("/root/Main")
|
||||||
|
if not main_scene:
|
||||||
|
return
|
||||||
|
|
||||||
static func clone_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api) -> int:
|
# Create the visual node for the element
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE) # element to clone
|
var element_node = await main_scene.create_element_node(element, dom_parser)
|
||||||
var deep: bool = false
|
if not element_node:
|
||||||
|
LuaPrintUtils.lua_print_direct("Failed to create visual node for element: " + str(element))
|
||||||
|
return
|
||||||
|
|
||||||
if vm.lua_gettop() >= 2:
|
# Set metadata so ul/ol can detect dynamically added li elements
|
||||||
deep = vm.lua_toboolean(2)
|
element_node.set_meta("html_element", element)
|
||||||
|
|
||||||
# Get element info
|
# Register the DOM node
|
||||||
vm.lua_getfield(1, "_element_id")
|
dom_parser.register_dom_node(element, element_node)
|
||||||
var element_id: String = vm.lua_tostring(-1)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Find the element
|
# Add to parent - handle body special case
|
||||||
var element = find_element_by_id(element_id, dom_parser)
|
var container_node = parent_node
|
||||||
if not element:
|
if parent_node is MarginContainer and parent_node.get_child_count() > 0:
|
||||||
vm.lua_pushnil()
|
container_node = parent_node.get_child(0)
|
||||||
return 1
|
elif parent_node == main_scene.website_container:
|
||||||
|
container_node = parent_node
|
||||||
|
|
||||||
# Clone the element
|
main_scene.safe_add_child(container_node, element_node)
|
||||||
var cloned_element = clone_element(element, deep)
|
|
||||||
|
|
||||||
# Add cloned element to parser's element collection
|
|
||||||
dom_parser.parse_result.all_elements.append(cloned_element)
|
|
||||||
|
|
||||||
# Create Lua element wrapper with full functionality
|
|
||||||
create_element_wrapper(vm, cloned_element, lua_api)
|
|
||||||
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Helper functions
|
# Helper functions
|
||||||
static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HTMLParser.HTMLElement:
|
static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HTMLParser.HTMLElement:
|
||||||
@@ -290,26 +297,6 @@ static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HT
|
|||||||
else:
|
else:
|
||||||
return dom_parser.find_by_id(element_id)
|
return dom_parser.find_by_id(element_id)
|
||||||
|
|
||||||
static func create_element_wrapper(vm: LuauVM, element: HTMLParser.HTMLElement, lua_api: LuaAPI) -> void:
|
|
||||||
vm.lua_newtable()
|
|
||||||
|
|
||||||
var element_id: String
|
|
||||||
if element.tag_name == "body":
|
|
||||||
element_id = "body"
|
|
||||||
else:
|
|
||||||
element_id = element.get_attribute("id")
|
|
||||||
if element_id.is_empty():
|
|
||||||
element_id = lua_api.get_or_assign_element_id(element)
|
|
||||||
element.set_attribute("id", element_id)
|
|
||||||
|
|
||||||
vm.lua_pushstring(element_id)
|
|
||||||
vm.lua_setfield(-2, "_element_id")
|
|
||||||
vm.lua_pushstring(element.tag_name)
|
|
||||||
vm.lua_setfield(-2, "_tag_name")
|
|
||||||
|
|
||||||
if lua_api:
|
|
||||||
lua_api.add_element_methods(vm)
|
|
||||||
|
|
||||||
static func clone_element(element: HTMLParser.HTMLElement, deep: bool) -> HTMLParser.HTMLElement:
|
static func clone_element(element: HTMLParser.HTMLElement, deep: bool) -> HTMLParser.HTMLElement:
|
||||||
var cloned = HTMLParser.HTMLElement.new(element.tag_name)
|
var cloned = HTMLParser.HTMLElement.new(element.tag_name)
|
||||||
|
|
||||||
@@ -370,18 +357,7 @@ static func handle_visual_replacement(old_child_element_id: String, new_child_el
|
|||||||
if parent_dom_node:
|
if parent_dom_node:
|
||||||
render_new_element_at_position.call_deferred(new_child_element, parent_dom_node, old_position, dom_parser)
|
render_new_element_at_position.call_deferred(new_child_element, parent_dom_node, old_position, dom_parser)
|
||||||
|
|
||||||
static func add_enhanced_element_methods(vm: LuauVM, lua_api, index: String = "element") -> void:
|
|
||||||
vm.lua_pushcallable(lua_api._element_insert_before_wrapper, index + ".insertBefore")
|
|
||||||
vm.lua_setfield(-2, "insertBefore")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._element_insert_after_wrapper, index + ".insertAfter")
|
|
||||||
vm.lua_setfield(-2, "insertAfter")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._element_replace_wrapper, index + ".replace")
|
|
||||||
vm.lua_setfield(-2, "replace")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._element_clone_wrapper, index + ".clone")
|
|
||||||
vm.lua_setfield(-2, "clone")
|
|
||||||
|
|
||||||
static func is_same_element_visual_node(node1: Node, node2: Node) -> bool:
|
static func is_same_element_visual_node(node1: Node, node2: Node) -> bool:
|
||||||
if node1 == node2:
|
if node1 == node2:
|
||||||
@@ -475,20 +451,531 @@ static func render_new_element_by_reference(element: HTMLParser.HTMLElement, par
|
|||||||
if insert_position >= 0 and insert_position < container_node.get_child_count() - 1:
|
if insert_position >= 0 and insert_position < container_node.get_child_count() - 1:
|
||||||
container_node.move_child(element_node, insert_position)
|
container_node.move_child(element_node, insert_position)
|
||||||
|
|
||||||
|
# Threaded-safe wrapper functions
|
||||||
|
static func emit_dom_operation(lua_api: LuaAPI, operation: Dictionary) -> void:
|
||||||
|
lua_api.threaded_vm.call_deferred("_emit_dom_operation_request", operation)
|
||||||
|
|
||||||
|
static func create_element_wrapper(vm: LuauVM, element: HTMLParser.HTMLElement, lua_api: LuaAPI) -> void:
|
||||||
|
vm.lua_newtable()
|
||||||
|
|
||||||
|
var element_id: String
|
||||||
|
if element.tag_name == "body":
|
||||||
|
element_id = "body"
|
||||||
|
else:
|
||||||
|
element_id = element.get_attribute("id")
|
||||||
|
if element_id.is_empty():
|
||||||
|
element_id = lua_api.get_or_assign_element_id(element)
|
||||||
|
element.set_attribute("id", element_id)
|
||||||
|
|
||||||
|
vm.lua_pushstring(element_id)
|
||||||
|
vm.lua_setfield(-2, "_element_id")
|
||||||
|
vm.lua_pushstring(element.tag_name)
|
||||||
|
vm.lua_setfield(-2, "_tag_name")
|
||||||
|
|
||||||
|
add_element_methods(vm, lua_api)
|
||||||
|
|
||||||
|
static func add_element_methods(vm: LuauVM, lua_api: LuaAPI) -> void:
|
||||||
|
vm.set_meta("lua_api", lua_api)
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_on_wrapper"), "element.on")
|
||||||
|
vm.lua_setfield(-2, "on")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_append_wrapper"), "element.append")
|
||||||
|
vm.lua_setfield(-2, "append")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_set_text_wrapper"), "element.setText")
|
||||||
|
vm.lua_setfield(-2, "setText")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_remove_wrapper"), "element.remove")
|
||||||
|
vm.lua_setfield(-2, "remove")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_insert_before_wrapper"), "element.insertBefore")
|
||||||
|
vm.lua_setfield(-2, "insertBefore")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_insert_after_wrapper"), "element.insertAfter")
|
||||||
|
vm.lua_setfield(-2, "insertAfter")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_replace_wrapper"), "element.replace")
|
||||||
|
vm.lua_setfield(-2, "replace")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_clone_wrapper"), "element.clone")
|
||||||
|
vm.lua_setfield(-2, "clone")
|
||||||
|
|
||||||
|
_add_classlist_support(vm, lua_api)
|
||||||
|
|
||||||
|
vm.lua_newtable()
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_index_wrapper"), "element.__index")
|
||||||
|
vm.lua_setfield(-2, "__index")
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_element_newindex_wrapper"), "element.__newindex")
|
||||||
|
vm.lua_setfield(-2, "__newindex")
|
||||||
|
vm.lua_setmetatable(-2)
|
||||||
|
|
||||||
|
static func _element_on_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
var event_name: String = vm.luaL_checkstring(2)
|
||||||
|
vm.luaL_checktype(3, vm.LUA_TFUNCTION)
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
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_pushvalue(3)
|
||||||
|
vm.lua_rawseti(vm.LUA_REGISTRYINDEX, callback_ref)
|
||||||
|
|
||||||
|
lua_api.call_deferred("_register_event_on_main_thread", element_id, event_name, callback_ref, subscription_id)
|
||||||
|
|
||||||
|
vm.lua_newtable()
|
||||||
|
vm.lua_pushinteger(subscription_id)
|
||||||
|
vm.lua_setfield(-2, "_subscription_id")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_unsubscribe_wrapper"), "subscription.unsubscribe")
|
||||||
|
vm.lua_setfield(-2, "unsubscribe")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
static func _element_append_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Queue append operation for main thread
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
||||||
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # child
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var parent_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(2, "_element_id")
|
||||||
|
var child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "append_element",
|
||||||
|
"parent_id": parent_id,
|
||||||
|
"child_id": child_id
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_set_text_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
var text: String = vm.luaL_checkstring(2)
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
element.text_content = text
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "set_text",
|
||||||
|
"selector": "#" + element_id,
|
||||||
|
"text": text
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_remove_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Get element ID from self table
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "remove_element",
|
||||||
|
"element_id": element_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Queue operation for main thread
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_insert_before_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Get parent element ID from self table
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
||||||
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
||||||
|
vm.luaL_checktype(3, vm.LUA_TTABLE) # reference_child
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var parent_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(2, "_element_id")
|
||||||
|
var new_child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(3, "_element_id")
|
||||||
|
var reference_child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "insert_before",
|
||||||
|
"parent_id": parent_id,
|
||||||
|
"new_child_id": new_child_id,
|
||||||
|
"reference_child_id": reference_child_id
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_insert_after_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Get parent element ID from self table
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
||||||
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
||||||
|
vm.luaL_checktype(3, vm.LUA_TTABLE) # reference_child
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var parent_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(2, "_element_id")
|
||||||
|
var new_child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(3, "_element_id")
|
||||||
|
var reference_child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "insert_after",
|
||||||
|
"parent_id": parent_id,
|
||||||
|
"new_child_id": new_child_id,
|
||||||
|
"reference_child_id": reference_child_id
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_replace_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Get parent element ID from self table
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
||||||
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
||||||
|
vm.luaL_checktype(3, vm.LUA_TTABLE) # old_child
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var parent_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(2, "_element_id")
|
||||||
|
var new_child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
vm.lua_getfield(3, "_element_id")
|
||||||
|
var old_child_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "replace_child",
|
||||||
|
"parent_id": parent_id,
|
||||||
|
"new_child_id": new_child_id,
|
||||||
|
"old_child_id": old_child_id
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_clone_wrapper(vm: LuauVM) -> int:
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Get element ID from self table
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # element
|
||||||
|
var deep: bool = true # Default to deep clone
|
||||||
|
|
||||||
|
if vm.lua_gettop() >= 2:
|
||||||
|
deep = vm.lua_toboolean(2)
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
# Find the element to clone
|
||||||
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
var cloned_element = clone_element(element, deep)
|
||||||
|
|
||||||
|
# Assign new ID to cloned element
|
||||||
|
var new_id = lua_api.get_or_assign_element_id(cloned_element)
|
||||||
|
|
||||||
|
# Add to parser's element collection
|
||||||
|
lua_api.dom_parser.parse_result.all_elements.append(cloned_element)
|
||||||
|
|
||||||
|
# Create element wrapper for the cloned element
|
||||||
|
create_element_wrapper(vm, cloned_element, lua_api)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
vm.lua_pushnil()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
static func _element_index_wrapper(vm: LuauVM) -> int:
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
var key: String = vm.luaL_checkstring(2)
|
||||||
|
|
||||||
|
match key:
|
||||||
|
"text":
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if lua_api:
|
||||||
|
# Get element ID and find the element
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
vm.lua_pushstring(element.text_content)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Fallback to empty string
|
||||||
|
vm.lua_pushstring("")
|
||||||
|
return 1
|
||||||
|
"children":
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if lua_api:
|
||||||
|
# Get element ID and find the element
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
# Create array of child elements
|
||||||
|
vm.lua_newtable()
|
||||||
|
var index = 1
|
||||||
|
for child in element.children:
|
||||||
|
create_element_wrapper(vm, child, lua_api)
|
||||||
|
vm.lua_rawseti(-2, index)
|
||||||
|
index += 1
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Fallback to empty array
|
||||||
|
vm.lua_newtable()
|
||||||
|
return 1
|
||||||
|
_:
|
||||||
|
# Check for DOM traversal properties first
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if lua_api:
|
||||||
|
match key:
|
||||||
|
"parent":
|
||||||
|
return get_element_parent_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
|
"nextSibling":
|
||||||
|
return get_element_next_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
|
"previousSibling":
|
||||||
|
return get_element_previous_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
|
"firstChild":
|
||||||
|
return get_element_first_child_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
|
"lastChild":
|
||||||
|
return get_element_last_child_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
|
|
||||||
|
# Check if it's a method in the original table
|
||||||
|
vm.lua_pushvalue(1)
|
||||||
|
vm.lua_pushstring(key)
|
||||||
|
vm.lua_rawget(-2)
|
||||||
|
vm.lua_remove(-2)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
static func _add_classlist_support(vm: LuauVM, lua_api: LuaAPI) -> void:
|
||||||
|
# Create classList table with threaded methods
|
||||||
|
vm.lua_newtable()
|
||||||
|
|
||||||
|
# Store the element_id in the classList table
|
||||||
|
vm.lua_getfield(-2, "_element_id") # Get element_id from parent element
|
||||||
|
vm.lua_setfield(-2, "_element_id") # Store it in classList table
|
||||||
|
|
||||||
|
# Add classList methods
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_classlist_add_wrapper"), "classList.add")
|
||||||
|
vm.lua_setfield(-2, "add")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_classlist_remove_wrapper"), "classList.remove")
|
||||||
|
vm.lua_setfield(-2, "remove")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(LuaDOMUtils, "_classlist_toggle_wrapper"), "classList.toggle")
|
||||||
|
vm.lua_setfield(-2, "toggle")
|
||||||
|
|
||||||
|
# Set classList on the element
|
||||||
|
vm.lua_setfield(-2, "classList")
|
||||||
|
|
||||||
|
static func _classlist_add_wrapper(vm: LuauVM) -> int:
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # classList table
|
||||||
|
var cls: String = vm.luaL_checkstring(2)
|
||||||
|
|
||||||
|
# Get element_id from classList table
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "add_class",
|
||||||
|
"element_id": element_id,
|
||||||
|
"class_name": cls
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _classlist_remove_wrapper(vm: LuauVM) -> int:
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # classList table
|
||||||
|
var cls: String = vm.luaL_checkstring(2)
|
||||||
|
|
||||||
|
# Get element_id from classList table
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "remove_class",
|
||||||
|
"element_id": element_id,
|
||||||
|
"class_name": cls
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _classlist_toggle_wrapper(vm: LuauVM) -> int:
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # classList table
|
||||||
|
var cls: String = vm.luaL_checkstring(2)
|
||||||
|
|
||||||
|
# Get element_id from classList table
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var operation = {
|
||||||
|
"type": "toggle_class",
|
||||||
|
"element_id": element_id,
|
||||||
|
"class_name": cls
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _element_newindex_wrapper(vm: LuauVM) -> int:
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
var key: String = vm.luaL_checkstring(2)
|
||||||
|
var value = vm.lua_tovariant(3)
|
||||||
|
|
||||||
|
match key:
|
||||||
|
"text":
|
||||||
|
var text: String = str(value) # Convert value to string
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_element_id")
|
||||||
|
var element_id: String = vm.lua_tostring(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
||||||
|
if element:
|
||||||
|
element.text_content = text
|
||||||
|
|
||||||
|
# Also queue the DOM operation for visual updates if the element is already rendered
|
||||||
|
var operation = {
|
||||||
|
"type": "set_text",
|
||||||
|
"selector": "#" + element_id,
|
||||||
|
"text": text
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_dom_operation(lua_api, operation)
|
||||||
|
return 0
|
||||||
|
_:
|
||||||
|
# Store in table normally
|
||||||
|
vm.lua_pushvalue(2)
|
||||||
|
vm.lua_pushvalue(3)
|
||||||
|
vm.lua_rawset(1)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
static func _unsubscribe_wrapper(vm: LuauVM) -> int:
|
||||||
|
# Get subscription ID from the subscription table
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
|
||||||
|
vm.lua_getfield(1, "_subscription_id")
|
||||||
|
var subscription_id: int = vm.lua_tointeger(-1)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
# Get lua_api from VM metadata
|
||||||
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||||
|
if not lua_api:
|
||||||
|
vm.lua_pushboolean(false)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Handle unsubscribe on main thread
|
||||||
|
if subscription_id > 0:
|
||||||
|
lua_api.call_deferred("_unsubscribe_event_on_main_thread", subscription_id)
|
||||||
|
vm.lua_pushboolean(true)
|
||||||
|
else:
|
||||||
|
vm.lua_pushboolean(false)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
static func _index_handler(vm: LuauVM, lua_api: LuaAPI) -> int:
|
static func _index_handler(vm: LuauVM, lua_api: LuaAPI) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
var key: String = vm.luaL_checkstring(2)
|
var key: String = vm.luaL_checkstring(2)
|
||||||
|
|
||||||
match key:
|
match key:
|
||||||
"parent":
|
"parent":
|
||||||
return lua_api._get_element_parent_wrapper(vm, lua_api)
|
return get_element_parent_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
"nextSibling":
|
"nextSibling":
|
||||||
return lua_api._get_element_next_sibling_wrapper(vm, lua_api)
|
return get_element_next_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
"previousSibling":
|
"previousSibling":
|
||||||
return lua_api._get_element_previous_sibling_wrapper(vm, lua_api)
|
return get_element_previous_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
"firstChild":
|
"firstChild":
|
||||||
return lua_api._get_element_first_child_wrapper(vm, lua_api)
|
return get_element_first_child_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
"lastChild":
|
"lastChild":
|
||||||
return lua_api._get_element_last_child_wrapper(vm, lua_api)
|
return get_element_last_child_handler(vm, lua_api.dom_parser, lua_api)
|
||||||
_:
|
_:
|
||||||
return lua_api._element_index_handler(vm)
|
return _element_index_wrapper(vm)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ static func is_date_button(node: Node) -> bool:
|
|||||||
|
|
||||||
static func connect_element_event(signal_node: Node, event_name: String, subscription) -> bool:
|
static func connect_element_event(signal_node: Node, event_name: String, subscription) -> bool:
|
||||||
if not signal_node:
|
if not signal_node:
|
||||||
print("ERROR: Signal node is null for event: ", event_name)
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
match event_name:
|
match event_name:
|
||||||
@@ -151,7 +150,6 @@ static func connect_element_event(signal_node: Node, event_name: String, subscri
|
|||||||
subscription.callback_func = callback # Store for later disconnect
|
subscription.callback_func = callback # Store for later disconnect
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
print("ERROR: No text_changed signal found for input event on ", signal_node.get_class())
|
|
||||||
return false
|
return false
|
||||||
"submit":
|
"submit":
|
||||||
# For form elements - look for a submit button or form container
|
# For form elements - look for a submit button or form container
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
class_name LuaFunctionUtils
|
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
# Core Lua handler functions that extend Lua functionality
|
|
||||||
|
|
||||||
static 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
|
|
||||||
|
|
||||||
static func setup_gurt_api(vm: LuauVM, lua_api, dom_parser: HTMLParser) -> void:
|
|
||||||
# override global print
|
|
||||||
# This makes print() behave like gurt.log()
|
|
||||||
vm.lua_pushcallable(LuaPrintUtils.lua_print, "print")
|
|
||||||
vm.lua_setglobal("print")
|
|
||||||
|
|
||||||
# Add table.tostring utility
|
|
||||||
vm.lua_getglobal("table")
|
|
||||||
if vm.lua_isnil(-1):
|
|
||||||
vm.lua_pop(1)
|
|
||||||
vm.lua_newtable()
|
|
||||||
vm.lua_setglobal("table")
|
|
||||||
vm.lua_getglobal("table")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(LuaFunctionUtils.table_tostring_handler, "table.tostring")
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Setup Clipboard API
|
|
||||||
LuaClipboardUtils.setup_clipboard_api(vm)
|
|
||||||
|
|
||||||
vm.lua_newtable()
|
|
||||||
|
|
||||||
vm.lua_pushcallable(LuaPrintUtils.lua_print, "gurt.log")
|
|
||||||
vm.lua_setfield(-2, "log")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._gurt_select_handler, "gurt.select")
|
|
||||||
vm.lua_setfield(-2, "select")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._gurt_select_all_handler, "gurt.selectAll")
|
|
||||||
vm.lua_setfield(-2, "selectAll")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._gurt_create_handler, "gurt.create")
|
|
||||||
vm.lua_setfield(-2, "create")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._gurt_set_timeout_handler, "gurt.setTimeout")
|
|
||||||
vm.lua_setfield(-2, "setTimeout")
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._gurt_clear_timeout_handler, "gurt.clearTimeout")
|
|
||||||
vm.lua_setfield(-2, "clearTimeout")
|
|
||||||
|
|
||||||
# Add body element access
|
|
||||||
var body_element = dom_parser.find_first("body")
|
|
||||||
if body_element:
|
|
||||||
vm.lua_newtable()
|
|
||||||
vm.lua_pushstring("body")
|
|
||||||
vm.lua_setfield(-2, "_element_id")
|
|
||||||
|
|
||||||
lua_api.add_element_methods(vm)
|
|
||||||
|
|
||||||
vm.lua_pushcallable(lua_api._body_on_event_handler, "body.on")
|
|
||||||
vm.lua_setfield(-2, "on")
|
|
||||||
|
|
||||||
vm.lua_setfield(-2, "body")
|
|
||||||
|
|
||||||
vm.lua_setglobal("gurt")
|
|
||||||
@@ -61,7 +61,6 @@ class LuaSignal:
|
|||||||
|
|
||||||
# Call the function
|
# Call the function
|
||||||
if vm.lua_pcall(args.size(), 0, 0) != vm.LUA_OK:
|
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)
|
vm.lua_pop(1)
|
||||||
# Pop the callbacks table
|
# Pop the callbacks table
|
||||||
vm.lua_pop(1)
|
vm.lua_pop(1)
|
||||||
|
|||||||
501
flumi/Scripts/Utils/Lua/ThreadedVM.gd
Normal file
501
flumi/Scripts/Utils/Lua/ThreadedVM.gd
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
class_name ThreadedLuaVM
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
signal script_completed(result: Dictionary)
|
||||||
|
signal script_error(error: String)
|
||||||
|
signal print_output(message: String)
|
||||||
|
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 = null
|
||||||
|
|
||||||
|
func execute_script_async(script_code: String):
|
||||||
|
queue_mutex.lock()
|
||||||
|
command_queue.append({
|
||||||
|
"type": "execute_script",
|
||||||
|
"code": script_code
|
||||||
|
})
|
||||||
|
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(_threaded_print_handler, "print")
|
||||||
|
lua_vm.lua_setglobal("print")
|
||||||
|
|
||||||
|
# Setup threaded Time.sleep function
|
||||||
|
lua_vm.lua_newtable()
|
||||||
|
lua_vm.lua_pushcallable(_threaded_time_sleep_handler, "Time.sleep")
|
||||||
|
lua_vm.lua_setfield(-2, "sleep")
|
||||||
|
lua_vm.lua_setglobal("Time")
|
||||||
|
|
||||||
|
# Setup GURT API with thread-safe versions
|
||||||
|
_setup_threaded_gurt_api()
|
||||||
|
|
||||||
|
# Setup additional API functions that are needed in callbacks
|
||||||
|
_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)
|
||||||
|
"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):
|
||||||
|
if not lua_vm:
|
||||||
|
call_deferred("_emit_script_error", "Lua VM not initialized")
|
||||||
|
return
|
||||||
|
|
||||||
|
var result = lua_vm.lua_dostring(script_code)
|
||||||
|
|
||||||
|
if 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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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([]):
|
||||||
|
# Clean up the callback from registry after execution
|
||||||
|
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 _threaded_print_handler(vm: LuauVM) -> int:
|
||||||
|
var message_parts: Array = []
|
||||||
|
var num_args = vm.lua_gettop()
|
||||||
|
|
||||||
|
for i in range(1, num_args + 1):
|
||||||
|
var arg_str = ""
|
||||||
|
if vm.lua_isstring(i):
|
||||||
|
arg_str = vm.lua_tostring(i)
|
||||||
|
elif vm.lua_isnumber(i):
|
||||||
|
arg_str = str(vm.lua_tonumber(i))
|
||||||
|
elif vm.lua_isboolean(i):
|
||||||
|
arg_str = "true" if vm.lua_toboolean(i) else "false"
|
||||||
|
elif vm.lua_isnil(i):
|
||||||
|
arg_str = "nil"
|
||||||
|
else:
|
||||||
|
arg_str = vm.lua_typename(vm.lua_type(i))
|
||||||
|
|
||||||
|
message_parts.append(arg_str)
|
||||||
|
|
||||||
|
var final_message = "\t".join(message_parts)
|
||||||
|
var current_time = Time.get_ticks_msec() / 1000.0
|
||||||
|
|
||||||
|
call_deferred("_emit_print_output", final_message)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
func _threaded_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 _setup_threaded_gurt_api():
|
||||||
|
lua_vm.lua_pushcallable(_threaded_print_handler, "print")
|
||||||
|
lua_vm.lua_setglobal("print")
|
||||||
|
|
||||||
|
LuaTimeUtils.setup_time_api(lua_vm)
|
||||||
|
|
||||||
|
lua_vm.lua_getglobal("Time")
|
||||||
|
if not lua_vm.lua_isnil(-1):
|
||||||
|
lua_vm.lua_pushcallable(_threaded_time_sleep_handler, "Time.sleep")
|
||||||
|
lua_vm.lua_setfield(-2, "sleep")
|
||||||
|
lua_vm.lua_pop(1)
|
||||||
|
|
||||||
|
lua_vm.lua_newtable()
|
||||||
|
|
||||||
|
lua_vm.lua_pushcallable(_threaded_print_handler, "gurt.log")
|
||||||
|
lua_vm.lua_setfield(-2, "log")
|
||||||
|
|
||||||
|
lua_vm.lua_pushcallable(_threaded_gurt_select_handler, "gurt.select")
|
||||||
|
lua_vm.lua_setfield(-2, "select")
|
||||||
|
|
||||||
|
lua_vm.lua_pushcallable(_threaded_gurt_select_all_handler, "gurt.selectAll")
|
||||||
|
lua_vm.lua_setfield(-2, "selectAll")
|
||||||
|
|
||||||
|
lua_vm.lua_pushcallable(_threaded_gurt_create_handler, "gurt.create")
|
||||||
|
lua_vm.lua_setfield(-2, "create")
|
||||||
|
|
||||||
|
lua_vm.lua_pushcallable(_threaded_set_timeout_handler, "gurt.setTimeout")
|
||||||
|
lua_vm.lua_setfield(-2, "setTimeout")
|
||||||
|
|
||||||
|
lua_vm.lua_pushcallable(_threaded_clear_timeout_handler, "gurt.clearTimeout")
|
||||||
|
lua_vm.lua_setfield(-2, "clearTimeout")
|
||||||
|
|
||||||
|
# Add body element access
|
||||||
|
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(_threaded_body_on_handler, "body.on")
|
||||||
|
lua_vm.lua_setfield(-2, "on")
|
||||||
|
lua_vm.lua_setfield(-2, "body")
|
||||||
|
|
||||||
|
lua_vm.lua_setglobal("gurt")
|
||||||
|
|
||||||
|
func _setup_additional_lua_apis():
|
||||||
|
# Add table.tostring utility that's needed in callbacks
|
||||||
|
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(_threaded_table_tostring_handler, "table.tostring")
|
||||||
|
lua_vm.lua_setfield(-2, "tostring")
|
||||||
|
lua_vm.lua_pop(1) # Pop table from stack
|
||||||
|
|
||||||
|
# Setup Signal API for threaded execution
|
||||||
|
LuaSignalUtils.setup_signal_api(lua_vm)
|
||||||
|
|
||||||
|
# Setup Clipboard API for threaded execution
|
||||||
|
LuaClipboardUtils.setup_clipboard_api(lua_vm)
|
||||||
|
|
||||||
|
func _threaded_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(message: String):
|
||||||
|
print_output.emit(message)
|
||||||
|
|
||||||
|
func _threaded_gurt_select_all_handler(vm: LuauVM) -> int:
|
||||||
|
# For threaded mode, selectAll is complex as it requires DOM access
|
||||||
|
# Return empty array for now, or implement via main thread operation
|
||||||
|
vm.lua_newtable()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func _threaded_gurt_create_handler(vm: LuauVM) -> int:
|
||||||
|
# Create new HTML element using existing system
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Create HTML element using existing HTMLParser
|
||||||
|
var new_element = HTMLParser.HTMLElement.new(tag_name)
|
||||||
|
|
||||||
|
# Apply attributes and content
|
||||||
|
for attr_name in attributes:
|
||||||
|
if attr_name == "text":
|
||||||
|
# Set text content directly on the HTML element
|
||||||
|
new_element.text_content = str(attributes[attr_name])
|
||||||
|
else:
|
||||||
|
new_element.set_attribute(attr_name, str(attributes[attr_name]))
|
||||||
|
|
||||||
|
# Assign a unique ID
|
||||||
|
var element_id = lua_api.get_or_assign_element_id(new_element)
|
||||||
|
new_element.set_attribute("id", element_id)
|
||||||
|
|
||||||
|
# Add to parser's element collection
|
||||||
|
dom_parser.parse_result.all_elements.append(new_element)
|
||||||
|
|
||||||
|
LuaDOMUtils.create_element_wrapper(vm, new_element, lua_api)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func _threaded_set_timeout_handler(vm: LuauVM) -> int:
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
||||||
|
var delay_ms: int = vm.luaL_checkint(2)
|
||||||
|
|
||||||
|
# Generate a unique timeout ID
|
||||||
|
var timeout_id = lua_api.timeout_manager.next_timeout_id
|
||||||
|
lua_api.timeout_manager.next_timeout_id += 1
|
||||||
|
|
||||||
|
# Store the callback in THIS threaded VM's registry
|
||||||
|
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) # Copy the callback function
|
||||||
|
vm.lua_rawset(-3)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
# Create timeout info and send timer creation command to main thread
|
||||||
|
call_deferred("_create_threaded_timeout", timeout_id, delay_ms)
|
||||||
|
|
||||||
|
vm.lua_pushinteger(timeout_id)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func _threaded_clear_timeout_handler(vm: LuauVM) -> int:
|
||||||
|
# Delegate to Lua API timeout system
|
||||||
|
return lua_api._gurt_clear_timeout_handler(vm)
|
||||||
|
|
||||||
|
func _threaded_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
|
||||||
|
|
||||||
|
# Find the element using the existing SelectorUtils
|
||||||
|
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
||||||
|
if element:
|
||||||
|
# Use DOM.gd element wrapper
|
||||||
|
LuaDOMUtils.create_element_wrapper(vm, element, lua_api)
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
# Return nil if element not found
|
||||||
|
vm.lua_pushnil()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# All element handlers now use DOM.gd wrappers
|
||||||
|
|
||||||
|
func _threaded_body_on_handler(vm: LuauVM) -> int:
|
||||||
|
# Handle body event registration in threaded mode
|
||||||
|
# Arguments: (self, event_name, callback) due to colon syntax
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # self (body table)
|
||||||
|
var event_name: String = vm.luaL_checkstring(2) # event name
|
||||||
|
vm.luaL_checktype(3, vm.LUA_TFUNCTION) # callback function
|
||||||
|
|
||||||
|
# Store callback in registry
|
||||||
|
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
|
||||||
|
|
||||||
|
# Get a proper subscription ID
|
||||||
|
var subscription_id = lua_api.next_subscription_id
|
||||||
|
lua_api.next_subscription_id += 1
|
||||||
|
|
||||||
|
vm.lua_pushinteger(callback_ref)
|
||||||
|
vm.lua_pushvalue(3) # Copy the callback function (3rd argument)
|
||||||
|
vm.lua_rawset(-3)
|
||||||
|
vm.lua_pop(1)
|
||||||
|
|
||||||
|
# Queue DOM operation for main thread (body events)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Return subscription with unsubscribe method
|
||||||
|
vm.lua_newtable()
|
||||||
|
vm.lua_pushinteger(subscription_id)
|
||||||
|
vm.lua_setfield(-2, "_subscription_id")
|
||||||
|
|
||||||
|
vm.lua_pushcallable(Callable(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):
|
||||||
|
# Ensure timeout manager exists
|
||||||
|
lua_api._ensure_timeout_manager()
|
||||||
|
|
||||||
|
# Create timeout info for threaded execution
|
||||||
|
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(timeout_id, timeout_id, lua_vm, lua_api.timeout_manager)
|
||||||
|
lua_api.timeout_manager.active_timeouts[timeout_id] = timeout_info
|
||||||
|
lua_api.timeout_manager.threaded_vm = self
|
||||||
|
|
||||||
|
# Create and start timer on main thread
|
||||||
|
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()
|
||||||
1
flumi/Scripts/Utils/Lua/ThreadedVM.gd.uid
Normal file
1
flumi/Scripts/Utils/Lua/ThreadedVM.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c5t0so2od1scw
|
||||||
@@ -64,12 +64,13 @@ static func time_date_handler(vm: LuauVM) -> int:
|
|||||||
static func time_sleep_handler(vm: LuauVM) -> int:
|
static func time_sleep_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checknumber(1)
|
vm.luaL_checknumber(1)
|
||||||
var seconds = vm.lua_tonumber(1)
|
var seconds = vm.lua_tonumber(1)
|
||||||
var _milliseconds = int(seconds * 1000)
|
|
||||||
|
|
||||||
# TODO: implement a proper sleep function
|
if seconds > 0:
|
||||||
|
var target_time = Time.get_ticks_msec() + (seconds * 1000.0)
|
||||||
|
while Time.get_ticks_msec() < target_time:
|
||||||
|
OS.delay_msec(1)
|
||||||
|
|
||||||
vm.lua_pushnumber(seconds)
|
return 0
|
||||||
return 1
|
|
||||||
|
|
||||||
static func time_benchmark_handler(vm: LuauVM) -> int:
|
static func time_benchmark_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ extends RefCounted
|
|||||||
|
|
||||||
var active_timeouts: Dictionary = {}
|
var active_timeouts: Dictionary = {}
|
||||||
var next_timeout_id: int = 1
|
var next_timeout_id: int = 1
|
||||||
|
var threaded_vm: ThreadedLuaVM = null
|
||||||
|
|
||||||
class TimeoutInfo:
|
class TimeoutInfo:
|
||||||
var id: int
|
var id: int
|
||||||
@@ -58,6 +59,20 @@ func set_timeout_handler(vm: LuauVM, parent_node: Node) -> int:
|
|||||||
vm.lua_pushinteger(timeout_id)
|
vm.lua_pushinteger(timeout_id)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
func clear_timeout_handler(vm: LuauVM) -> int:
|
func clear_timeout_handler(vm: LuauVM) -> int:
|
||||||
var timeout_id: int = vm.luaL_checkint(1)
|
var timeout_id: int = vm.luaL_checkint(1)
|
||||||
|
|
||||||
@@ -68,14 +83,6 @@ func clear_timeout_handler(vm: LuauVM) -> int:
|
|||||||
timeout_info.timer.stop()
|
timeout_info.timer.stop()
|
||||||
timeout_info.timer.queue_free()
|
timeout_info.timer.queue_free()
|
||||||
|
|
||||||
# Clean up callback reference
|
|
||||||
vm.lua_pushstring("GURT_TIMEOUTS")
|
|
||||||
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
|
||||||
if not vm.lua_isnil(-1):
|
|
||||||
vm.lua_pushinteger(timeout_info.callback_ref)
|
|
||||||
vm.lua_pushnil()
|
|
||||||
vm.lua_rawset(-3)
|
|
||||||
vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Remove from active timeouts
|
# Remove from active timeouts
|
||||||
active_timeouts.erase(timeout_id)
|
active_timeouts.erase(timeout_id)
|
||||||
@@ -86,29 +93,10 @@ func _on_timeout_triggered(timeout_info: TimeoutInfo) -> void:
|
|||||||
if not active_timeouts.has(timeout_info.id):
|
if not active_timeouts.has(timeout_info.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Execute the callback
|
if threaded_vm:
|
||||||
timeout_info.vm.lua_pushstring("GURT_TIMEOUTS")
|
_execute_threaded_timeout_callback(timeout_info.id)
|
||||||
timeout_info.vm.lua_rawget(timeout_info.vm.LUA_REGISTRYINDEX)
|
|
||||||
timeout_info.vm.lua_pushinteger(timeout_info.callback_ref)
|
|
||||||
timeout_info.vm.lua_rawget(-2)
|
|
||||||
timeout_info.vm.lua_remove(-2)
|
|
||||||
|
|
||||||
if timeout_info.vm.lua_isfunction(-1):
|
|
||||||
if timeout_info.vm.lua_pcall(0, 0, 0) != timeout_info.vm.LUA_OK:
|
|
||||||
print("GURT ERROR in timeout callback: ", timeout_info.vm.lua_tostring(-1))
|
|
||||||
timeout_info.vm.lua_pop(1)
|
|
||||||
else:
|
|
||||||
timeout_info.vm.lua_pop(1)
|
|
||||||
|
|
||||||
# Clean up timeout
|
|
||||||
timeout_info.timer.queue_free()
|
timeout_info.timer.queue_free()
|
||||||
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.erase(timeout_info.id)
|
active_timeouts.erase(timeout_info.id)
|
||||||
|
|
||||||
func cleanup_all_timeouts():
|
func cleanup_all_timeouts():
|
||||||
@@ -129,3 +117,9 @@ func cleanup_all_timeouts():
|
|||||||
timeout_info.vm.lua_rawset(-3)
|
timeout_info.vm.lua_rawset(-3)
|
||||||
timeout_info.vm.lua_pop(1)
|
timeout_info.vm.lua_pop(1)
|
||||||
active_timeouts.clear()
|
active_timeouts.clear()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
@@ -102,12 +102,9 @@ func render() -> void:
|
|||||||
parser.register_dom_node(body, website_container)
|
parser.register_dom_node(body, website_container)
|
||||||
|
|
||||||
var scripts = parser.find_all("script")
|
var scripts = parser.find_all("script")
|
||||||
var lua_vm = null
|
|
||||||
var lua_api = null
|
var lua_api = null
|
||||||
if scripts.size() > 0:
|
if scripts.size() > 0:
|
||||||
lua_vm = LuauVM.new()
|
|
||||||
lua_api = LuaAPI.new()
|
lua_api = LuaAPI.new()
|
||||||
add_child(lua_vm)
|
|
||||||
add_child(lua_api)
|
add_child(lua_api)
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
@@ -163,8 +160,8 @@ func render() -> void:
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if scripts.size() > 0 and lua_vm and lua_api:
|
if scripts.size() > 0 and lua_api:
|
||||||
parser.process_scripts(lua_api, lua_vm)
|
parser.process_scripts(lua_api, null)
|
||||||
|
|
||||||
static func safe_add_child(parent: Node, child: Node) -> void:
|
static func safe_add_child(parent: Node, child: Node) -> void:
|
||||||
if child.get_parent():
|
if child.get_parent():
|
||||||
@@ -353,7 +350,7 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP
|
|||||||
return node
|
return node
|
||||||
"li":
|
"li":
|
||||||
node = P.instantiate()
|
node = P.instantiate()
|
||||||
node.init(element)
|
node.init(element, parser)
|
||||||
"select":
|
"select":
|
||||||
node = SELECT.instantiate()
|
node = SELECT.instantiate()
|
||||||
node.init(element, parser)
|
node.init(element, parser)
|
||||||
|
|||||||
Reference in New Issue
Block a user