types, ports, nodes, decks, test renderer

This commit is contained in:
Lera Elvoé 2023-06-10 20:13:16 +03:00
parent e686112765
commit 39a18a4087
No known key found for this signature in database
14 changed files with 558 additions and 0 deletions

88
classes/UUID.gd Normal file
View file

@ -0,0 +1,88 @@
# MIT License
#
# Copyright (c) 2023 Xavier Sellier
#
# 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.
# Note: The code might not be as pretty it could be, since it's written
# in a way that maximizes performance. Methods are inlined and loops are avoided.
class_name UUID
const BYTE_MASK: int = 0b11111111
static func uuidbin() -> Array:
randomize()
# 16 random bytes with the bytes on index 6 and 8 modified
return [
randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
randi() & BYTE_MASK, randi() & BYTE_MASK, ((randi() & BYTE_MASK) & 0x0f) | 0x40, randi() & BYTE_MASK,
((randi() & BYTE_MASK) & 0x3f) | 0x80, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
]
static func uuidbinrng(rng: RandomNumberGenerator) -> Array:
rng.randomize()
return [
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, rng.randi() & BYTE_MASK,
((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
]
static func v4() -> String:
# 16 random bytes with the bytes on index 6 and 8 modified
var b = uuidbin()
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
# low
b[0], b[1], b[2], b[3],
# mid
b[4], b[5],
# hi
b[6], b[7],
# clock
b[8], b[9],
# clock
b[10], b[11], b[12], b[13], b[14], b[15]
]
static func v4_rng(rng: RandomNumberGenerator) -> String:
# 16 random bytes with the bytes on index 6 and 8 modified
var b = uuidbinrng(rng)
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
# low
b[0], b[1], b[2], b[3],
# mid
b[4], b[5],
# hi
b[6], b[7],
# clock
b[8], b[9],
# clock
b[10], b[11], b[12], b[13], b[14], b[15]
]

57
classes/deck/deck.gd Normal file
View file

@ -0,0 +1,57 @@
class_name Deck
var nodes: Dictionary
enum Types{
ERROR = -1,
BOOL,
NUMERIC,
STRING,
ARRAY,
DICTIONARY,
}
static var type_assoc: Dictionary = {
Types.ERROR: DeckType.DeckTypeError,
Types.BOOL: DeckType.DeckTypeBool,
Types.NUMERIC: DeckType.DeckTypeNumeric,
Types.STRING: DeckType.DeckTypeString,
Types.ARRAY: DeckType.DeckTypeArray,
Types.DICTIONARY: DeckType.DeckTypeDictionary,
}
func add_node(node: GDScript, meta: Dictionary = {}) -> DeckNode:
# TODO: accept instances of DeckNode instead of instancing here?
var uuid := UUID.v4()
var node_inst: DeckNode = node.new() as DeckNode
nodes[uuid] = node_inst
node_inst._belonging_to = self
node_inst._id = uuid
if !meta.is_empty():
for k in meta:
node_inst.set_meta(k, meta[k])
return node_inst
func get_node(uuid: String) -> DeckNode:
return nodes.get(uuid)
func connect_nodes(from_node: DeckNode, to_node: DeckNode, from_port: int, to_port: int) -> bool:
# first, check that we can do the type conversion.
var type_a: Types = from_node.input_ports[from_port].type
var type_b: Types = to_node.output_ports[to_port].type
var err: DeckType = (type_assoc[type_b] as GDScript).from(type_assoc[type_a])
if err is DeckType.DeckTypeError:
print(err.error_message)
return false
# TODO: prevent duplicate connections
from_node.add_outgoing_connection(from_port, to_node._id, to_port)
return true

41
classes/deck/deck_node.gd Normal file
View file

@ -0,0 +1,41 @@
class_name DeckNode
var name: String
var input_ports: Array[Port]
var output_ports: Array[Port]
var outgoing_connections: Dictionary
var _belonging_to: Deck
var _id: String
func add_input_port(type: Deck.Types, label: String, descriptor: String = "") -> void:
var port := Port.new(type, label, descriptor)
input_ports.append(port)
func add_output_port(type: Deck.Types, label: String, descriptor: String = "") -> void:
var port := Port.new(type, label, descriptor)
output_ports.append(port)
func send(from_port: int, data: DeckType, extra_data: Array = []) -> void:
if !(outgoing_connections.get(from_port)):
return
for connection in outgoing_connections[from_port]:
connection = connection as Dictionary
# key is node uuid
# value is input port on destination node
for node in connection:
_belonging_to.get_node(node)._receive(connection[node], data, extra_data)
func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void:
pass
func add_outgoing_connection(from_port: int, to_node: String, to_port: int) -> void:
var port_connections: Array = outgoing_connections.get(from_port, [])
port_connections.append({to_node: to_port})
outgoing_connections[from_port] = port_connections

