setInterval, clearInterval, image tests
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -1568,7 +1568,7 @@ var HTML_CONTENTa = """<head>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
var HTML_CONTENT = """<head>
|
||||
var HTML_CONTENTva = """<head>
|
||||
<title>WebSocket API Demo</title>
|
||||
<icon src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Lua-Logo.svg/256px-Lua-Logo.svg.png">
|
||||
<meta name="theme-color" content="#059669">
|
||||
@@ -2014,3 +2014,171 @@ var HTML_CONTENTy = """<head>
|
||||
</div>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
var HTML_CONTENT = """<head>
|
||||
<title>setInterval & Network Image Demo</title>
|
||||
<icon src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Lua-Logo.svg/256px-Lua-Logo.svg.png">
|
||||
<meta name="theme-color" content="#7c3aed">
|
||||
<meta name="description" content="Testing setInterval/clearInterval and network image loading with binary data">
|
||||
<style>
|
||||
body { bg-[#f8fafc] p-6 }
|
||||
h1 { text-[#7c3aed] text-3xl font-bold text-center }
|
||||
h2 { text-[#6d28d9] text-xl font-semibold }
|
||||
.container { bg-[#ffffff] p-6 rounded-lg shadow-lg max-w-4xl mx-auto }
|
||||
.button-group { flex gap-3 justify-center items-center flex-wrap }
|
||||
.control-button { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#7c3aed] text-white hover:bg-[#6d28d9] }
|
||||
.danger-button { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#ef4444] text-white hover:bg-[#dc2626] }
|
||||
.success-button { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#059669] text-white hover:bg-[#047857] }
|
||||
.log-area { bg-[#f1f5f9] p-4 rounded-lg min-h-32 font-mono text-sm max-h-96 overflow-auto }
|
||||
.image-container { text-center p-4 border-2 border-dashed border-[#cbd5e1] rounded-lg }
|
||||
.counter { text-[#7c3aed] text-6xl font-bold text-center p-4 }
|
||||
.info-box { bg-[#ede9fe] border border-[#7c3aed] p-4 rounded-lg }
|
||||
</style>
|
||||
<script>
|
||||
-- Get UI elements
|
||||
local logArea = gurt.select('#log-area')
|
||||
local counterDisplay = gurt.select('#counter')
|
||||
local startIntervalBtn = gurt.select('#start-interval-btn')
|
||||
local stopIntervalBtn = gurt.select('#stop-interval-btn')
|
||||
local loadImageBtn = gurt.select('#load-image-btn')
|
||||
local imageContainer = gurt.select('#image-container')
|
||||
|
||||
gurt.log('setInterval & Network Image demo script started.')
|
||||
|
||||
local logMessages = {}
|
||||
local counter = 0
|
||||
local intervalId = nil
|
||||
|
||||
-- Function to add message to log
|
||||
local function addLog(message)
|
||||
table.insert(logMessages, Time.format(Time.now(), '%H:%M:%S.%f'):sub(1, 12) .. ' - ' .. message)
|
||||
if #logMessages > 30 then
|
||||
table.remove(logMessages, 1)
|
||||
end
|
||||
logArea.text = table.concat(logMessages, '\\n')
|
||||
end
|
||||
|
||||
-- Update counter display
|
||||
local function updateCounter()
|
||||
counterDisplay.text = tostring(counter)
|
||||
end
|
||||
|
||||
-- Start interval button
|
||||
startIntervalBtn:on('click', function()
|
||||
if intervalId then
|
||||
addLog('❌ Interval already running (ID: ' .. intervalId .. ')')
|
||||
return
|
||||
end
|
||||
|
||||
counter = 0
|
||||
updateCounter()
|
||||
|
||||
intervalId = gurt.setInterval(function()
|
||||
counter = counter + 1
|
||||
updateCounter()
|
||||
addLog('⏱️ Counter updated to: ' .. counter)
|
||||
end, 500) -- Every 500ms
|
||||
|
||||
addLog('✅ Interval started (ID: ' .. intervalId .. ')')
|
||||
end)
|
||||
|
||||
-- Stop interval button
|
||||
stopIntervalBtn:on('click', function()
|
||||
if not intervalId then
|
||||
addLog('❌ No interval to stop')
|
||||
return
|
||||
end
|
||||
|
||||
gurt.clearInterval(intervalId)
|
||||
addLog('🛑 Interval stopped (ID: ' .. intervalId .. ')')
|
||||
intervalId = nil
|
||||
end)
|
||||
|
||||
-- Network image loading
|
||||
loadImageBtn:on('click', function()
|
||||
addLog('🔄 Starting random image fetch...')
|
||||
imageContainer.text = 'Loading image...'
|
||||
|
||||
-- Use a random image service that returns binary data
|
||||
local randomImageUrl = 'https://picsum.photos/300/200?random=' .. math.floor(Time.now() * 1000)
|
||||
|
||||
addLog('📡 Fetching: ' .. randomImageUrl)
|
||||
|
||||
local response = fetch(randomImageUrl)
|
||||
|
||||
-- Handle both success and redirect cases (302 is common for image services)
|
||||
if response:ok() or response.status == 302 then
|
||||
addLog('✅ Image fetch response received (Status: ' .. response.status .. ')')
|
||||
addLog('📊 Content-Type: ' .. (response.headers['content-type'] or 'unknown'))
|
||||
addLog('📏 Content-Length: ' .. (response.headers['content-length'] or 'unknown'))
|
||||
|
||||
-- For image services, we can often use the original URL directly
|
||||
-- since the browser/engine handles redirects automatically
|
||||
local imageElement = gurt.create('img', {
|
||||
src = randomImageUrl,
|
||||
style = 'max-w-[300px] max-h-[200px] rounded-lg shadow-md'
|
||||
})
|
||||
|
||||
-- Clear container and append new image
|
||||
imageContainer.text = ''
|
||||
imageContainer:append(imageElement)
|
||||
|
||||
addLog('🖼️ Image element created and appended')
|
||||
addLog('💡 Note: Image service may use redirects (302), but img tag handles this')
|
||||
else
|
||||
addLog('❌ Image fetch failed: ' .. response.status .. ' ' .. (response.statusText or 'Unknown'))
|
||||
imageContainer.text = 'Failed to load image'
|
||||
end
|
||||
end)
|
||||
|
||||
-- Initialize
|
||||
updateCounter()
|
||||
addLog('🚀 Demo ready!')
|
||||
addLog('💡 Click "Start Interval" to begin counter')
|
||||
addLog('🖼️ Click "Load Random Image" to test binary data handling')
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>⏰ setInterval & 🖼️ Network Image Demo</h1>
|
||||
|
||||
<div style="container mt-6">
|
||||
<div style="info-box mb-6">
|
||||
<h3 style="text-[#6d28d9] font-semibold mb-2">Testing Features:</h3>
|
||||
<p><strong>Intervals:</strong> <code>gurt.setInterval(callback, delay)</code> and <code>gurt.clearInterval(id)</code></p>
|
||||
<p><strong>Network Images:</strong> <code>fetch()</code> with binary data and dynamic <code><img></code> creation</p>
|
||||
</div>
|
||||
|
||||
<div style="counter mb-6" id="counter">0</div>
|
||||
|
||||
<h2>Interval Controls</h2>
|
||||
<div style="button-group mb-6">
|
||||
<button id="start-interval-btn" style="control-button">▶️ Start Interval</button>
|
||||
<button id="stop-interval-btn" style="danger-button">⏹️ Stop Interval</button>
|
||||
</div>
|
||||
|
||||
<h2>Network Image Test</h2>
|
||||
<div style="button-group mb-6">
|
||||
<button id="load-image-btn" style="success-button">🖼️ Load Random Image</button>
|
||||
</div>
|
||||
|
||||
<div id="image-container" style="image-container mb-6">
|
||||
<p style="text-[#64748b]">Click "Load Random Image" to test binary data fetching</p>
|
||||
</div>
|
||||
|
||||
<h2>Activity Log</h2>
|
||||
<div style="log-area mb-6">
|
||||
<pre id="log-area">Initializing...</pre>
|
||||
</div>
|
||||
|
||||
<div style="bg-[#f3f4f6] p-4 rounded-lg">
|
||||
<h3 style="text-[#374151] font-semibold mb-2">Implementation Notes:</h3>
|
||||
<ul style="text-[#4b5563] space-y-1 text-sm">
|
||||
<li><strong>setInterval:</strong> Creates repeating timers (one_shot = false)</li>
|
||||
<li><strong>clearInterval:</strong> Stops and cleans up interval timers</li>
|
||||
<li><strong>Binary Data:</strong> fetch() handles binary image data seamlessly</li>
|
||||
<li><strong>Dynamic DOM:</strong> Images created and appended programmatically</li>
|
||||
<li><strong>Random Images:</strong> Picsum service provides different images each time</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -257,4 +257,4 @@ static func _websocket_connect(vm: LuauVM) -> int:
|
||||
if wrapper:
|
||||
wrapper.connect_to_url()
|
||||
|
||||
return 0
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user