Files
leonwww/flumi/Scripts/Utils/Lua/WebSocket.gd
2025-08-09 14:57:29 +03:00

261 lines
6.3 KiB
GDScript

class_name LuaWebSocketUtils
extends RefCounted
static var websocket_instances: Dictionary = {}
static var instance_counter: int = 0
class WebSocketWrapper:
extends RefCounted
var instance_id: String
var vm: LuauVM
var url: String
var websocket: WebSocketPeer
var is_connected: bool = false
var event_handlers: Dictionary = {}
var timer: Timer
var last_state: int = -1
func _init():
websocket = WebSocketPeer.new()
func connect_to_url():
if is_connected:
return
var error = websocket.connect_to_url(url)
if error == OK:
# Start polling timer
if timer:
timer.queue_free()
timer = Timer.new()
timer.wait_time = 0.016 # ~60 FPS polling
timer.timeout.connect(_poll_websocket)
# Add to scene tree using call_deferred for thread safety
var main_loop = Engine.get_main_loop()
if main_loop and main_loop.current_scene:
main_loop.current_scene.call_deferred("add_child", timer)
timer.call_deferred("start")
else:
trigger_event("error", {"message": "No scene available for WebSocket timer"})
else:
trigger_event("error", {"message": "Failed to connect to " + url + " (error: " + str(error) + ")"})
func _poll_websocket():
if not websocket:
return
websocket.poll()
var state = websocket.get_ready_state()
match state:
WebSocketPeer.STATE_OPEN:
if not is_connected:
is_connected = true
trigger_event("open", {})
# Check for messages
while websocket.get_available_packet_count() > 0:
var packet = websocket.get_packet()
var message = packet.get_string_from_utf8()
trigger_event("message", {"data": message})
WebSocketPeer.STATE_CLOSED:
if is_connected:
is_connected = false
trigger_event("close", {})
# Clean up timer
if timer:
timer.queue_free()
timer = null
WebSocketPeer.STATE_CONNECTING:
# Still connecting, keep polling
pass
WebSocketPeer.STATE_CLOSING:
# Connection is closing
if is_connected:
is_connected = false
_:
# Unknown state or connection failed
if is_connected:
is_connected = false
trigger_event("close", {})
elif not is_connected:
# This might be a connection failure
trigger_event("error", {"message": "Connection failed or was rejected by server"})
func send_message(message: String):
if is_connected and websocket:
websocket.send_text(message)
func close_connection():
if websocket:
websocket.close()
is_connected = false
if timer:
timer.queue_free()
timer = null
func trigger_event(event_name: String, data: Dictionary):
if not vm or not event_handlers.has(event_name):
return
var func_ref = event_handlers[event_name]
# Get the function from the reference
vm.lua_getref(func_ref)
if vm.lua_isfunction(-1):
# Create event data table
vm.lua_newtable()
for key in data:
vm.lua_pushvariant(data[key])
vm.lua_setfield(-2, key)
# Call the function
var result = vm.lua_pcall(1, 0, 0)
if result != vm.LUA_OK:
vm.lua_pop(1)
else:
vm.lua_pop(1) # Pop the non-function value
static func setup_websocket_api(vm: LuauVM):
vm.lua_newtable()
vm.lua_pushcallable(_websocket_new, "WebSocket.new")
vm.lua_setfield(-2, "new")
vm.lua_setglobal("WebSocket")
static func _websocket_new(vm: LuauVM) -> int:
# handle both WebSocket:new(url) and WebSocket.new(url) syntax
var url: String
if vm.lua_gettop() == 2 and vm.lua_istable(1):
url = vm.luaL_checkstring(2)
else:
url = vm.luaL_checkstring(1)
# Generate unique instance ID
instance_counter += 1
var instance_id = "ws_" + str(instance_counter)
# Create WebSocket wrapper object
var wrapper = WebSocketWrapper.new()
wrapper.instance_id = instance_id
wrapper.vm = vm
wrapper.url = url
# Store in global instances to keep it alive
websocket_instances[instance_id] = wrapper
# Create Lua table for the WebSocket
vm.lua_newtable()
# Store instance ID
vm.lua_pushstring(instance_id)
vm.lua_setfield(-2, "_instance_id")
# Store URL
vm.lua_pushstring(url)
vm.lua_setfield(-2, "_url")
# Store connection state
vm.lua_pushboolean(false)
vm.lua_setfield(-2, "_connected")
# Initialize event handlers table
vm.lua_newtable()
vm.lua_setfield(-2, "_event_handlers")
# Add methods
vm.lua_pushcallable(_websocket_on, "websocket.on")
vm.lua_setfield(-2, "on")
vm.lua_pushcallable(_websocket_send, "websocket.send")
vm.lua_setfield(-2, "send")
vm.lua_pushcallable(_websocket_close, "websocket.close")
vm.lua_setfield(-2, "close")
vm.lua_pushcallable(_websocket_connect, "websocket.connect")
vm.lua_setfield(-2, "connect")
# Auto-connect
wrapper.connect_to_url()
return 1
static func _websocket_on(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var event_name: String = vm.luaL_checkstring(2)
vm.luaL_checktype(3, vm.LUA_TFUNCTION)
# Get instance ID
vm.lua_getfield(1, "_instance_id")
var instance_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
# Get wrapper instance
var wrapper: WebSocketWrapper = websocket_instances.get(instance_id)
if wrapper:
# Store the function reference in wrapper
var func_ref = vm.lua_ref(3)
wrapper.event_handlers[event_name] = func_ref
return 0
static func _websocket_send(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var message: String = vm.luaL_checkstring(2)
# Get instance ID
vm.lua_getfield(1, "_instance_id")
var instance_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
# Get wrapper instance
var wrapper: WebSocketWrapper = websocket_instances.get(instance_id)
if wrapper and wrapper.is_connected:
wrapper.send_message(message)
else:
vm.luaL_error("WebSocket is not connected")
return 0
static func _websocket_close(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
# Get instance ID
vm.lua_getfield(1, "_instance_id")
var instance_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
# Get wrapper instance
var wrapper: WebSocketWrapper = websocket_instances.get(instance_id)
if wrapper:
wrapper.close_connection()
return 0
static func _websocket_connect(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
# Get instance ID
vm.lua_getfield(1, "_instance_id")
var instance_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
# Get wrapper instance
var wrapper: WebSocketWrapper = websocket_instances.get(instance_id)
if wrapper:
wrapper.connect_to_url()
return 0