View file

@ -0,0 +1,11 @@
extends DeckNode
func _init() -> void:
add_output_port(
Deck.Types.STRING,
"Press me",
"button"
)
name = "Button"

View file

@ -0,0 +1,18 @@
extends DeckNode
func _init() -> void:
add_input_port(
Deck.Types.STRING,
"Input",
"field"
)
name = "Print"
func _receive(_to_port: int, data: DeckType, extra_data: Array = []) -> void:
# we only have one port, so we can skip checking which port we received on
var data_to_print = data.get_value()
print(data_to_print)
print("extra data: ", extra_data)

11
classes/deck/port.gd Normal file
View file

@ -0,0 +1,11 @@
class_name Port
var type: Deck.Types
var label: String
var descriptor: String
func _init(p_type: Deck.Types, p_label: String, p_descriptor: String = "") -> void:
type = p_type
label = p_label
descriptor = p_descriptor

106
classes/types/deck_type.gd Normal file
View file

@ -0,0 +1,106 @@
class_name DeckType
var _value: Variant
var _success: bool = true
func is_valid() -> bool:
return _success
func get_value() -> Variant:
return _value
static func from(other: DeckType):
return null
class DeckTypeError extends DeckType:
var error_message: String
func _init(p_error_message: String = ""):
_value = self
_success = false
error_message = p_error_message
static func from(other: DeckType) -> DeckTypeError:
return DeckTypeError.new()
class DeckTypeNumeric extends DeckType:
static func from(other: DeckType):
if other is DeckTypeNumeric:
return other
if other is DeckTypeString:
if (other.get_value() as String).is_valid_float():
var value: float = float(other.get_value() as String)
var inst := DeckTypeNumeric.new()
inst._value = value
return inst
else:
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "Conversion from String to Numeric failed, check the number"
return err
if other is DeckTypeBool:
var inst := DeckTypeNumeric.new()
inst._value = float(other.get_value() as bool)
return inst
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "Conversion to Numeric is only possible from String or Bool."
return err
class DeckTypeString extends DeckType:
static func from(other: DeckType):
if other is DeckTypeString:
return other
var inst := DeckTypeString.new()
inst._value = var_to_str(other.get_value())
class DeckTypeBool extends DeckType:
static func from(other: DeckType):
if other is DeckTypeBool:
return other
if other is DeckTypeNumeric:
var inst := DeckTypeBool.new()
inst._value = bool(other.get_value())
return inst
if other is DeckTypeDictionary or other is DeckTypeArray:
var inst := DeckTypeBool.new()
inst._value = !other.get_value().is_empty()
return inst
class DeckTypeArray extends DeckType:
static func from(other: DeckType):
if other is DeckTypeString:
var inst := DeckTypeArray.new()
inst._value = str_to_var(other.get_value())
return inst
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "conversions to Array is only possible from String"
return err
class DeckTypeDictionary extends DeckType:
static func from(other: DeckType):
if other is DeckTypeString:
var inst := DeckTypeDictionary.new()
inst._value = str_to_var(other.get_value())
return inst
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "conversions to Dictionary is only possible from String"
return err

53
port_drawer.gd Normal file
View file

@ -0,0 +1,53 @@
extends HBoxContainer
@onready var left_slot: ColorRect = $LeftSlot
@onready var right_slot: ColorRect = $RightSlot
var text: String
signal button_pressed
func set_input_enabled(enabled: bool) -> void:
left_slot.visible = enabled
func set_output_enabled(enabled: bool) -> void:
right_slot.visible = enabled
func add_label(text: String) -> void:
var l := Label.new()
add_child(l)
l.text = text
move_child(l, 1)
l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
func add_field() -> void:
var le := LineEdit.new()
add_child(le)
move_child(le, 1)
le.size_flags_horizontal = Control.SIZE_EXPAND_FILL
le.text_changed.connect(
func(new_text: String):
text = new_text
)
func get_text() -> String:
return text
func add_button(text: String) -> void:
var b := Button.new()
b.text = text
add_child(b)
move_child(b, 1)
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
b.pressed.connect(
func():
button_pressed.emit()
)

16
port_drawer.tscn Normal file
View file

@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3 uid="uid://ddqtmahfxel26"]
[ext_resource type="Script" path="res://port_drawer.gd" id="1_wot5w"]
[node name="PortDrawer" type="HBoxContainer"]
script = ExtResource("1_wot5w")
[node name="LeftSlot" type="ColorRect" parent="."]
visible = false
custom_minimum_size = Vector2(12, 12)
layout_mode = 2
[node name="RightSlot" type="ColorRect" parent="."]
visible = false
custom_minimum_size = Vector2(12, 12)
layout_mode = 2

View file

