mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
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:
parent
240750c48e
commit
ba2dd6a837
27 changed files with 6 additions and 1685 deletions
|
@ -78,16 +78,6 @@ signal node_moved(node_id: String, new_position: Dictionary, deck: Deck)
|
||||||
#endregion
|
#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]
|
## Instantiate a node by its' [member DeckNode.node_type] and add it to this deck.[br]
|
||||||
## See [method add_node_inst] for parameter descriptions.
|
## See [method add_node_inst] for parameter descriptions.
|
||||||
func add_node_type(type: String, assign_id: String = "", assign_to_self: bool = true) -> DeckNode:
|
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_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()])
|
print_verbose("Deck %s::%s: added node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()])
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
|
@ -23,9 +23,6 @@ enum Compat {
|
||||||
|
|
||||||
|
|
||||||
static func _static_init() -> void:
|
static func _static_init() -> void:
|
||||||
signals.deck_added.connect(RPCSignalLayer._on_deck_added)
|
|
||||||
signals.deck_closed.connect(RPCSignalLayer._on_deck_closed)
|
|
||||||
|
|
||||||
NodeDB.init()
|
NodeDB.init()
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +32,6 @@ static func add_empty_deck() -> Deck:
|
||||||
var uuid := UUID.v4()
|
var uuid := UUID.v4()
|
||||||
decks[uuid] = deck
|
decks[uuid] = deck
|
||||||
deck.id = uuid
|
deck.id = uuid
|
||||||
deck.connect_rpc_signals()
|
|
||||||
signals.deck_added.emit(uuid)
|
signals.deck_added.emit(uuid)
|
||||||
print_verbose("DeckHolder: added empty deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
print_verbose("DeckHolder: added empty deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
||||||
return deck
|
return deck
|
||||||
|
@ -78,7 +74,6 @@ static func open_deck_from_dict(data: Dictionary, path := "") -> Deck:
|
||||||
apply_compat_patches(data, get_deck_compat(data))
|
apply_compat_patches(data, get_deck_compat(data))
|
||||||
var deck := Deck.from_dict(data, path)
|
var deck := Deck.from_dict(data, path)
|
||||||
decks[deck.id] = deck
|
decks[deck.id] = deck
|
||||||
deck.connect_rpc_signals()
|
|
||||||
signals.deck_added.emit(deck.id)
|
signals.deck_added.emit(deck.id)
|
||||||
print_verbose("DeckHolder: opened deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
print_verbose("DeckHolder: opened deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
||||||
return deck
|
return deck
|
||||||
|
@ -97,7 +92,6 @@ static func add_group_from_dict(data: Dictionary, deck_id: String, instance_id:
|
||||||
connect_group_signals(group)
|
connect_group_signals(group)
|
||||||
|
|
||||||
if deck_id not in groups_emitted:
|
if deck_id not in groups_emitted:
|
||||||
group.connect_rpc_signals()
|
|
||||||
signals.deck_added.emit(deck_id)
|
signals.deck_added.emit(deck_id)
|
||||||
groups_emitted.append(deck_id)
|
groups_emitted.append(deck_id)
|
||||||
#print(decks)
|
#print(decks)
|
||||||
|
@ -149,7 +143,6 @@ static func add_empty_group(parent: String = "") -> Deck:
|
||||||
decks[group.id] = {group.instance_id: group}
|
decks[group.id] = {group.instance_id: group}
|
||||||
connect_group_signals(group)
|
connect_group_signals(group)
|
||||||
if group.id not in groups_emitted:
|
if group.id not in groups_emitted:
|
||||||
group.connect_rpc_signals()
|
|
||||||
signals.deck_added.emit(group.id)
|
signals.deck_added.emit(group.id)
|
||||||
groups_emitted.append(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()])
|
print_verbose("DeckHolder: added empty group %s::%s, id %s" % [group.id, group.instance_id, group.get_instance_id()])
|
||||||
|
|
|
@ -74,13 +74,6 @@ signal port_value_updated(port_idx: int, new_value: Variant)
|
||||||
signal renamed(new_name: String)
|
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.
|
## Add an input port to this node. Usually only used at initialization.
|
||||||
func add_input_port(
|
func add_input_port(
|
||||||
type: DeckType.Types,
|
type: DeckType.Types,
|
||||||
|
|
|
@ -41,6 +41,7 @@ enum ConnectionsMenuId {
|
||||||
TWITCH,
|
TWITCH,
|
||||||
RPC,
|
RPC,
|
||||||
}
|
}
|
||||||
|
@onready var connections_popup_menu: PopupMenu = %Connections
|
||||||
|
|
||||||
enum DebugMenuId {
|
enum DebugMenuId {
|
||||||
DECKS,
|
DECKS,
|
||||||
|
@ -71,7 +72,6 @@ var _deck_to_save: WeakRef
|
||||||
|
|
||||||
@onready var obs_setup_dialog := $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog
|
@onready var obs_setup_dialog := $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog
|
||||||
@onready var twitch_setup_dialog := $Twitch_Setup_Dialog as TwitchSetupDialog
|
@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 bottom_dock: BottomDock = %BottomDock
|
||||||
@onready var sidebar_split: HSplitContainer = %SidebarSplit
|
@onready var sidebar_split: HSplitContainer = %SidebarSplit
|
||||||
|
@ -80,8 +80,6 @@ var _deck_to_save: WeakRef
|
||||||
@onready var compat_dialog: ConfirmationDialog = %CompatDialog
|
@onready var compat_dialog: ConfirmationDialog = %CompatDialog
|
||||||
|
|
||||||
signal quit_completed()
|
signal quit_completed()
|
||||||
signal rpc_start_requested(port: int)
|
|
||||||
signal rpc_stop_requested()
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
@ -114,32 +112,6 @@ func _ready() -> void:
|
||||||
|
|
||||||
add_recents_to_menu()
|
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(
|
tab_container.tab_close_requested.connect(
|
||||||
func(tab: int):
|
func(tab: int):
|
||||||
if tab_container.get_tab_metadata(tab, "dirty") and not tab_container.get_tab_metadata(tab, "group"):
|
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))
|
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:
|
func reset_popup_menu_shortcuts() -> void:
|
||||||
file_popup_menu.set_item_shortcut(FileMenuId.NEW, RendererShortcuts.get_shortcut("new_deck"))
|
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()
|
obs_setup_dialog.popup_centered()
|
||||||
ConnectionsMenuId.TWITCH:
|
ConnectionsMenuId.TWITCH:
|
||||||
twitch_setup_dialog.popup_centered()
|
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:
|
func _on_obs_websocket_setup_dialog_connect_button_pressed(state: OBSWebsocketSetupDialog.ConnectionState) -> void:
|
||||||
|
|
|
@ -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="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"]
|
[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://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://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://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://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"]
|
[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_1/id = 1
|
||||||
item_2/text = "RPC Server..."
|
item_2/text = "RPC Server..."
|
||||||
item_2/id = 2
|
item_2/id = 2
|
||||||
|
item_2/disabled = true
|
||||||
|
|
||||||
[node name="Debug" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
[node name="Debug" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||||
unique_name_in_owner = true
|
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="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="UnsavedChangesDialogSingleDeck" parent="." instance=ExtResource("8_qf6ve")]
|
||||||
|
|
||||||
[node name="UnsavedChangesDialog" parent="." instance=ExtResource("9_4n0q6")]
|
[node name="UnsavedChangesDialog" parent="." instance=ExtResource("9_4n0q6")]
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
12
main.gd
|
@ -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: DeckHolderRenderer = null
|
||||||
var deck_holder_renderer_finished := false
|
var deck_holder_renderer_finished := false
|
||||||
|
|
||||||
@onready var rpc_renderer := $RPCRenderer as RPCRenderer
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
get_tree().auto_accept_quit = false
|
get_tree().auto_accept_quit = false
|
||||||
|
@ -19,8 +17,6 @@ func _ready() -> void:
|
||||||
deck_holder_renderer = DEFAULT_RENDERER.instantiate()
|
deck_holder_renderer = DEFAULT_RENDERER.instantiate()
|
||||||
add_child(deck_holder_renderer)
|
add_child(deck_holder_renderer)
|
||||||
deck_holder_renderer.quit_completed.connect(_on_deck_holder_renderer_quit_completed)
|
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:
|
func _on_deck_holder_renderer_quit_completed() -> void:
|
||||||
|
@ -31,14 +27,6 @@ func _on_deck_holder_renderer_quit_completed() -> void:
|
||||||
get_tree().quit()
|
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:
|
func _notification(what: int) -> void:
|
||||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||||
if deck_holder_renderer:
|
if deck_holder_renderer:
|
||||||
|
|
|
@ -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://main.gd" id="1_rxyjw"]
|
||||||
[ext_resource type="Script" path="res://rpc_renderer/rpc_renderer.gd" id="2_m1ypq"]
|
|
||||||
|
|
||||||
[node name="Main" type="Node"]
|
[node name="Main" type="Node"]
|
||||||
script = ExtResource("1_rxyjw")
|
script = ExtResource("1_rxyjw")
|
||||||
|
|
||||||
[node name="RPCRenderer" type="Node" parent="."]
|
|
||||||
script = ExtResource("2_m1ypq")
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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 = {}
|
|
|
@ -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)
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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))))
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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",
|
|
||||||
]
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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()
|
|
||||||
})
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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()
|
|
Loading…
Reference in a new issue