From d47f8116704f974ba8d6aa3090b1688ce60c01c5 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sat, 9 Aug 2025 14:57:29 +0300 Subject: [PATCH] setInterval, clearInterval, image tests --- flumi/Scripts/B9/Lua.gd | 8 ++ flumi/Scripts/Constants.gd | 170 ++++++++++++++++++++++++- flumi/Scripts/Utils/BackgroundUtils.gd | 1 + flumi/Scripts/Utils/Lua/ThreadedVM.gd | 80 ++++++++++-- flumi/Scripts/Utils/Lua/Timeout.gd | 77 ++++++++++- flumi/Scripts/Utils/Lua/WebSocket.gd | 2 +- 6 files changed, 321 insertions(+), 17 deletions(-) diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd index fd32576..96ee6ad 100644 --- a/flumi/Scripts/B9/Lua.gd +++ b/flumi/Scripts/B9/Lua.gd @@ -143,6 +143,14 @@ func _gurt_clear_timeout_handler(vm: LuauVM) -> int: _ensure_timeout_manager() return timeout_manager.clear_timeout_handler(vm) +func _gurt_set_interval_handler(vm: LuauVM) -> int: + _ensure_timeout_manager() + return timeout_manager.set_threaded_interval_handler(vm, self, threaded_vm) + +func _gurt_clear_interval_handler(vm: LuauVM) -> int: + _ensure_timeout_manager() + return timeout_manager.clear_interval_handler(vm) + # Event system handlers func _element_on_event_handler(vm: LuauVM) -> int: vm.luaL_checktype(1, vm.LUA_TTABLE) diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd index e2aeb31..e0c5dfb 100644 --- a/flumi/Scripts/Constants.gd +++ b/flumi/Scripts/Constants.gd @@ -1568,7 +1568,7 @@ var HTML_CONTENTa = """ """.to_utf8_buffer() -var HTML_CONTENT = """ +var HTML_CONTENTva = """ WebSocket API Demo @@ -2014,3 +2014,171 @@ var HTML_CONTENTy = """ """.to_utf8_buffer() +var HTML_CONTENT = """ + setInterval & Network Image Demo + + + + + + + +

⏰ setInterval & 🖼️ Network Image Demo

+ +
+
+

Testing Features:

+

Intervals: gurt.setInterval(callback, delay) and gurt.clearInterval(id)

+

Network Images: fetch() with binary data and dynamic <img> creation

+
+ +
0
+ +

Interval Controls

+
+ + +
+ +

Network Image Test

+
+ +
+ +
+

Click "Load Random Image" to test binary data fetching

+
+ +

Activity Log

+
+
Initializing...
+
+ +
+

Implementation Notes:

+
    +
  • setInterval: Creates repeating timers (one_shot = false)
  • +
  • clearInterval: Stops and cleans up interval timers
  • +
  • Binary Data: fetch() handles binary image data seamlessly
  • +
  • Dynamic DOM: Images created and appended programmatically
  • +
  • Random Images: Picsum service provides different images each time
  • +
