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
+
+
+
+
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