miggor-StreamGraph/rpc_renderer/rpc_renderer.gd

120 lines
2.6 KiB
GDScript3
Raw Normal View History

# (c) 2023-present Eroax
# (c) 2023-present Yagich
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
extends Node
class_name RPCRenderer
## A WebSocket API server.
##
## A renderer that exposes a subset of the core API to remote clients using WebSocket.
const SCOPES_DIR := "res://rpc_renderer/scopes/"
@export var default_port := 6907
var _ws: WebSocketServer = WebSocketServer.new()
var scopes: Array[RPCScope]
var request_schema: Zodot
func load_scopes() -> void:
var d := DirAccess.open(SCOPES_DIR)
d.list_dir_begin()
var current := d.get_next()
while current != "":
if !d.current_is_dir():
var scope = load(SCOPES_DIR.path_join(current)).new() as RPCScope
scopes.append(scope)
current = d.get_next()
for scope in scopes:
scope.event.connect(
func(event: RPCEvent):
send_frame(0, event)
)
scope.response.connect(
func(response: RPCRequestResponse):
send_frame(response.peer_id, response)
if response.event_counterpart != null:
send_frame(-response.peer_id, response.event_counterpart)
)
func build_schema() -> void:
var scope_names = scopes.map(
func(scope: RPCScope):
return scope.name
)
var scopes_schema: Array[Zodot]
scopes_schema.assign(scope_names.map(
func(scope_name: String):
return Z.literal(scope_name)
))
request_schema = Z.schema({
"request": Z.schema({
"id": Z.string(),
"scope": Z.union(scopes_schema),
"operation": RPCOperation.schema(),
"keep": Z.dictionary().nullable(),
})
})
func _ready() -> void:
load_scopes()
build_schema()
add_child(_ws)
_ws.client_connected.connect(_on_ws_client_connected)
_ws.client_disconnected.connect(_on_ws_client_disconnected)
_ws.message_received.connect(_on_ws_message)
listen()
func listen(port := default_port) -> void:
if _ws.listen(port) != OK:
pass
func stop() -> void:
_ws.stop()
func send_frame(peer_id: int, frame: RPCFrame) -> void:
_ws.send(peer_id, JSON.stringify(frame.to_dict(), "", false))
func _on_ws_message(peer_id: int, message: Variant) -> void:
if not message is String:
return
var json := JSON.new()
var err := json.parse(message)
if err != OK:
send_frame(peer_id, RPCError.new(json.get_error_message()))
return
var result = request_schema.parse(json.get_data())
if not result.ok():
send_frame(peer_id, RPCError.new(result.error))
return
var req := RPCRequest.from_dict(result.data, peer_id)
for scope in scopes:
if scope.can_handle_request(req):
scope.handle_request(req)
return
func _on_ws_client_connected(peer_id: int) -> void:
pass
func _on_ws_client_disconnected(peer_id: int) -> void:
pass