remove RPC (#153)

bye bye

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/153
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
Lera Elvoé 2024-05-08 07:59:19 +00:00 committed by yagich
parent 240750c48e
commit ba2dd6a837
27 changed files with 6 additions and 1685 deletions

View file

@ -78,16 +78,6 @@ signal node_moved(node_id: String, new_position: Dictionary, deck: Deck)
#endregion
func connect_rpc_signals() -> void:
node_added.connect(RPCSignalLayer._on_deck_node_added)
node_removed.connect(RPCSignalLayer._on_deck_node_removed)
nodes_connected.connect(RPCSignalLayer._on_deck_nodes_connected.bind(id))
nodes_disconnected.connect(RPCSignalLayer._on_deck_nodes_disconnected.bind(id))
variables_updated.connect(RPCSignalLayer._on_deck_variables_updated.bind(id))
## Instantiate a node by its' [member DeckNode.node_type] and add it to this deck.[br]
## See [method add_node_inst] for parameter descriptions.
func add_node_type(type: String, assign_id: String = "", assign_to_self: bool = true) -> DeckNode:
@ -138,7 +128,6 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool
node_moved.emit(node._id, new_position, self)
)
node.connect_rpc_signals()
print_verbose("Deck %s::%s: added node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()])
return node

View file

@ -23,9 +23,6 @@ enum Compat {
static func _static_init() -> void:
signals.deck_added.connect(RPCSignalLayer._on_deck_added)
signals.deck_closed.connect(RPCSignalLayer._on_deck_closed)
NodeDB.init()
@ -35,7 +32,6 @@ static func add_empty_deck() -> Deck:
var uuid := UUID.v4()
decks[uuid] = deck
deck.id = uuid
deck.connect_rpc_signals()
signals.deck_added.emit(uuid)
print_verbose("DeckHolder: added empty deck %s, id %s" % [deck.id, deck.get_instance_id()])
return deck
@ -78,7 +74,6 @@ static func open_deck_from_dict(data: Dictionary, path := "") -> Deck:
apply_compat_patches(data, get_deck_compat(data))
var deck := Deck.from_dict(data, path)
decks[deck.id] = deck
deck.connect_rpc_signals()
signals.deck_added.emit(deck.id)
print_verbose("DeckHolder: opened deck %s, id %s" % [deck.id, deck.get_instance_id()])
return deck
@ -97,7 +92,6 @@ static func add_group_from_dict(data: Dictionary, deck_id: String, instance_id:
connect_group_signals(group)
if deck_id not in groups_emitted:
group.connect_rpc_signals()
signals.deck_added.emit(deck_id)
groups_emitted.append(deck_id)
#print(decks)
@ -149,7 +143,6 @@ static func add_empty_group(parent: String = "") -> Deck:
decks[group.id] = {group.instance_id: group}
connect_group_signals(group)
if group.id not in groups_emitted:
group.connect_rpc_signals()
signals.deck_added.emit(group.id)
groups_emitted.append(group.id)
print_verbose("DeckHolder: added empty group %s::%s, id %s" % [group.id, group.instance_id, group.get_instance_id()])

View file

@ -74,13 +74,6 @@ signal port_value_updated(port_idx: int, new_value: Variant)
signal renamed(new_name: String)
func connect_rpc_signals() -> void:
Util.safe_connect(port_added, RPCSignalLayer._on_node_port_added.bind(self))
Util.safe_connect(ports_updated, RPCSignalLayer._on_node_ports_updated.bind(self))
Util.safe_connect(port_value_updated, RPCSignalLayer._on_node_port_value_updated.bind(self))
Util.safe_connect(renamed, RPCSignalLayer._on_node_renamed.bind(self))
## Add an input port to this node. Usually only used at initialization.
func add_input_port(
type: DeckType.Types,

View file

@ -41,6 +41,7 @@ enum ConnectionsMenuId {
TWITCH,
RPC,
}
@onready var connections_popup_menu: PopupMenu = %Connections
enum DebugMenuId {
DECKS,
@ -71,7 +72,6 @@ var _deck_to_save: WeakRef
@onready var obs_setup_dialog := $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog
@onready var twitch_setup_dialog := $Twitch_Setup_Dialog as TwitchSetupDialog
@onready var rpc_setup_dialog := $RPCSetupDialog as RPCSetupDialog
@onready var bottom_dock: BottomDock = %BottomDock
@onready var sidebar_split: HSplitContainer = %SidebarSplit
@ -80,8 +80,6 @@ var _deck_to_save: WeakRef
@onready var compat_dialog: ConfirmationDialog = %CompatDialog
signal quit_completed()
signal rpc_start_requested(port: int)
signal rpc_stop_requested()
func _ready() -> void:
@ -114,32 +112,6 @@ func _ready() -> void:
add_recents_to_menu()
var rpc_port: int = RendererPersistence.get_or_create(
PERSISTENCE_NAMESPACE, "config",
"rpc_server_port", 6907
)
rpc_setup_dialog.set_port(rpc_port)
rpc_setup_dialog.start_requested.connect(
func(port: int):
rpc_start_requested.emit(port)
RendererPersistence.set_value(
PERSISTENCE_NAMESPACE, "config",
"rpc_server_port", port
)
)
rpc_setup_dialog.stop_requested.connect(
func():
rpc_stop_requested.emit()
)
rpc_setup_dialog.confirmed.connect(
func():
RendererPersistence.set_value(
PERSISTENCE_NAMESPACE, "config",
"rpc_server_port", rpc_setup_dialog.get_port()
)
)
tab_container.tab_close_requested.connect(
func(tab: int):
if tab_container.get_tab_metadata(tab, "dirty") and not tab_container.get_tab_metadata(tab, "group"):
@ -182,6 +154,8 @@ func _ready() -> void:
deck_renderer.focus_node(deck_renderer.get_node_renderer(node))
)
connections_popup_menu.set_item_tooltip(ConnectionsMenuId.RPC, "RPC support has been removed and will return in a future version.")
func reset_popup_menu_shortcuts() -> void:
file_popup_menu.set_item_shortcut(FileMenuId.NEW, RendererShortcuts.get_shortcut("new_deck"))
@ -516,8 +490,6 @@ func _on_connections_id_pressed(id: int) -> void:
obs_setup_dialog.popup_centered()
ConnectionsMenuId.TWITCH:
twitch_setup_dialog.popup_centered()
ConnectionsMenuId.RPC:
rpc_setup_dialog.popup_centered()
func _on_obs_websocket_setup_dialog_connect_button_pressed(state: OBSWebsocketSetupDialog.ConnectionState) -> void:

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=19 format=3 uid="uid://duaah5x0jhkn6"]
[gd_scene load_steps=18 format=3 uid="uid://duaah5x0jhkn6"]
[ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"]
[ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"]
@ -15,7 +15,6 @@
[ext_resource type="PackedScene" uid="uid://cuwou2aa7qfc2" path="res://graph_node_renderer/unsaved_changes_dialog_single_deck.tscn" id="8_qf6ve"]
[ext_resource type="PackedScene" uid="uid://cvvkj138fg8jg" path="res://graph_node_renderer/unsaved_changes_dialog.tscn" id="9_4n0q6"]
[ext_resource type="PackedScene" uid="uid://bu466w2w3q08c" path="res://graph_node_renderer/about_dialog.tscn" id="11_6ln7n"]
[ext_resource type="PackedScene" uid="uid://brfrufvkjwcor" path="res://graph_node_renderer/rpc_setup_dialog.tscn" id="12_1xrfk"]
[ext_resource type="PackedScene" uid="uid://dodqetbke5wji" path="res://graph_node_renderer/settings/settings_dialog.tscn" id="16_rktri"]
[ext_resource type="PackedScene" uid="uid://cd1t0gvi022gx" path="res://graph_node_renderer/compat_dialog.tscn" id="17_2ndnq"]
@ -104,6 +103,7 @@ item_1/text = "Twitch.."
item_1/id = 1
item_2/text = "RPC Server..."
item_2/id = 2
item_2/disabled = true
[node name="Debug" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
unique_name_in_owner = true
@ -163,8 +163,6 @@ script = ExtResource("5_3n36q")
[node name="Twitch_Setup_Dialog" parent="." instance=ExtResource("7_7rhap")]
[node name="RPCSetupDialog" parent="." instance=ExtResource("12_1xrfk")]
[node name="UnsavedChangesDialogSingleDeck" parent="." instance=ExtResource("8_qf6ve")]
[node name="UnsavedChangesDialog" parent="." instance=ExtResource("9_4n0q6")]

View file

@ -1,50 +0,0 @@
# (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 ConfirmationDialog
class_name RPCSetupDialog
@onready var port_spin_box: SpinBox = %PortSpinBox
@onready var start_server_button: Button = %StartServerButton
var _old_port: int
var is_started: bool = false
signal start_requested(port: int)
signal stop_requested()
func _ready() -> void:
canceled.connect(
func():
port_spin_box.value = float(_old_port)
)
confirmed.connect(
func():
_old_port = int(port_spin_box.value)
)
func _on_start_server_button_pressed() -> void:
if not is_started:
start_server_button.text = "Stop"
_old_port = int(port_spin_box.value)
start_requested.emit(int(port_spin_box.value))
port_spin_box.editable = true
is_started = true
else:
start_server_button.text = "Start"
stop_requested.emit()
port_spin_box.editable = true
is_started = false
func set_port(port: int) -> void:
port_spin_box.value = port
_old_port = port
func get_port() -> int:
return int(port_spin_box.value)

View file

@ -1,44 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://brfrufvkjwcor"]
[ext_resource type="Script" path="res://graph_node_renderer/rpc_setup_dialog.gd" id="1_7pkns"]
[node name="RPCSetupDialog" type="ConfirmationDialog"]
title = "RPC Server Setup"
initial_position = 4
size = Vector2i(320, 135)
script = ExtResource("1_7pkns")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Port"
[node name="PortSpinBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
max_value = 25565.0
value = 6907.0
[node name="StartServerButton" type="Button" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 6
text = "Start"
[connection signal="pressed" from="VBoxContainer/StartServerButton" to="." method="_on_start_server_button_pressed"]

12
main.gd
View file

@ -8,8 +8,6 @@ const DEFAULT_RENDERER := preload("res://graph_node_renderer/deck_holder_rendere
var deck_holder_renderer: DeckHolderRenderer = null
var deck_holder_renderer_finished := false
@onready var rpc_renderer := $RPCRenderer as RPCRenderer
func _ready() -> void:
get_tree().auto_accept_quit = false
@ -19,8 +17,6 @@ func _ready() -> void:
deck_holder_renderer = DEFAULT_RENDERER.instantiate()
add_child(deck_holder_renderer)
deck_holder_renderer.quit_completed.connect(_on_deck_holder_renderer_quit_completed)
deck_holder_renderer.rpc_start_requested.connect(_on_deck_holder_renderer_rpc_start_requested)
deck_holder_renderer.rpc_stop_requested.connect(_on_deck_holder_renderer_rpc_stop_requested)
func _on_deck_holder_renderer_quit_completed() -> void:
@ -31,14 +27,6 @@ func _on_deck_holder_renderer_quit_completed() -> void:
get_tree().quit()
func _on_deck_holder_renderer_rpc_start_requested(port: int) -> void:
rpc_renderer.listen(port)
func _on_deck_holder_renderer_rpc_stop_requested() -> void:
rpc_renderer.stop()
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST:
if deck_holder_renderer:

View file

@ -1,10 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://clxtes7sdpe65"]
[gd_scene load_steps=2 format=3 uid="uid://clxtes7sdpe65"]
[ext_resource type="Script" path="res://main.gd" id="1_rxyjw"]
[ext_resource type="Script" path="res://rpc_renderer/rpc_renderer.gd" id="2_m1ypq"]
[node name="Main" type="Node"]
script = ExtResource("1_rxyjw")
[node name="RPCRenderer" type="Node" parent="."]
script = ExtResource("2_m1ypq")

View file

@ -1,40 +0,0 @@
# (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)
class_name RPCFrame
## An RPC frame.
##
## A frame is the second smallest unit of data transmittable over RPC, after base types.
## The name of this frame. When a frame is converted to JSON,
## this will be the key in an object, or if this is the top level frame,
## it will be an object with only one key, which is the value of this property.
var frame_name: String
## The props (key names) that will be serialized to JSON before sending to remote clients.
var _props: Array[StringName]
## Wraps this frame's contents into a dictionary with one key: the [member frame_name]. See [member _props].
## If any prop in [member _props] is also an [RPCFrame], it will be serialized
## using [method to_inner_dict].
func to_dict() -> Dictionary:
return {frame_name: to_inner_dict()}
## Converts the [i]contents[/i] of this frame to a dictionary.
func to_inner_dict() -> Dictionary:
var res := {}
for prop in _props:
var value = get(prop)
if value is RPCFrame:
res[prop] = value.to_dict()
elif value is Array and not value.is_empty() and value[0] is RPCFrame:
res[prop] = value.map(
func(e: RPCFrame):
return e.to_dict()
)
else:
res[prop] = value
return res

View file

@ -1,179 +0,0 @@
# (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 clients: Dictionary # Dictionary[int -> id, Client]
var _ws: WebSocketServer = WebSocketServer.new()
var scopes: Array[RPCScope]
var system_scope: RPCScopeSystem
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:
if scope.name == "system":
system_scope = scope
scope.event.connect(
func(event: RPCEvent):
if event.to_peer != 0:
send_frame(event.to_peer, event)
return
for client_id in clients:
var client: Client = get_client(client_id)
if not client.subscriptions.has(event.scope):
continue
if event.type in (client.subscriptions[event.scope] as Array):
send_frame(client_id, event)
)
scope.response.connect(
func(response: RPCRequestResponse):
send_frame(response.peer_id, response)
if response.event_counterpart != null:
var event := response.event_counterpart
for client_id in clients:
if client_id == response.peer_id:
continue
var client: Client = get_client(client_id)
if event.name in (client.subscriptions[event.scope] as Array):
send_frame(client_id, event)
)
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)
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 get_client(peer_id: int) -> Client:
return clients.get(peer_id)
func drop_client(peer_id: int, reason: String) -> void:
var disconnect_data := {
"disconnect": {
"message": "You have been disconnected: %s" % reason
}
}
_ws.send(peer_id, JSON.stringify(disconnect_data, "", false))
_ws.peers.erase(peer_id)
clients.erase(peer_id)
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, get_client(peer_id))
if not get_client(peer_id).identified:
if not (req.scope == "system" and req.operation.type == "identify"):
drop_client(peer_id, "You must identify your client first.")
return
system_scope.identify(req)
return
var scope_idx := -1
for i in scopes.size():
var scope := scopes[i]
if scope.name == req.scope:
scope_idx = i
break
if scope_idx == -1:
return # TODO: error
var scope := scopes[scope_idx]
if scope.can_handle_request(req):
scope.handle_request(req)
func _on_ws_client_connected(peer_id: int) -> void:
var c := Client.new()
c.id = peer_id
clients[peer_id] = c
RPCSignalLayer.signals.client_connected.emit(c)
func _on_ws_client_disconnected(peer_id: int) -> void:
clients.erase(peer_id)
class Client:
var id: int
var identified: bool = false
var subscriptions: Dictionary = {}

View file

@ -1,103 +0,0 @@
# (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)
class_name RPCScope
## An object that can respond to requests and send events.
##
## A scope is a handler for requests sent by remote clients, and events from the API. It is generally related to different areas of the program.
## The name of this scope.
var name: String
# Dictionary[String -> RPCOperation.type, Callable]
## The operation types this scope can handle.
## Map of [member RPCOperation.type] to [Callable]. The function will be called
## with one argument: the [RPCRequest] to handle.
var operation_types: Dictionary
## Emitted when a response to a request is ready to be sent.
signal response(response: RPCRequestResponse)
## Emitted when an event is ready to be sent.
signal event(event: RPCEvent)
## Returns [code]true[/code] if this scope can handle the given request.
func can_handle_request(request: RPCRequest) -> bool:
return request.operation.type in operation_types
## Wrapper function to handle a request. See [param operation_types].
func handle_request(request: RPCRequest) -> void:
var c: Callable = operation_types[request.operation.type].callable
c.call(request)
## Returns an [RPCRequestResponse] that's set up to respond to the given [param request].
## Does not emit [signal response].
func create_response(request: RPCRequest, data: Variant, error: RPCError = null) -> RPCRequestResponse:
var r := RPCRequestResponse.new(request)
r.for_request = request.id
r.scope = name
r.kept = request.keep
r.peer_id = request.peer_id
if data is RPCFrame:
r.data = data.to_dict()
else:
r.data = data
if error:
r.errors.append(error)
return r
func create_error(request: RPCRequest, error_text: String) -> RPCRequestResponse:
var r := RPCRequestResponse.new(request)
r.for_request = request.id
r.scope = name
r.kept = request.keep
r.peer_id = request.peer_id
r.data = {}
r.errors.append(RPCError.new(error_text))
return r
func create_generic_success(request: RPCRequest) -> RPCRequestResponse:
var r := RPCRequestResponse.new(request)
r.for_request = request.id
r.scope = name
r.kept = request.keep
r.peer_id = request.peer_id
r.data = {
"success": true
}
return r
func create_generic_failure(request: RPCRequest) -> RPCRequestResponse:
var r := RPCRequestResponse.new(request)
r.for_request = request.id
r.scope = name
r.kept = request.keep
r.peer_id = request.peer_id
r.data = {
"success": false
}
return r
func create_event(type: String, data: Variant, condition := {}) -> RPCEvent:
return RPCEvent.new(type, name, data, condition)
func reconnect(p_signal: Signal, connect_to: Callable, to_call: Callable) -> void:
p_signal.disconnect(connect_to)
to_call.call()
p_signal.connect(connect_to)

View file

@ -1,88 +0,0 @@
# (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)
class_name RPCSignalLayer
static var signals := Signals.new()
#region deck holder
static func _on_deck_added(deck_id: String) -> void:
signals.deck_added.emit(deck_id)
static func _on_deck_closed(deck_id: String) -> void:
signals.deck_closed.emit(deck_id)
#endregion
#region deck
static func _on_deck_node_added(node: DeckNode) -> void:
signals.deck_node_added.emit(node._belonging_to.id, node._id)
static func _on_deck_node_removed(node: DeckNode) -> void:
signals.deck_node_removed.emit(node._belonging_to.id, node._id)
static func _on_deck_nodes_connected(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int, deck_id: String) -> void:
signals.deck_nodes_connected.emit(deck_id, from_node_id, to_node_id, from_output_port, to_input_port)
static func _on_deck_nodes_disconnected(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int, deck_id: String) -> void:
signals.deck_nodes_disconnected.emit(deck_id, from_node_id, to_node_id, from_output_port, to_input_port)
static func _on_deck_variables_updated(deck_id: String) -> void:
signals.deck_variables_updated.emit(deck_id)
#endregion
#region node
static func _on_node_position_updated(new_position: Dictionary, node: DeckNode) -> void:
signals.node_position_updated.emit(node._belonging_to.id, node._id, new_position)
static func _on_node_port_added(port: int, node: DeckNode) -> void:
signals.node_port_added.emit(node._belonging_to.id, node._id, port, node)
static func _on_node_ports_updated(node: DeckNode) -> void:
signals.node_ports_updated.emit(node._belonging_to.id, node._id)
static func _on_node_port_value_updated(port_idx: int, new_value: Variant, node: DeckNode) -> void:
signals.node_port_value_updated.emit(node._belonging_to.id, node._id, port_idx, new_value)
static func _on_node_renamed(new_name: String, node: DeckNode) -> void:
signals.node_renamed.emit(node._belonging_to.id, node._id, new_name)
#endregion
class Signals:
#region system
signal client_connected(client: RPCRenderer.Client)
#endregion
#region deck holder
signal deck_added(deck_id: String)
signal deck_closed(deck_id: String)
#endregion
#region deck
signal deck_node_added(deck_id: String, node_id: String)
signal deck_node_removed(deck_id: String, node_id: String)
signal deck_nodes_connected(deck_id: String, from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int)
signal deck_nodes_disconnected(deck_id: String, from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int)
signal deck_variables_updated(deck_id: String)
#endregion
#region node
signal node_position_updated(deck_id: String, node_id: String, new_position: Dictionary)
signal node_port_added(deck_id: String, node_id: String, port: int)
signal node_ports_updated(deck_id: String, node_id: String)
signal node_port_value_updated(deck_id: String, node_id: String, port_idx: int, new_value: Variant)
signal node_renamed(deck_id: String, node_id: String, new_name: String)
#endregion

View file

@ -1,255 +0,0 @@
# (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 RPCScope
func _init() -> void:
name = "deck"
operation_types = {
"add_node": {"callable": add_node, "event_name": "node_added"},
"remove_node": {"callable": remove_node, "event_name": "node_removed"},
"get_node": {"callable": get_node, "event_name": ""},
"is_valid_connection": {"callable": is_valid_connection, "event_name": ""},
"connect_nodes": {"callable": connect_nodes, "event_name": "nodes_connected"},
"disconnect_nodes": {"callable": disconnect_nodes, "event_name": "nodes_disconnected"},
"group_nodes": {"callable": group_nodes, "event_name": "nodes_grouped"},
"set_variable": {"callable": set_variable, "event_name": "variables_updated"},
"get_variable": {"callable": get_variable, "event_name": ""},
"get_variable_list": {"callable": get_variable_list, "event_name": ""},
"send_event": {"callable": send_event, "event_name": ""},
"get_referenced_groups": {"callable": get_referenced_groups, "event_name": ""},
}
RPCSignalLayer.signals.deck_node_added.connect(_on_deck_node_added)
RPCSignalLayer.signals.deck_node_removed.connect(_on_deck_node_removed)
RPCSignalLayer.signals.deck_nodes_connected.connect(_on_deck_nodes_connected)
RPCSignalLayer.signals.deck_nodes_disconnected.connect(_on_deck_nodes_disconnected)
RPCSignalLayer.signals.deck_variables_updated.connect(_on_deck_variables_updated)
func add_node(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_node_added,
_on_deck_node_added,
func():
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var node := deck.add_node_type(r.operation.payload.node_type)
var node_partial := RPCNodePartial.new()
node_partial.deck_id = deck.id
node_partial.id = node._id
var resp := create_response(r, node_partial)
resp.create_event_counterpart(node_partial, operation_types)
response.emit(resp)
)
func remove_node(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_node_removed,
_on_deck_node_removed,
func():
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var node_id: String = r.operation.payload.node_id
deck.remove_node(node_id, true)
var node_partial := RPCNodePartial.new()
node_partial.deck_id = deck.id
node_partial.id = node_id
var resp := create_generic_success(r)
resp.create_event_counterpart(node_partial, operation_types)
response.emit(resp)
)
func get_node(r: RPCRequest) -> void:
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var node := deck.get_node(r.operation.payload.node_id)
var node_data := node.to_dict()
var resp := create_response(r, {"node": node_data})
response.emit(resp)
func is_valid_connection(r: RPCRequest) -> void:
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var from_node_id: String = r.operation.payload.from_node_id
var from_output_port := int(r.operation.payload.from_output_port)
var to_node_id: String = r.operation.payload.to_node_id
var to_input_port := int(r.operation.payload.to_input_port)
var is_valid = deck.is_valid_connection(from_node_id, to_node_id, from_output_port, to_input_port)
var resp := create_response(r, {"valid": is_valid})
response.emit(resp)
func connect_nodes(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_nodes_connected,
_on_deck_nodes_connected,
func():
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var from_node_id: String = r.operation.payload.from_node_id
var from_output_port := int(r.operation.payload.from_output_port)
var to_node_id: String = r.operation.payload.to_node_id
var to_input_port := int(r.operation.payload.to_input_port)
var connected := deck.connect_nodes(from_node_id, to_node_id, from_output_port, to_input_port)
if connected:
var connection := RPCNodeConnection.new()
connection.from_node_id = from_node_id
connection.to_node_id = to_node_id
connection.from_output_port = from_output_port
connection.to_input_port = to_input_port
var resp := create_generic_success(r)
resp.create_event_counterpart(connection, operation_types)
response.emit(resp)
else:
var err := create_generic_failure(r)
response.emit(err)
)
func disconnect_nodes(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_nodes_disconnected,
_on_deck_nodes_disconnected,
func():
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var from_node_id: String = r.operation.payload.from_node_id
var from_output_port := int(r.operation.payload.from_output_port)
var to_node_id: String = r.operation.payload.to_node_id
var to_input_port := int(r.operation.payload.to_input_port)
deck.disconnect_nodes(from_node_id, to_node_id, from_output_port, to_input_port)
var connection := RPCNodeConnection.new()
connection.from_node_id = from_node_id
connection.to_node_id = to_node_id
connection.from_output_port = from_output_port
connection.to_input_port = to_input_port
var resp := create_generic_success(r)
resp.create_event_counterpart(connection, operation_types)
response.emit(resp)
)
func group_nodes(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_node_removed,
_on_deck_node_removed,
func():
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var node_ids: Array = r.operation.payload.nodes
var nodes := node_ids.map(
func(e: String):
return deck.get_node(e)
)
var group := deck.group_nodes(nodes)
if group == null:
var err := create_error(r, "Error grouping")
response.emit(err)
else:
var dp := RPCDeckPartial.new(group)
var resp := create_response(r, dp)
resp.create_event_counterpart(node_ids, operation_types)
response.emit(resp)
)
func set_variable(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_variables_updated,
_on_deck_variables_updated,
func():
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var var_name: String = r.operation.payload.name
var var_value = r.operation.payload.value
deck.set_variable(var_name, var_value)
var resp := create_generic_success(r)
resp.create_event_counterpart({}, operation_types)
response.emit(resp)
)
func get_variable(r: RPCRequest) -> void:
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var var_name: String = r.operation.payload.name
var value = deck.variable_stack.get(var_name)
var resp := create_response(r, {"name": var_name, "value": value})
response.emit(resp)
func get_variable_list(r: RPCRequest) -> void:
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var resp := create_response(r, {"variables": deck.variable_stack.keys()})
response.emit(resp)
func send_event(r: RPCRequest) -> void:
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var event_name: StringName = r.operation.payload.event_name
var event_data: Dictionary = r.operation.payload.get("event_data", {})
deck.send_event(event_name, event_data)
var resp := create_generic_success(r)
response.emit(resp)
func get_referenced_groups(r: RPCRequest) -> void:
var deck := DeckHolder.get_deck(r.operation.condition.deck_id)
var groups := deck.get_referenced_groups()
var resp := create_response(r, {"groups": groups})
response.emit(resp)
func _on_deck_node_added(deck_id: String, node_id: String) -> void:
event.emit(create_event("node_added", {"node_id": node_id}, {"deck_id": deck_id}))
func _on_deck_node_removed(deck_id: String, node_id: String) -> void:
event.emit(create_event("node_removed", {"node_id": node_id}, {"deck_id": deck_id}))
func _on_deck_nodes_connected(deck_id: String, from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void:
var connection := RPCNodeConnection.new()
connection.from_node_id = from_node_id
connection.to_node_id = to_node_id
connection.from_output_port = from_output_port
connection.to_input_port = to_input_port
var ev := create_event("nodes_connected", connection, {"deck_id": deck_id})
event.emit(ev)
func _on_deck_nodes_disconnected(deck_id: String, from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void:
var connection := RPCNodeConnection.new()
connection.from_node_id = from_node_id
connection.to_node_id = to_node_id
connection.from_output_port = from_output_port
connection.to_input_port = to_input_port
var ev := create_event("nodes_disconnected", connection, {"deck_id": deck_id})
event.emit(ev)
func _on_deck_variables_updated(deck_id: String) -> void:
var ev := create_event("variables_updated", {}, {"deck_id": deck_id})
event.emit(ev)

View file

@ -1,72 +0,0 @@
# (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 RPCScope
func _init() -> void:
name = "deck_holder"
operation_types = {
#"new_deck": new_deck, # TODO: evaluate later
"get_deck": {"callable": get_deck, "event_name": ""},
"send_event": {"callable": send_event, "event_name": ""},
}
RPCSignalLayer.signals.deck_added.connect(_on_deck_holder_deck_added)
RPCSignalLayer.signals.deck_closed.connect(_on_deck_holder_deck_closed)
func new_deck(r: RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.deck_added,
_on_deck_holder_deck_added,
func():
var deck_partial := RPCDeckPartial.new()
deck_partial.id = DeckHolder.add_empty_deck().id
var resp := create_response(r, deck_partial)
resp.create_event_counterpart(deck_partial, operation_types)
response.emit(resp)
)
func get_deck(r: RPCRequest) -> void:
if not r.operation.condition.has("id"):
response.emit(create_error(r, "Invalid Condition"))
return
var deck := DeckHolder.get_deck(r.operation.condition.id)
if deck == null:
response.emit(create_error(r, "Deck doesn't exist"))
return
response.emit(create_response(r, deck.to_dict()))
func send_event(r: RPCRequest) -> void:
if not r.operation.payload.has("event_name"):
response.emit(create_error(r, "Event name must be present and not empty."))
return
var event_data: Dictionary
if not r.operation.payload.has("event_data"):
event_data = {}
elif not r.operation.payload.event_data is Dictionary:
#response.emit(create_error(r, "Event data must be an Object"))
var err := RPCError.new("Event data must be an Object")
response.emit(create_response(r, {}, err))
return
else:
event_data = r.operation.payload.event_data
DeckHolder.send_event(r.operation.payload.event_name, event_data)
response.emit(create_generic_success(r))
func _on_deck_holder_deck_added(deck_id: String) -> void:
event.emit(create_event("new_deck", RPCDeckPartial.new(DeckHolder.get_deck(deck_id))))
func _on_deck_holder_deck_closed(deck_id: String) -> void:
event.emit(create_event("deck_closed", RPCDeckPartial.new(DeckHolder.get_deck(deck_id))))

View file

@ -1,267 +0,0 @@
# (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 RPCScope
func _init() -> void:
name = "node"
operation_types = {
"send" : {"callable" : send, "event_name" : ""},
"press_button" : {"callable" : press_button, "event_name" : ""},
"get_input_ports" : {"callable" : get_input_ports, "event_name" : ""},
"get_output_ports" : {"callable" : get_output_ports, "event_name" : ""},
"get_virtual_ports" : {"callable" : get_virtual_ports, "event_name" : ""},
"get_all_ports" : {"callable" : get_all_ports, "event_name" : ""},
"get_port_value" : {"callable" : get_port_value, "event_name" : ""},
"set_port_value" : {"callable" : set_port_value, "event_name" : "node_port_value_updated"},
"get_position" : {"callable" : get_position, "event_name" : ""},
"set_position" : {"callable" : set_position, "event_name" : "node_position_updated"},
"rename" : {"callable" : rename, "event_name" : "node_renamed"},
"get_group_id" : {"callable" : get_group_id, "event_name" : ""},
}
RPCSignalLayer.signals.node_position_updated.connect(_on_node_position_updated)
RPCSignalLayer.signals.node_renamed.connect(_on_node_renamed)
RPCSignalLayer.signals.node_port_value_updated.connect(_on_node_port_value_updated)
# Get Node workflow = Get Deck from ID > Get Node in Deck from ID
# Condition -> Target
# Payload -> Data
func send(req : RPCRequest) -> void:
# Condition
# deck_id, id, output_port
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
node.send(req.operation.condition.output_port, req.operation.payload.data)
var resp := create_generic_success(req)
response.emit(resp)
func press_button(req : RPCRequest) -> void:
# Condition
# deck_id, id, global_port
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
node.press_button(req.operation.condition.global_port)
var resp := create_generic_success(req)
response.emit(resp)
#region Port Getters
func get_input_ports(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var ports : Array[RPCPort]
for all in node.get_input_ports():
ports.append(RPCPort.new(all))
var resp = create_response(req, ports)
response.emit(resp)
func get_output_ports(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var ports : Array[RPCPort]
for all in node.get_output_ports():
ports.append(RPCPort.new(all))
var resp = create_response(req, ports)
response.emit(resp)
func get_virtual_ports(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var ports : Array[RPCPort]
for all in node.get_virtual_ports():
ports.append(RPCPort.new(all))
var resp = create_response(req, ports)
response.emit(resp)
func get_all_ports(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var ports : Array[RPCPort]
for all in node.get_all_ports():
ports.append(RPCPort.new(all))
var resp = create_response(req, ports)
response.emit(resp)
#endregion
#region Port Values
func get_port_value(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var value = node.get_all_ports()[req.operation.condition.global_port].value
var resp = create_response(req, value)
response.emit(resp)
func set_port_value(req : RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.node_port_value_updated,
_on_node_port_value_updated,
func():
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var port = node.get_all_ports()[req.operation.condition.global_port]
# Doesn't show in the Renderer
port.set_value(req.operation.payload.port_value)
var resp = create_generic_success(req)
resp.create_event_counterpart({"new_value" : req.operation.payload.port_value}, operation_types)
response.emit(resp)
)
#endregion
func get_position(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var resp = create_response(req, node.position)
response.emit(resp)
func set_position(req : RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.node_position_updated,
_on_node_position_updated,
func():
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
node.position = req.operation.payload.position
var resp := create_generic_success(req)
resp.create_event_counterpart({"new_position" : req.operation.payload.position}, operation_types)
response.emit(resp)
)
func rename(req : RPCRequest) -> void:
reconnect(
RPCSignalLayer.signals.node_renamed,
_on_node_renamed,
func():
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
node.name = req.operation.payload.name
var resp := create_generic_success(req)
resp.create_event_counterpart({"new_name" :req.operation.payload.name}, operation_types)
response.emit(resp)
)
# Note, Check if Node IS a group node.
func get_group_id(req : RPCRequest) -> void:
var node := _get_deck_node(
req.operation.condition.deck_id,
req.operation.condition.id
)
var group_id = node.group_id
var resp := create_response(req, group_id)
response.emit(resp)
func _on_node_position_updated(deck_id: String, node_id: String, position: Dictionary) -> void:
event.emit(create_event("position_updated", position, {"deck_id": deck_id, "id": node_id}))
func _on_node_renamed(deck_id: String, node_id: String, new_name: String) -> void:
event.emit(create_event("renamed", {"new_name": new_name}, {"deck_id": deck_id, "id": node_id}))
func _on_node_port_value_updated(deck_id: String, node_id: String, port_idx: int, new_value: Variant) -> void:
event.emit(create_event(
"port_value_updated",
{
"port": port_idx,
"new_value": new_value,
},
{
"deck_id": deck_id,
"id": node_id,
}
)
)
#region Utility Functions
## Utility Functions for getting a Deck from a node_id and a deck_id.
func _get_deck_node(deck_id, node_id) -> DeckNode:
var deck := DeckHolder.get_deck(deck_id)
var node := deck.get_node(node_id)
return node
#endregion

View file

@ -1,77 +0,0 @@
# (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 RPCScope
class_name RPCScopeSystem
func _init() -> void:
name = "system"
operation_types = {
"identify": {"callable": identify, "event_name": ""},
"add_subscription": {"callable": add_subscription, "event_name": ""},
"get_subscriptions": {"callable": get_subscriptions, "event_name": ""},
"remove_subscriptions": {"callable": remove_subscriptions, "event_name": ""},
}
RPCSignalLayer.signals.client_connected.connect(_on_client_connected)
func identify(r: RPCRequest) -> void:
var subscriptions: Dictionary = r.operation.payload.get("subscriptions", {})
if subscriptions.is_empty():
subscriptions = {
"deck": [
"node_added", "node_removed", "nodes_connected", "nodes_disconnected",
"nodes_grouped", "variables_updated"
],
"deck_holder": [
"new_deck", "deck_closed"
]
}
r.client.subscriptions = subscriptions
r.client.identified = true
var resp := create_response(r, {"identified": true})
response.emit(resp)
func add_subscription(r: RPCRequest) -> void:
var new_subscriptions: Dictionary = r.operation.payload.subscriptions
for subscription_scope: String in new_subscriptions:
var event_names: Array = new_subscriptions[subscription_scope]
for event: String in event_names:
if event not in r.client.subscriptions[subscription_scope]:
(r.client.subscriptions[subscription_scope] as Array).append(event)
response.emit(create_generic_success(r))
func get_subscriptions(r: RPCRequest) -> void:
var resp := create_response(r, {"subscriptions": r.client.subscriptions})
response.emit(resp)
func remove_subscriptions(r: RPCRequest) -> void:
var to_remove: Dictionary = r.operation.payload.subscriptions
var scopes_to_remove: Array[String]
for subscription_scope: String in to_remove:
if subscription_scope not in r.client.subscriptions:
continue
var event_names: Array = to_remove[subscription_scope]
for event: String in event_names:
(r.client.subscriptions[subscription_scope] as Array).erase(event)
if (r.client.subscriptions[subscription_scope] as Array).is_empty():
scopes_to_remove.append(subscription_scope)
for scope in scopes_to_remove:
r.client.subscriptions.erase(scope)
response.emit(create_generic_success(r))
func _on_client_connected(client: RPCRenderer.Client) -> void:
var ev := create_event("identify", {})
ev.to_peer = client.id
event.emit(ev)

View file

@ -1,20 +0,0 @@
# (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 RPCFrame
class_name RPCNodeConnection
var from_node_id: String
var from_output_port: int
var to_node_id: String
var to_input_port: int
func _init() -> void:
frame_name = "node_connection"
_props = [
&"from_node_id",
&"from_output_port",
&"to_node_id",
&"to_input_port",
]

View file

@ -1,17 +0,0 @@
# (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 RPCFrame
class_name RPCDeckPartial
var id: String
var is_group: bool
func _init(from: Deck = null) -> void:
frame_name = "deck_partial"
_props = [&"id"]
if from:
id = from.id
is_group = from.is_group

View file

@ -1,17 +0,0 @@
# (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 RPCFrame
class_name RPCNodePartial
var id: String
var deck_id: String
func _init(from: DeckNode = null) -> void:
frame_name = "node_partial"
_props = [&"id", &"deck_id"]
if from:
id = from._id
deck_id = from._belonging_to.id

View file

@ -1,35 +0,0 @@
# (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 RPCFrame
class_name RPCPort
var type: DeckType.Types
var label: String
var index: int
var port_type: DeckNode.PortType
var index_of_type: int
var descriptor: String
var usage_type: Port.UsageType
func _init(port : Port):
frame_name = "port"
_props = [
&"type",
&"label",
&"descriptor",
&"port_type",
&"index_of_type",
&"index",
&"usage_type"
]
type = port.type
label = port.label
descriptor = port.descriptor
port_type = port.port_type
index_of_type = port.index_of_type
index = port.index
usage_type = port.usage_type

View file

@ -1,15 +0,0 @@
# (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 RPCFrame
class_name RPCError
## Generic RPC error type.
var text: String
func _init(p_text: String) -> void:
frame_name = "error"
_props = [&"text"]
text = p_text

View file

@ -1,37 +0,0 @@
# (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 RPCFrame
class_name RPCEvent
## RPC Event type.
##
## Events are sent when something happens that wasn't initiated by a client.
var id: String = UUID.v4()
var type: String
var scope: String
var data: Variant
var condition: Dictionary
var to_peer: int = 0
func _init(
p_type: String,
p_scope: String,
p_data: Variant,
p_condition: Dictionary = {},
) -> void:
frame_name = "event"
_props = [
&"id",
&"type",
&"scope",
&"data",
&"condition",
]
type = p_type
scope = p_scope
data = p_data
condition = p_condition

View file

@ -1,41 +0,0 @@
# (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 RPCFrame
class_name RPCOperation
## RPC Operation type.
##
## An operation is a part of a request from a remote client. It describes a request based on the type (what to do), the condition (what to do it to) and the payload (what data to do it with).
var type: String
## A condition is an object usually describing which object the operation will apply to.
var condition: Dictionary
## The payload is additional data that may be required to perform an action.
var payload: Dictionary
func _init(p_type: String, p_condition: Dictionary = {}, p_payload: Dictionary = {}) -> void:
frame_name = "operation"
_props = [&"type", &"condition", &"payload"]
type = p_type
condition = p_condition
payload = p_payload
static func from_dict(d: Dictionary) -> RPCOperation:
var r := RPCOperation.new(
d.type,
d.get("condition", {}),
d.get("payload", {}),
)
return r
static func schema() -> Zodot:
return Z.schema({
"type": Z.string(),
"condition": Z.dictionary().nullable(),
"payload": Z.dictionary().nullable()
})

View file

@ -1,36 +0,0 @@
# (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)
class_name RPCRequest
## RPC Request type.
##
## A request is initiated by a remote client that tells the program it wants to do something.
## Provided by the client.
var id: String
## Broadly, which part of the program should handle this request. See [RPCScope].
var scope: String
## The action that the client wants to perform.
var operation: RPCOperation
## Any other data that will be kept and returned back to the client when a response is sent. Optional.
var keep: Dictionary
## Which peer initiated this request.
var peer_id: int
var client: RPCRenderer.Client
static func from_dict(d: Dictionary, p_client: RPCRenderer.Client) -> RPCRequest:
if not d.has("request"):
return null
var r := RPCRequest.new()
r.id = d.request.id
r.scope = d.request.scope
r.operation = RPCOperation.from_dict(d.request.operation)
r.keep = d.request.get("keep", {})
r.client = p_client
r.peer_id = p_client.id
return r

View file

@ -1,44 +0,0 @@
# (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 RPCFrame
class_name RPCRequestResponse
## RPC Request Response type.
##
## A response to a [RPCRequest]. Initiated by the server as a response. Does not inherently indicate a successful operation.
var id: String = UUID.v4()
## The ID of the request that prompted this response. See [member RPCRequest.id].
var for_request: String
## Broadly, which part of the program this response comes from. See [RPCScope]. The scope will be the same as the [RPCRequest] this is a response for.
var scope: String
## If non-[code]null[/code], the error that this request represents.
var errors: Array[RPCError]
## Additional data associated with the response. Mandatory, but can be empty.
var data: Variant
## See [member RPCRequest.keep].
var kept: Dictionary
## The peer ID this response is intended for. See [member RPCRequest.peer_id].
var peer_id: int
var request: RPCRequest
var event_counterpart: RPCEvent
func _init(p_request: RPCRequest) -> void:
request = p_request
frame_name = "request_response"
_props = [
&"id",
&"for_request",
&"scope",
&"errors",
&"data",
&"kept",
]
func create_event_counterpart(data: Variant, operation_types: Dictionary) -> void:
var event_name: String = (operation_types[request.operation.type] as Dictionary).get("event_name", request.operation.type)
event_counterpart = RPCEvent.new(event_name, scope, data, request.operation.condition)

View file

@ -1,171 +0,0 @@
#Copyright (c) 2014-present Godot Engine contributors.
#Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
#
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extends Node
class_name WebSocketServer
signal message_received(peer_id: int, message)
signal client_connected(peer_id: int)
signal client_disconnected(peer_id: int)
@export var handshake_headers := PackedStringArray()
@export var supported_protocols: PackedStringArray
@export var handshake_timout := 3000
@export var use_tls := false
@export var tls_cert: X509Certificate
@export var tls_key: CryptoKey
@export var refuse_new_connections := false:
set(refuse):
if refuse:
pending_peers.clear()
class PendingPeer:
var connect_time: int
var tcp: StreamPeerTCP
var connection: StreamPeer
var ws: WebSocketPeer
func _init(p_tcp: StreamPeerTCP):
tcp = p_tcp
connection = p_tcp
connect_time = Time.get_ticks_msec()
var tcp_server := TCPServer.new()
var pending_peers: Array[PendingPeer] = []
var peers: Dictionary
func listen(port: int) -> int:
assert(not tcp_server.is_listening())
return tcp_server.listen(port)
func stop():
tcp_server.stop()
pending_peers.clear()
peers.clear()
func send(peer_id, message) -> int:
var type = typeof(message)
if peer_id <= 0:
# Send to multiple peers, (zero = brodcast, negative = exclude one)
for id in peers:
if id == -peer_id:
continue
if type == TYPE_STRING:
peers[id].send_text(message)
else:
peers[id].put_packet(message)
return OK
assert(peers.has(peer_id))
var socket = peers[peer_id]
if type == TYPE_STRING:
return socket.send_text(message)
return socket.send(var_to_bytes(message))
func get_message(peer_id) -> Variant:
assert(peers.has(peer_id))
var socket = peers[peer_id]
if socket.get_available_packet_count() < 1:
return null
var pkt = socket.get_packet()
if socket.was_string_packet():
return pkt.get_string_from_utf8()
return bytes_to_var(pkt)
func has_message(peer_id) -> bool:
assert(peers.has(peer_id))
return peers[peer_id].get_available_packet_count() > 0
func _create_peer() -> WebSocketPeer:
var ws = WebSocketPeer.new()
ws.supported_protocols = supported_protocols
ws.handshake_headers = handshake_headers
return ws
func poll() -> void:
if not tcp_server.is_listening():
return
while not refuse_new_connections and tcp_server.is_connection_available():
var conn = tcp_server.take_connection()
assert(conn != null)
pending_peers.append(PendingPeer.new(conn))
var to_remove := []
for p in pending_peers:
if not _connect_pending(p):
if p.connect_time + handshake_timout < Time.get_ticks_msec():
# Timeout
to_remove.append(p)
continue # Still pending
to_remove.append(p)
for r in to_remove:
pending_peers.erase(r)
to_remove.clear()
for id in peers:
var p: WebSocketPeer = peers[id]
var packets = p.get_available_packet_count()
p.poll()
if p.get_ready_state() != WebSocketPeer.STATE_OPEN:
client_disconnected.emit(id)
to_remove.append(id)
continue
while p.get_available_packet_count():
message_received.emit(id, get_message(id))
for r in to_remove:
peers.erase(r)
to_remove.clear()
func _connect_pending(p: PendingPeer) -> bool:
if p.ws != null:
# Poll websocket client if doing handshake
p.ws.poll()
var state = p.ws.get_ready_state()
if state == WebSocketPeer.STATE_OPEN:
var id = randi_range(2, 1 << 30)
peers[id] = p.ws
client_connected.emit(id)
return true # Success.
elif state != WebSocketPeer.STATE_CONNECTING:
return true # Failure.
return false # Still connecting.
elif p.tcp.get_status() != StreamPeerTCP.STATUS_CONNECTED:
return true # TCP disconnected.
elif not use_tls:
# TCP is ready, create WS peer
p.ws = _create_peer()
p.ws.accept_stream(p.tcp)
return false # WebSocketPeer connection is pending.
else:
if p.connection == p.tcp:
assert(tls_key != null and tls_cert != null)
var tls = StreamPeerTLS.new()
tls.accept_stream(p.tcp, TLSOptions.server(tls_key, tls_cert))
p.connection = tls
p.connection.poll()
var status = p.connection.get_status()
if status == StreamPeerTLS.STATUS_CONNECTED:
p.ws = _create_peer()
p.ws.accept_stream(p.connection)
return false # WebSocketPeer connection is pending.
if status != StreamPeerTLS.STATUS_HANDSHAKING:
return true # Failure.
return false
func _process(delta):
poll()