@ -11,5 +11,7 @@ config_version=5
[application] [application]
config/name="Re-DotDeck" config/name="Re-DotDeck"
config/tags=PackedStringArray("dot_deck")
run/main_scene="res://test.tscn"
config/features=PackedStringArray("4.1", "Forward Plus") config/features=PackedStringArray("4.1", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"

32
test.gd Normal file
View file

@ -0,0 +1,32 @@
extends Control
var node_renderer_scene := preload("res://test_node_renderer.tscn")
@onready var nodes_container: HBoxContainer = $NodesContainer
@onready var add_button_button: Button = $AddButtonButton
@onready var add_print_button: Button = $AddPrintButton
@onready var connect_them_button: Button = $ConnectThemButton
var deck: Deck = Deck.new()
var button_node = preload("res://classes/deck/nodes/button.gd")
var print_node = preload("res://classes/deck/nodes/print.gd")
func _ready() -> void:
add_button_button.pressed.connect(
func():
var node := deck.add_node(button_node)
var node_renderer = node_renderer_scene.instantiate()
node_renderer.node = node
nodes_container.add_child(node_renderer)
add_button_button.disabled = true
)
add_print_button.pressed.connect(
func():
var node := deck.add_node(print_node)
var node_renderer = node_renderer_scene.instantiate()
node_renderer.node = node
nodes_container.add_child(node_renderer)
add_print_button.disabled = true
)

46
test.tscn Normal file
View file

@ -0,0 +1,46 @@
[gd_scene load_steps=2 format=3 uid="uid://bhpd6rfiuimw5"]
[ext_resource type="Script" path="res://test.gd" id="1_in4g7"]
[node name="Test" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_in4g7")
[node name="AddButtonButton" type="Button" parent="."]
layout_mode = 0
offset_left = 26.0
offset_top = 576.0
offset_right = 119.0
offset_bottom = 607.0
text = "Add Button"
[node name="AddPrintButton" type="Button" parent="."]
layout_mode = 0
offset_left = 152.0
offset_top = 576.0
offset_right = 248.0
offset_bottom = 607.0
text = "Add Print"
[node name="ConnectThemButton" type="Button" parent="."]
layout_mode = 0
offset_left = 283.0
offset_top = 576.0
offset_right = 379.0
offset_bottom = 607.0
text = "Connect them"
[node name="NodesContainer" type="HBoxContainer" parent="."]
layout_mode = 1
anchors_preset = -1
anchor_right = 0.999646
anchor_bottom = 0.245654
offset_right = 0.40799
offset_bottom = -0.184006
theme_override_constants/separation = 20
metadata/_edit_use_anchors_ = true

38
test_node_renderer.gd Normal file
View file

@ -0,0 +1,38 @@
extends PanelContainer
@onready var name_label: Label = %NameLabel
@onready var elements_container: VBoxContainer = %ElementsContainer
var node: DeckNode
const PortDrawer := preload("res://port_drawer.gd")
var port_drawer_scene := preload("res://port_drawer.tscn")
# THIS IS SUPER JANK AND A HACK FOR DEMONSTRATION PURPOSES
# PLEASE DO NOT ACTUALLY DO ANYTHING THIS CLASS DOES
# IN THE REAL PROJECT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
func _ready() -> void:
name_label.text = node.name
for input_port in node.input_ports:
var port_drawer: PortDrawer = port_drawer_scene.instantiate()
elements_container.add_child(port_drawer)
port_drawer.set_input_enabled(true)
match input_port.descriptor:
"field":
port_drawer.add_field()
# "button":
# port_drawer.add_button(input_port.label)
_:
port_drawer.add_label(input_port.label)
for i in node.output_ports.size():
if elements_container.get_child_count() - 1 < i:
var pd: PortDrawer = port_drawer_scene.instantiate()
elements_container.add_child(pd)
var port_drawer: PortDrawer = elements_container.get_child(i)
port_drawer.set_output_enabled(true)
var output_port := node.output_ports[i]
if output_port.descriptor == "button":
port_drawer.add_button(output_port.label)

39
test_node_renderer.tscn Normal file
View file

@ -0,0 +1,39 @@
[gd_scene load_steps=2 format=3 uid="uid://ch8s1d7vobhi4"]
[ext_resource type="Script" path="res://test_node_renderer.gd" id="1_85wy1"]
[node name="TestNodeRenderer" type="PanelContainer"]
custom_minimum_size = Vector2(300, 0)
anchors_preset = -1
anchor_right = 0.26
anchor_bottom = 0.34
offset_right = 0.47998
offset_bottom = -0.320007
script = ExtResource("1_85wy1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="ColorRect" type="ColorRect" parent="VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(4, 0)
layout_mode = 2
color = Color(1, 1, 0.34902, 1)
[node name="NameLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Name"
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="ElementsContainer" type="VBoxContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3