+
+
+ +""".to_utf8_buffer() diff --git a/flumi/Scripts/Utils/BackgroundUtils.gd b/flumi/Scripts/Utils/BackgroundUtils.gd index 5972271..5c0bd68 100644 --- a/flumi/Scripts/Utils/BackgroundUtils.gd +++ b/flumi/Scripts/Utils/BackgroundUtils.gd @@ -158,6 +158,7 @@ static func create_panel_container_with_background(styles: Dictionary, hover_sty vbox.name = "VBoxContainer" # Allow mouse events to pass through to the parent PanelContainer vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE + vbox.size_flags_vertical = Control.SIZE_SHRINK_BEGIN panel_container.add_child(vbox) var style_box = create_stylebox_from_styles(styles) diff --git a/flumi/Scripts/Utils/Lua/ThreadedVM.gd b/flumi/Scripts/Utils/Lua/ThreadedVM.gd index 67dc489..e3e7d3e 100644 --- a/flumi/Scripts/Utils/Lua/ThreadedVM.gd +++ b/flumi/Scripts/Utils/Lua/ThreadedVM.gd @@ -204,6 +204,10 @@ func _execute_timeout_in_thread(timeout_id: int): if not lua_vm: return + # Check if this is an interval by looking at the timeout manager + var timeout_info = lua_api.timeout_manager.active_timeouts.get(timeout_id, null) + var is_interval = timeout_info != null and timeout_info.is_interval + # Retrieve timeout callback from the special timeout registry lua_vm.lua_pushstring("GURT_THREADED_TIMEOUTS") lua_vm.lua_rawget(lua_vm.LUA_REGISTRYINDEX) @@ -213,14 +217,15 @@ func _execute_timeout_in_thread(timeout_id: int): 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) + # Only clean up the callback if it's a timeout (not an interval) + if not is_interval: + lua_vm.lua_pushstring("GURT_THREADED_TIMEOUTS") + lua_vm.lua_rawget(lua_vm.LUA_REGISTRYINDEX) + if not lua_vm.lua_isnil(-1): + lua_vm.lua_pushinteger(timeout_id) + lua_vm.lua_pushnil() + lua_vm.lua_rawset(-3) + lua_vm.lua_pop(1) return else: lua_vm.lua_pop(1) # Pop non-function value @@ -294,6 +299,12 @@ func _setup_threaded_gurt_api(): lua_vm.lua_pushcallable(_threaded_clear_timeout_handler, "gurt.clearTimeout") lua_vm.lua_setfield(-2, "clearTimeout") + lua_vm.lua_pushcallable(_threaded_set_interval_handler, "gurt.setInterval") + lua_vm.lua_setfield(-2, "setInterval") + + lua_vm.lua_pushcallable(_threaded_clear_interval_handler, "gurt.clearInterval") + lua_vm.lua_setfield(-2, "clearInterval") + # Add body element access var body_element = dom_parser.find_first("body") if body_element: @@ -407,6 +418,39 @@ func _threaded_clear_timeout_handler(vm: LuauVM) -> int: # Delegate to Lua API timeout system return lua_api._gurt_clear_timeout_handler(vm) +func _threaded_set_interval_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TFUNCTION) + var delay_ms: int = vm.luaL_checkint(2) + + # Generate a unique interval ID + var interval_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 (same as timeout) + vm.lua_pushstring("GURT_THREADED_TIMEOUTS") + vm.lua_rawget(vm.LUA_REGISTRYINDEX) + if vm.lua_isnil(-1): + vm.lua_pop(1) + vm.lua_newtable() + vm.lua_pushstring("GURT_THREADED_TIMEOUTS") + vm.lua_pushvalue(-2) + vm.lua_rawset(vm.LUA_REGISTRYINDEX) + + vm.lua_pushinteger(interval_id) + vm.lua_pushvalue(1) # Copy the callback function + vm.lua_rawset(-3) + vm.lua_pop(1) + + # Create interval info and send timer creation command to main thread + call_deferred("_create_threaded_interval", interval_id, delay_ms) + + vm.lua_pushinteger(interval_id) + return 1 + +func _threaded_clear_interval_handler(vm: LuauVM) -> int: + # Delegate to Lua API timeout system (clearInterval works same as clearTimeout) + return lua_api._gurt_clear_interval_handler(vm) + func _threaded_gurt_select_handler(vm: LuauVM) -> int: var selector: String = vm.luaL_checkstring(1) @@ -483,7 +527,7 @@ func _create_threaded_timeout(timeout_id: int, delay_ms: int): 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) + var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(timeout_id, timeout_id, lua_vm, lua_api.timeout_manager, false, delay_ms) lua_api.timeout_manager.active_timeouts[timeout_id] = timeout_info lua_api.timeout_manager.threaded_vm = self @@ -496,3 +540,21 @@ func _create_threaded_timeout(timeout_id: int, delay_ms: int): timeout_info.timer = timer lua_api.add_child(timer) timer.start() + +func _create_threaded_interval(interval_id: int, delay_ms: int): + # Ensure timeout manager exists + lua_api._ensure_timeout_manager() + + # Create interval info for threaded execution + var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(interval_id, interval_id, lua_vm, lua_api.timeout_manager, true, delay_ms) + lua_api.timeout_manager.active_timeouts[interval_id] = timeout_info + lua_api.timeout_manager.threaded_vm = self + + var timer = Timer.new() + timer.wait_time = delay_ms / 1000.0 + timer.one_shot = false # Repeating timer for intervals + timer.timeout.connect(lua_api.timeout_manager._on_timeout_triggered.bind(timeout_info)) + + timeout_info.timer = timer + lua_api.add_child(timer) + timer.start() diff --git a/flumi/Scripts/Utils/Lua/Timeout.gd b/flumi/Scripts/Utils/Lua/Timeout.gd index 0ddd914..3eff796 100644 --- a/flumi/Scripts/Utils/Lua/Timeout.gd +++ b/flumi/Scripts/Utils/Lua/Timeout.gd @@ -11,12 +11,16 @@ class TimeoutInfo: var vm: LuauVM var timer: Timer var timeout_manager: LuaTimeoutManager + var is_interval: bool = false + var delay_ms: int = 0 - func _init(timeout_id: int, cb_ref: int, lua_vm: LuauVM, manager: LuaTimeoutManager): + func _init(timeout_id: int, cb_ref: int, lua_vm: LuauVM, manager: LuaTimeoutManager, interval: bool = false, delay: int = 0): id = timeout_id callback_ref = cb_ref vm = lua_vm timeout_manager = manager + is_interval = interval + delay_ms = delay func set_timeout_handler(vm: LuauVM, parent_node: Node) -> int: vm.luaL_checktype(1, vm.LUA_TFUNCTION) @@ -59,10 +63,55 @@ func set_timeout_handler(vm: LuauVM, parent_node: Node) -> int: vm.lua_pushinteger(timeout_id) return 1 +func set_interval_handler(vm: LuauVM, parent_node: Node) -> int: + vm.luaL_checktype(1, vm.LUA_TFUNCTION) + var delay_ms: int = vm.luaL_checkint(2) + + var timeout_id = next_timeout_id + next_timeout_id += 1 + + # Store callback in isolated registry table + vm.lua_pushstring("GURT_TIMEOUTS") + vm.lua_rawget(vm.LUA_REGISTRYINDEX) + if vm.lua_isnil(-1): + vm.lua_pop(1) + vm.lua_newtable() + vm.lua_pushstring("GURT_TIMEOUTS") + vm.lua_pushvalue(-2) + vm.lua_rawset(vm.LUA_REGISTRYINDEX) + + vm.lua_pushinteger(timeout_id) + vm.lua_pushvalue(1) + vm.lua_rawset(-3) + vm.lua_pop(1) + + # Create interval info + var timeout_info = TimeoutInfo.new(timeout_id, timeout_id, vm, self, true, delay_ms) + + # Create and configure timer for intervals + var timer = Timer.new() + timer.wait_time = delay_ms / 1000.0 + timer.one_shot = false # This is the key difference - repeating timer + timer.timeout.connect(_on_timeout_triggered.bind(timeout_info)) + + timeout_info.timer = timer + active_timeouts[timeout_id] = timeout_info + + # Add timer to scene tree + parent_node.add_child(timer) + timer.start() + + vm.lua_pushinteger(timeout_id) + return 1 + 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 set_threaded_interval_handler(vm: LuauVM, parent_node: Node, threaded_vm_ref: ThreadedLuaVM) -> int: + threaded_vm = threaded_vm_ref + return set_interval_handler(vm, parent_node) + 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 @@ -76,6 +125,19 @@ func _create_timer_on_main_thread(timeout_info: TimeoutInfo, delay_ms: int, pare func clear_timeout_handler(vm: LuauVM) -> int: var timeout_id: int = vm.luaL_checkint(1) + # If we have a threaded VM, defer the cleanup to main thread + if threaded_vm: + call_deferred("_cleanup_timeout_on_main_thread", timeout_id) + else: + _cleanup_timeout_immediately(timeout_id) + + return 0 + +func _cleanup_timeout_on_main_thread(timeout_id: int): + # This runs on the main thread - safe to access timers + _cleanup_timeout_immediately(timeout_id) + +func _cleanup_timeout_immediately(timeout_id: int): var timeout_info = active_timeouts.get(timeout_id, null) if timeout_info: # Stop and remove timer @@ -83,11 +145,11 @@ func clear_timeout_handler(vm: LuauVM) -> int: timeout_info.timer.stop() timeout_info.timer.queue_free() - # Remove from active timeouts active_timeouts.erase(timeout_id) - - return 0 + +func clear_interval_handler(vm: LuauVM) -> int: + return clear_timeout_handler(vm) func _on_timeout_triggered(timeout_info: TimeoutInfo) -> void: if not active_timeouts.has(timeout_info.id): @@ -96,8 +158,11 @@ func _on_timeout_triggered(timeout_info: TimeoutInfo) -> void: if threaded_vm: _execute_threaded_timeout_callback(timeout_info.id) - timeout_info.timer.queue_free() - active_timeouts.erase(timeout_info.id) + # For intervals, don't clean up - let them keep running + # For timeouts, clean up after execution + if not timeout_info.is_interval: + timeout_info.timer.queue_free() + active_timeouts.erase(timeout_info.id) func cleanup_all_timeouts(): # Clean up all active timeouts diff --git a/flumi/Scripts/Utils/Lua/WebSocket.gd b/flumi/Scripts/Utils/Lua/WebSocket.gd index cb178c9..4182d2f 100644 --- a/flumi/Scripts/Utils/Lua/WebSocket.gd +++ b/flumi/Scripts/Utils/Lua/WebSocket.gd @@ -257,4 +257,4 @@ static func _websocket_connect(vm: LuauVM) -> int: if wrapper: wrapper.connect_to_url() - return 0 \ No newline at end of file + return 0