Skip to content

Commit 1cc8b43

Browse files
committed
1.0.0
1 parent 2edd950 commit 1cc8b43

26 files changed

+478
-16
lines changed

.gitattributes

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
# Auto detect text files and perform LF normalization
2-
* text=auto
1+
* text=auto eol=lf

.gitignore

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
# Godot 4+ specific ignores
21
.godot/
3-
4-
# Godot-specific ignores
5-
.import/
6-
export.cfg
7-
export_presets.cfg
8-
9-
# Imported translations (automatically generated from CSV files)
10-
*.translation
11-
12-
# Mono-specific ignores
13-
.mono/
14-
data_*/
15-
mono_crash.*.json
2+
addons/
3+
.editorconfig

caret-right.svg

Lines changed: 1 addition & 0 deletions
Loading

caret-right.svg.import

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://bhop1cdyv78s4"
6+
path="res://.godot/imported/caret-right.svg-fef3695546786de47ae107bddc677593.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://caret-right.svg"
14+
dest_files=["res://.godot/imported/caret-right.svg-fef3695546786de47ae107bddc677593.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/hdr_compression=1
22+
compress/normal_map=0
23+
compress/channel_pack=0
24+
mipmaps/generate=false
25+
mipmaps/limit=-1
26+
roughness/mode=0
27+
roughness/src_normal=""
28+
process/fix_alpha_border=true
29+
process/premult_alpha=false
30+
process/normal_map_invert_y=false
31+
process/hdr_as_srgb=false
32+
process/hdr_clamp_exposure=false
33+
process/size_limit=0
34+
detect_3d/compress_to=1
35+
svg/scale=1.0
36+
editor/scale_with_editor_scale=false
37+
editor/convert_colors_with_editor_theme=false

graph.gd

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
extends GraphEdit
2+
3+
const TYPE_FLOW := 69
4+
const TYPE_ANY := 42
5+
6+
@export var verbose := false
7+
8+
class GraphNodeRepr:
9+
var node: GraphNode
10+
var in_connections: Array[GraphNodeConnection]
11+
var out_connections: Array[GraphNodeConnection]
12+
13+
func _init(_node: GraphNode) -> void:
14+
node = _node
15+
16+
func flow_next() -> GraphNodeRepr:
17+
var conn := out_connections[0] as GraphNodeConnection if !out_connections.is_empty() else null
18+
return conn.to_node if conn else null
19+
20+
func get_input(port_idx: int, default = null) -> Variant:
21+
var conn: GraphNodeConnection
22+
for c in in_connections:
23+
if c.to_port != port_idx: continue
24+
conn = c
25+
26+
if conn == null:
27+
print("No connection for port %d" % port_idx)
28+
return default
29+
30+
var in_node := conn.from_node.node
31+
32+
if in_node is ValueGraphNode:
33+
return in_node.get_value()
34+
elif in_node is ExecutableGraphNode:
35+
push_error("Not implemented")
36+
else:
37+
push_error("Invalid connection")
38+
39+
return default
40+
41+
class GraphNodeConnection:
42+
var from_node: GraphNodeRepr
43+
var from_port: int
44+
var to_node: GraphNodeRepr
45+
var to_port: int
46+
47+
func _init(d: Dictionary, nodes: Dictionary[StringName, GraphNodeRepr]) -> void:
48+
from_node = nodes[d["from_node"]]
49+
from_port = d["from_port"]
50+
to_node = nodes[d["to_node"]]
51+
to_port = d["to_port"]
52+
53+
54+
func execute() -> void:
55+
if verbose: print("Executing...")
56+
var nodes: Dictionary[StringName, GraphNodeRepr]
57+
58+
#region parsing
59+
for node in get_children() as Array[Control]:
60+
if node is not GraphNode: continue
61+
nodes[node.name] = GraphNodeRepr.new(node)
62+
63+
for conn in connections:
64+
var from := nodes[conn["from_node"] as StringName]
65+
var to := nodes[conn["to_node"] as StringName]
66+
var from_conn := GraphNodeConnection.new(conn, nodes)
67+
var to_conn := GraphNodeConnection.new(conn, nodes)
68+
from.out_connections.append(from_conn)
69+
to.in_connections.append(to_conn)
70+
#endregion
71+
72+
#region logic
73+
var ready_node := nodes["ReadyNode"]
74+
if verbose:
75+
print(ready_node.node.name)
76+
print("In: ", ready_node.in_connections)
77+
print("Out: ", ready_node.out_connections)
78+
79+
var next := ready_node.flow_next()
80+
while next:
81+
if verbose:
82+
print("\n - ", next.node.name)
83+
print("In: ", next.in_connections)
84+
print("Out: ", next.out_connections)
85+
86+
var node := next.node as ExecutableGraphNode
87+
var args := []
88+
89+
# resolve inputs
90+
for port_idx in node.get_input_port_count():
91+
var slot_idx := node.get_input_port_slot(port_idx)
92+
var debug := " [Port %d | Slot %d] " % [port_idx, slot_idx]
93+
94+
var port_type := node.get_slot_type_left(slot_idx)
95+
if port_type == TYPE_FLOW:
96+
if verbose:
97+
debug += "Flow (Skipped)"
98+
print(debug)
99+
continue
100+
elif port_type == TYPE_ANY:
101+
debug += "Any"
102+
else:
103+
debug += type_string(port_type)
104+
105+
var value = next.get_input(port_idx)
106+
if verbose: print(debug, " (%s) | " % type_string(typeof(value)), value)
107+
args.append(value)
108+
109+
if verbose: print("Args: ", args)
110+
111+
node.execute(args)
112+
next = next.flow_next()
113+
#endregion
114+
115+
if verbose: print("\nFinished")
116+
117+
118+
func _on_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
119+
var from := get_node(str(from_node)) as GraphNode
120+
var to := get_node(str(to_node)) as GraphNode
121+
122+
if from == to:
123+
print("Cannot connect to self")
124+
return
125+
126+
for conn in connections:
127+
if conn["to_node"] == to_node and conn["to_port"] == to_port:
128+
print("Port already used")
129+
return
130+
131+
# only connect flow outputs to flow inputs for dynamic nodes
132+
var _out := from.get_output_port_type(from_port)
133+
var _in := to.get_input_port_type(to_port)
134+
if _out == TYPE_FLOW || _in == TYPE_FLOW:
135+
if _out != _in:
136+
print("Flow -> Flow connection required")
137+
return
138+
139+
connect_node(from_node, from_port, to_node, to_port)
140+
141+
142+
func _on_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
143+
disconnect_node(from_node, from_port, to_node, to_port)
144+
145+
146+
func _on_delete_nodes_request(nodes: Array[StringName]) -> void:
147+
for node in nodes:
148+
get_node(node as String).queue_free()
149+
150+
for conn in connections:
151+
if conn["from_node"] == node || conn["to_node"] == node:
152+
disconnect_node(conn["from_node"], conn["from_port"], conn["to_node"], conn["to_port"])

graph.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://44j7r8skexf1

main.tscn

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[gd_scene load_steps=5 format=3 uid="uid://qr52aafdvj5l"]
2+
3+
[ext_resource type="Script" uid="uid://c4xjo5mavlg5r" path="res://node_selection.gd" id="1_7mycd"]
4+
[ext_resource type="Script" uid="uid://44j7r8skexf1" path="res://graph.gd" id="1_h2yge"]
5+
[ext_resource type="Script" uid="uid://bgdixhn7vioeb" path="res://run.gd" id="4_1bvp3"]
6+
7+
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7dm0k"]
8+
9+
[node name="Main" type="VBoxContainer"]
10+
anchors_preset = 15
11+
anchor_right = 1.0
12+
anchor_bottom = 1.0
13+
grow_horizontal = 2
14+
grow_vertical = 2
15+
16+
[node name="TopBar" type="HBoxContainer" parent="."]
17+
layout_mode = 2
18+
19+
[node name="NodeSelection" type="ScrollContainer" parent="TopBar" node_paths=PackedStringArray("graph", "buttons")]
20+
layout_mode = 2
21+
size_flags_horizontal = 3
22+
follow_focus = true
23+
horizontal_scroll_mode = 2
24+
vertical_scroll_mode = 0
25+
script = ExtResource("1_7mycd")
26+
graph = NodePath("../../GraphEdit")
27+
buttons = NodePath("ButtonContainer")
28+
29+
[node name="ButtonContainer" type="HBoxContainer" parent="TopBar/NodeSelection"]
30+
layout_mode = 2
31+
size_flags_horizontal = 3
32+
size_flags_vertical = 3
33+
34+
[node name="Run" type="Button" parent="TopBar" node_paths=PackedStringArray("graph")]
35+
custom_minimum_size = Vector2(100, 50)
36+
layout_mode = 2
37+
size_flags_horizontal = 8
38+
size_flags_vertical = 8
39+
text = "Run"
40+
script = ExtResource("4_1bvp3")
41+
graph = NodePath("../../GraphEdit")
42+
43+
[node name="GraphEdit" type="GraphEdit" parent="."]
44+
unique_name_in_owner = true
45+
layout_mode = 2
46+
size_flags_vertical = 3
47+
theme_override_styles/panel = SubResource("StyleBoxEmpty_7dm0k")
48+
grid_pattern = 1
49+
right_disconnects = true
50+
connection_lines_antialiased = false
51+
minimap_enabled = false
52+
show_menu = false
53+
show_zoom_buttons = false
54+
show_grid_buttons = false
55+
show_minimap_button = false
56+
show_arrange_button = false
57+
script = ExtResource("1_h2yge")
58+
verbose = true
59+
60+
[connection signal="connection_request" from="GraphEdit" to="GraphEdit" method="_on_connection_request"]
61+
[connection signal="delete_nodes_request" from="GraphEdit" to="GraphEdit" method="_on_delete_nodes_request"]
62+
[connection signal="disconnection_request" from="GraphEdit" to="GraphEdit" method="_on_disconnection_request"]

node_selection.gd

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
extends Container
2+
3+
const PATH := "res://nodes/"
4+
const NODE_BUTTON := preload("res://ui/node_button.tscn") as PackedScene
5+
6+
@export var graph: GraphEdit
7+
@export var buttons: Container
8+
9+
var nodes: Dictionary[String, PackedScene]
10+
11+
func _enter_tree() -> void:
12+
for file in DirAccess.get_files_at(PATH):
13+
if file.ends_with(".tscn"):
14+
var scn := load(PATH+file) as PackedScene
15+
nodes[file] = scn
16+
var btn := NODE_BUTTON.instantiate() as Button
17+
btn.text = file.get_basename().to_pascal_case()
18+
btn.pressed.connect(_pressed.bind(file))
19+
buttons.add_child(btn)
20+
21+
func _pressed(node: String) -> void:
22+
var scn := nodes[node].instantiate() as GraphNode
23+
scn.position_offset = graph.scroll_offset
24+
graph.add_child(scn)

node_selection.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://c4xjo5mavlg5r

nodes/executable_node.gd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class_name ExecutableGraphNode
2+
extends GraphNode
3+
4+
@warning_ignore("unused_parameter")
5+
func execute(args: Array) -> void:
6+
push_error("Cannot call function on abstract class")

nodes/executable_node.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dcx1bl2m38n6b

nodes/print_node.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class_name GraphNodePrint
2+
extends ExecutableGraphNode
3+
4+
func execute(args: Array) -> void:
5+
prints(self, args[0])

nodes/print_node.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://c6oxvefk7b6as

nodes/print_node.tscn

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[gd_scene load_steps=3 format=3 uid="uid://cd8lgyd1aojs8"]
2+
3+
[ext_resource type="Texture2D" uid="uid://bhop1cdyv78s4" path="res://caret-right.svg" id="1_3ksu2"]
4+
[ext_resource type="Script" uid="uid://c6oxvefk7b6as" path="res://nodes/print_node.gd" id="1_gu88u"]
5+
6+
[node name="PrintNode" type="GraphNode"]
7+
offset_right = 200.0
8+
offset_bottom = 100.0
9+
title = "Print"
10+
ignore_invalid_connection_type = true
11+
slot/0/left_enabled = true
12+
slot/0/left_type = 69
13+
slot/0/left_color = Color(1, 1, 1, 1)
14+
slot/0/left_icon = ExtResource("1_3ksu2")
15+
slot/0/right_enabled = true
16+
slot/0/right_type = 69
17+
slot/0/right_color = Color(1, 1, 1, 1)
18+
slot/0/right_icon = ExtResource("1_3ksu2")
19+
slot/0/draw_stylebox = true
20+
slot/1/left_enabled = true
21+
slot/1/left_type = 42
22+
slot/1/left_color = Color(1, 1, 1, 1)
23+
slot/1/left_icon = null
24+
slot/1/right_enabled = false
25+
slot/1/right_type = 0
26+
slot/1/right_color = Color(1, 1, 1, 1)
27+
slot/1/right_icon = null
28+
slot/1/draw_stylebox = true
29+
script = ExtResource("1_gu88u")
30+
31+
[node name="String" type="Label" parent="."]
32+
layout_mode = 2
33+
34+
[node name="Label" type="Label" parent="."]
35+
layout_mode = 2
36+
text = "Value"

nodes/ready_node.gd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class_name GraphNodeReady
2+
extends GraphNode

nodes/ready_node.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dvwb8u1qywkue

nodes/ready_node.tscn

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[gd_scene load_steps=3 format=3 uid="uid://dwkeqo8f1jixv"]
2+
3+
[ext_resource type="Script" uid="uid://dvwb8u1qywkue" path="res://nodes/ready_node.gd" id="1_7udae"]
4+
[ext_resource type="Texture2D" uid="uid://bhop1cdyv78s4" path="res://caret-right.svg" id="1_dgv1w"]
5+
6+
[node name="ReadyNode" type="GraphNode"]
7+
offset_right = 200.0
8+
offset_bottom = 150.0
9+
resizable = true
10+
title = "Ready"
11+
slot/0/left_enabled = false
12+
slot/0/left_type = 0
13+
slot/0/left_color = Color(1, 1, 1, 1)
14+
slot/0/left_icon = null
15+
slot/0/right_enabled = true
16+
slot/0/right_type = 69
17+
slot/0/right_color = Color(1, 1, 1, 1)
18+
slot/0/right_icon = ExtResource("1_dgv1w")
19+
slot/0/draw_stylebox = true
20+
script = ExtResource("1_7udae")
21+
22+
[node name="Label" type="Label" parent="."]
23+
layout_mode = 2
24+
text = "Flow"
25+
horizontal_alignment = 2

0 commit comments

Comments
 (0)