└── addons └── task_graph ├── LICENSE.md ├── editor ├── graph_edit.gd ├── graph_node_row.tscn ├── input_node.gd ├── input_node.tscn ├── output_node.gd ├── output_node.tscn ├── task_graph_editor.tscn ├── task_node.gd └── task_node.tscn ├── plugin.cfg ├── plugin.gd ├── task_graph_data.gd ├── task_graph_data_icon.png ├── task_graph_data_icon.png.import ├── task_manager.gd ├── task_manager_icon.png ├── task_manager_icon.png.import └── thread_utils ├── atomic_list.gd ├── thread_gate.gd └── thread_pool.gd /addons/task_graph/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Daniel Eliasinski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/task_graph/editor/graph_edit.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphEdit 3 | 4 | const TaskGraphData = preload("res://addons/task_graph/task_graph_data.gd") 5 | const TaskNode = preload("res://addons/task_graph/editor/task_node.tscn") 6 | const InputNode = preload("res://addons/task_graph/editor/input_node.tscn") 7 | const OutputNode = preload("res://addons/task_graph/editor/output_node.tscn") 8 | 9 | var resource 10 | 11 | var selected_node 12 | var place 13 | var from 14 | var from_slot 15 | 16 | func _ready(): 17 | add_valid_connection_type(0, 0) 18 | #add_valid_left_disconnect_type(0) 19 | add_valid_right_disconnect_type(0) 20 | connect("node_selected", self, "selected") 21 | connect("popup_request", self, "menu_requested") 22 | connect("connection_request", self, "connect_requested") 23 | connect("disconnection_request", self, "disconnect_requested") 24 | connect("connection_to_empty", self, "connect_empty_requested") 25 | 26 | $RightClickMenu.connect("index_pressed", self, "new_node_requested") 27 | 28 | $SaveDialog.connect("file_selected", self, "save_graph") 29 | $SaveDialog.add_filter("*." + TaskGraphData.EXTENSION + " ; Task Graph Data") 30 | 31 | $LoadDialog.connect("file_selected", self, "load_graph") 32 | $LoadDialog.add_filter("*." + TaskGraphData.EXTENSION + " ; Task Graph Data") 33 | 34 | $FileDialog.connect("file_selected", self, "file_selected") 35 | 36 | get_node("../Header/FileMenuButton").get_popup().connect("index_pressed", self, "file_option_selected") 37 | get_node("../Header/EditMenuButton").get_popup().connect("index_pressed", self, "edit_option_selected") 38 | 39 | func selected(node = selected_node): 40 | selected_node = node 41 | 42 | for child in get_children(): 43 | if child is GraphNode: child.overlay = GraphNode.OVERLAY_DISABLED 44 | 45 | if node: highlight_dependencies(node) 46 | 47 | func highlight_dependencies(node): 48 | var connections = get_connection_list() 49 | 50 | for connection in connections: 51 | if connection["to"] != node.name: continue 52 | 53 | var dependency = get_node(connection["from"]) 54 | dependency.overlay = GraphNode.OVERLAY_POSITION 55 | highlight_dependencies(dependency) 56 | 57 | func menu_requested(cursor): 58 | place = get_local_mouse_position() 59 | from = null 60 | from_slot = null 61 | $RightClickMenu.popup(Rect2(cursor, Vector2(1, 1))) 62 | 63 | func file_option_selected(idx): 64 | if idx == 0: 65 | new_graph() 66 | elif idx == 1: 67 | save_requested() 68 | elif idx == 2: 69 | load_requested() 70 | 71 | func edit_option_selected(idx): 72 | pass 73 | 74 | func new_node_requested(idx): 75 | # TODO Toggle comment property on nodes when they have dangling inputs as a sort of warning 76 | var node 77 | if idx == TaskGraphData.TASK_NODE: 78 | node = TaskNode.instance() 79 | node.set_meta("type", TaskGraphData.TASK_NODE) 80 | elif idx == TaskGraphData.INPUT_NODE: 81 | node = InputNode.instance() 82 | node.set_meta("type", TaskGraphData.INPUT_NODE) 83 | elif idx == TaskGraphData.OUTPUT_NODE: 84 | node = OutputNode.instance() 85 | node.set_meta("type", TaskGraphData.OUTPUT_NODE) 86 | elif idx == TaskGraphData.TASK_GRAPH_NODE: 87 | return 88 | elif idx == TaskGraphData.CONSTANT_NODE: 89 | return 90 | 91 | node.offset = place + scroll_offset 92 | add_child(node) 93 | 94 | if from and idx == 2: 95 | connect_node(from, from_slot, node.name, 0) 96 | 97 | func connect_requested(from, from_slot, to, to_slot): 98 | for connection in get_connection_list(): 99 | if connection["to"] == to and connection["to_port"] == to_slot: 100 | disconnect_node(connection["from"], connection["from_port"], connection["to"], connection["to_port"]) 101 | break 102 | connect_node(from, from_slot, to, to_slot) 103 | selected() 104 | 105 | func disconnect_requested(from, from_slot, to, to_slot): 106 | disconnect_node(from, from_slot, to, to_slot) 107 | selected() 108 | 109 | func connect_empty_requested(p_from, p_from_slot, cursor): 110 | place = cursor 111 | from = p_from 112 | from_slot = p_from_slot 113 | $RightClickMenu.popup(Rect2(get_global_mouse_position(), Vector2(1, 1))) 114 | 115 | func clear_node(node): 116 | for connection in get_connection_list(): 117 | if connection["from"] == node.name or connection["to"] == node.name: 118 | disconnect_node(connection["from"], connection["from_port"], connection["to"], connection["to_port"]) 119 | selected() 120 | 121 | func delete_node(node): 122 | clear_node(node) 123 | if selected_node == node: selected_node = null 124 | node.queue_free() 125 | 126 | 127 | func clear_graph(): 128 | clear_connections() 129 | for child in get_children(): 130 | if child is GraphNode: 131 | child.queue_free() 132 | 133 | func new_graph(): 134 | clear_graph() 135 | resource = null 136 | 137 | func save_requested(): 138 | if not resource or resource.resource_path == "": 139 | $SaveDialog.popup_centered_ratio() 140 | else: 141 | save_graph() 142 | 143 | func save_graph(file = null): 144 | if file: 145 | if not resource: resource = TaskGraphData.new() 146 | resource.take_over_path(file) 147 | 148 | var names = [] 149 | var nodes = [] 150 | for child in get_children(): 151 | if child is GraphNode: 152 | nodes.append({ 153 | "type": child.get_type(), 154 | "position": [child.offset.x, child.offset.y], 155 | "width": child.rect_size.x, 156 | "data": child.get_data(), 157 | #"inputs": child.get_inputs(), 158 | #"outputs": child.get_outputs(), 159 | }) 160 | names.append(child.name) 161 | resource.nodes = nodes 162 | 163 | var connections = get_connection_list() 164 | for connection in connections: 165 | var from = get_node(connection["from"]) 166 | var to = get_node(connection["to"]) 167 | connection["from"] = names.find(connection["from"]) 168 | connection["to"] = names.find(connection["to"]) 169 | connection["from_port"] = from.get_output_from_index(connection["from_port"]) 170 | connection["to_port"] = to.get_input_from_index(connection["to_port"]) 171 | resource.connections = connections 172 | 173 | # TODO save graph offset too? 174 | 175 | ResourceSaver.save(resource.resource_path, resource) 176 | 177 | func load_requested(): 178 | $LoadDialog.popup_centered_ratio() 179 | 180 | func load_graph(file): 181 | if typeof(file) == TYPE_STRING: 182 | resource = ResourceLoader.load(file) 183 | elif file is TaskGraphData: 184 | resource = file 185 | else: 186 | printerr("Unknown argument") 187 | return 188 | 189 | clear_graph() 190 | var nodes = [] 191 | for node in resource.nodes: 192 | var graph_node 193 | if node["type"] == TaskGraphData.TASK_NODE: 194 | graph_node = TaskNode.instance() 195 | elif node["type"] == TaskGraphData.INPUT_NODE: 196 | graph_node = InputNode.instance() 197 | elif node["type"] == TaskGraphData.OUTPUT_NODE: 198 | graph_node = OutputNode.instance() 199 | else: 200 | pass 201 | add_child(graph_node) 202 | graph_node.offset = Vector2(node["position"][0], node["position"][1]) 203 | graph_node.rect_size.x = node["width"] 204 | graph_node.set_data(node["data"]) 205 | nodes.append(graph_node) 206 | 207 | for connection in resource.connections: 208 | var from = nodes[connection["from"]] 209 | var to = nodes[connection["to"]] 210 | connect_node(from.name, from.get_index_from_output(connection["from_port"]), 211 | to.name, to.get_index_from_input(connection["to_port"])) 212 | 213 | var func_ref 214 | func show_file_dialog(obj, func_name): 215 | func_ref = funcref(obj, func_name) 216 | $FileDialog.popup_centered_ratio() 217 | 218 | func file_selected(file): 219 | func_ref.call_func(file) 220 | 221 | func error(msg): 222 | $ErrorDialog.dialog_text = msg 223 | $ErrorDialog.popup_centered() 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /addons/task_graph/editor/graph_node_row.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="GraphNodeRow" type="HBoxContainer" index="0"] 4 | 5 | anchor_left = 0.0 6 | anchor_top = 0.0 7 | anchor_right = 0.0 8 | anchor_bottom = 0.0 9 | margin_right = 18.0 10 | margin_bottom = 20.0 11 | rect_min_size = Vector2( 0, 20 ) 12 | rect_pivot_offset = Vector2( 0, 0 ) 13 | rect_clip_content = false 14 | mouse_filter = 1 15 | mouse_default_cursor_shape = 0 16 | size_flags_horizontal = 1 17 | size_flags_vertical = 1 18 | alignment = 0 19 | _sections_unfolded = [ "Rect" ] 20 | 21 | [node name="LeftLabel" type="Label" parent="." index="0"] 22 | 23 | anchor_left = 0.0 24 | anchor_top = 0.0 25 | anchor_right = 0.0 26 | anchor_bottom = 0.0 27 | margin_top = 3.0 28 | margin_bottom = 17.0 29 | rect_pivot_offset = Vector2( 0, 0 ) 30 | rect_clip_content = false 31 | mouse_filter = 2 32 | mouse_default_cursor_shape = 0 33 | size_flags_horizontal = 1 34 | size_flags_vertical = 4 35 | percent_visible = 1.0 36 | lines_skipped = 0 37 | max_lines_visible = -1 38 | 39 | [node name="Seperator" type="Control" parent="." index="1"] 40 | 41 | anchor_left = 0.0 42 | anchor_top = 0.0 43 | anchor_right = 0.0 44 | anchor_bottom = 0.0 45 | margin_left = 4.0 46 | margin_right = 14.0 47 | margin_bottom = 20.0 48 | rect_min_size = Vector2( 10, 0 ) 49 | rect_pivot_offset = Vector2( 0, 0 ) 50 | rect_clip_content = false 51 | mouse_filter = 0 52 | mouse_default_cursor_shape = 0 53 | size_flags_horizontal = 3 54 | size_flags_vertical = 1 55 | _sections_unfolded = [ "Rect" ] 56 | 57 | [node name="RightLabel" type="Label" parent="." index="2"] 58 | 59 | anchor_left = 0.0 60 | anchor_top = 0.0 61 | anchor_right = 0.0 62 | anchor_bottom = 0.0 63 | margin_left = 18.0 64 | margin_top = 3.0 65 | margin_right = 18.0 66 | margin_bottom = 17.0 67 | rect_pivot_offset = Vector2( 0, 0 ) 68 | rect_clip_content = false 69 | mouse_filter = 2 70 | mouse_default_cursor_shape = 0 71 | size_flags_horizontal = 1 72 | size_flags_vertical = 4 73 | percent_visible = 1.0 74 | lines_skipped = 0 75 | max_lines_visible = -1 76 | 77 | 78 | -------------------------------------------------------------------------------- /addons/task_graph/editor/input_node.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | 4 | const TaskGraphData = preload("res://addons/task_graph/task_graph_data.gd") 5 | 6 | func _ready(): 7 | connect("close_request", get_parent(), "delete_node", [self]) 8 | connect("resize_request", self, "resize_requested") 9 | 10 | func resize_requested(size): 11 | rect_size.x = size.x 12 | 13 | func set_data(data): 14 | $InputName.text = data 15 | 16 | func get_data(): 17 | return $InputName.text 18 | 19 | func get_type(): 20 | return TaskGraphData.INPUT_NODE 21 | 22 | # Input node doesn't have any inputs 23 | #func get_input_from_index(idx): 24 | # pass 25 | 26 | func get_output_from_index(idx): 27 | return $InputName.text 28 | 29 | #func get_index_from_input(input): 30 | # pass 31 | 32 | func get_index_from_output(output): 33 | return 0 34 | 35 | -------------------------------------------------------------------------------- /addons/task_graph/editor/input_node.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/task_graph/editor/input_node.gd" type="Script" id=1] 4 | 5 | [node name="InputNode" type="GraphNode"] 6 | anchor_left = 0.0 7 | anchor_top = 0.0 8 | anchor_right = 0.0 9 | anchor_bottom = 0.0 10 | margin_right = 200.0 11 | margin_bottom = 53.0 12 | rect_pivot_offset = Vector2( 0, 0 ) 13 | rect_clip_content = false 14 | mouse_filter = 0 15 | mouse_default_cursor_shape = 0 16 | size_flags_horizontal = 1 17 | size_flags_vertical = 1 18 | title = "Input" 19 | offset = Vector2( 0, 0 ) 20 | show_close = true 21 | resizable = true 22 | selected = false 23 | comment = false 24 | overlay = 0 25 | slot/0/left_enabled = false 26 | slot/0/left_type = 0 27 | slot/0/left_color = Color( 1, 1, 1, 1 ) 28 | slot/0/right_enabled = true 29 | slot/0/right_type = 0 30 | slot/0/right_color = Color( 1, 1, 1, 1 ) 31 | script = ExtResource( 1 ) 32 | _sections_unfolded = [ "0", "1", "Rect" ] 33 | 34 | [node name="InputName" type="LineEdit" parent="." index="0"] 35 | anchor_left = 0.0 36 | anchor_top = 0.0 37 | anchor_right = 0.0 38 | anchor_bottom = 0.0 39 | margin_left = 16.0 40 | margin_top = 24.0 41 | margin_right = 184.0 42 | margin_bottom = 48.0 43 | rect_pivot_offset = Vector2( 0, 0 ) 44 | rect_clip_content = false 45 | focus_mode = 2 46 | mouse_filter = 0 47 | mouse_default_cursor_shape = 1 48 | size_flags_horizontal = 1 49 | size_flags_vertical = 1 50 | secret_character = "*" 51 | focus_mode = 2 52 | context_menu_enabled = true 53 | placeholder_text = "Name" 54 | placeholder_alpha = 0.6 55 | caret_blink = false 56 | caret_blink_speed = 0.65 57 | caret_position = 0 58 | _sections_unfolded = [ "Placeholder" ] 59 | 60 | -------------------------------------------------------------------------------- /addons/task_graph/editor/output_node.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | 4 | const TaskGraphData = preload("res://addons/task_graph/task_graph_data.gd") 5 | 6 | func _ready(): 7 | connect("close_request", get_parent(), "delete_node", [self]) 8 | connect("resize_request", self, "resize_requested") 9 | 10 | func resize_requested(size): 11 | rect_size.x = size.x 12 | 13 | func set_data(data): 14 | $OutputName.text = data 15 | 16 | func get_data(): 17 | return $OutputName.text 18 | 19 | func get_type(): 20 | return TaskGraphData.OUTPUT_NODE 21 | 22 | func get_input_from_index(idx): 23 | return $OutputName.text 24 | 25 | #func get_output_from_index(idx): 26 | # pass 27 | 28 | func get_index_from_input(input): 29 | return 0 30 | 31 | #func get_index_from_output(output): 32 | # pass -------------------------------------------------------------------------------- /addons/task_graph/editor/output_node.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/task_graph/editor/output_node.gd" type="Script" id=1] 4 | 5 | [node name="OutputNode" type="GraphNode" index="0"] 6 | anchor_left = 0.0 7 | anchor_top = 0.0 8 | anchor_right = 0.0 9 | anchor_bottom = 0.0 10 | margin_right = 200.0 11 | margin_bottom = 53.0 12 | rect_pivot_offset = Vector2( 0, 0 ) 13 | rect_clip_content = false 14 | mouse_filter = 0 15 | mouse_default_cursor_shape = 0 16 | size_flags_horizontal = 1 17 | size_flags_vertical = 1 18 | title = "Output" 19 | offset = Vector2( 0, 0 ) 20 | show_close = true 21 | resizable = true 22 | selected = false 23 | comment = false 24 | overlay = 0 25 | slot/0/left_enabled = true 26 | slot/0/left_type = 0 27 | slot/0/left_color = Color( 1, 1, 1, 1 ) 28 | slot/0/right_enabled = false 29 | slot/0/right_type = 0 30 | slot/0/right_color = Color( 1, 1, 1, 1 ) 31 | script = ExtResource( 1 ) 32 | _sections_unfolded = [ "0", "Rect", "Slot" ] 33 | 34 | [node name="OutputName" type="LineEdit" parent="." index="0"] 35 | anchor_left = 0.0 36 | anchor_top = 0.0 37 | anchor_right = 0.0 38 | anchor_bottom = 0.0 39 | margin_left = 16.0 40 | margin_top = 24.0 41 | margin_right = 184.0 42 | margin_bottom = 48.0 43 | rect_pivot_offset = Vector2( 0, 0 ) 44 | rect_clip_content = false 45 | focus_mode = 2 46 | mouse_filter = 0 47 | mouse_default_cursor_shape = 1 48 | size_flags_horizontal = 1 49 | size_flags_vertical = 1 50 | secret_character = "*" 51 | focus_mode = 2 52 | context_menu_enabled = true 53 | placeholder_text = "Name" 54 | placeholder_alpha = 0.6 55 | caret_blink = false 56 | caret_blink_speed = 0.65 57 | caret_position = 0 58 | _sections_unfolded = [ "Rect" ] 59 | 60 | -------------------------------------------------------------------------------- /addons/task_graph/editor/task_graph_editor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/task_graph/editor/graph_edit.gd" type="Script" id=1] 4 | 5 | [node name="TaskGraph" type="VBoxContainer" index="0"] 6 | anchor_left = 0.0 7 | anchor_top = 0.0 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | rect_pivot_offset = Vector2( 0, 0 ) 11 | rect_clip_content = false 12 | mouse_filter = 1 13 | mouse_default_cursor_shape = 0 14 | size_flags_horizontal = 1 15 | size_flags_vertical = 1 16 | alignment = 0 17 | 18 | [node name="Header" type="HBoxContainer" parent="." index="0"] 19 | anchor_left = 0.0 20 | anchor_top = 0.0 21 | anchor_right = 0.0 22 | anchor_bottom = 0.0 23 | margin_right = 1024.0 24 | margin_bottom = 20.0 25 | rect_pivot_offset = Vector2( 0, 0 ) 26 | rect_clip_content = false 27 | mouse_filter = 1 28 | mouse_default_cursor_shape = 0 29 | size_flags_horizontal = 1 30 | size_flags_vertical = 1 31 | alignment = 0 32 | _sections_unfolded = [ "Custom Constants" ] 33 | 34 | [node name="FileMenuButton" type="MenuButton" parent="Header" index="0"] 35 | anchor_left = 0.0 36 | anchor_top = 0.0 37 | anchor_right = 0.0 38 | anchor_bottom = 0.0 39 | margin_right = 35.0 40 | margin_bottom = 20.0 41 | rect_pivot_offset = Vector2( 0, 0 ) 42 | rect_clip_content = false 43 | mouse_filter = 0 44 | mouse_default_cursor_shape = 0 45 | size_flags_horizontal = 1 46 | size_flags_vertical = 1 47 | toggle_mode = false 48 | action_mode = 0 49 | enabled_focus_mode = 0 50 | shortcut = null 51 | group = null 52 | text = "File" 53 | flat = true 54 | align = 1 55 | items = [ "New", null, 0, false, false, -1, 0, null, "", false, "Save", null, 0, false, false, -1, 0, null, "", false, "Load", null, 0, false, false, -1, 0, null, "", false ] 56 | 57 | [node name="EditMenuButton" type="MenuButton" parent="Header" index="1"] 58 | anchor_left = 0.0 59 | anchor_top = 0.0 60 | anchor_right = 0.0 61 | anchor_bottom = 0.0 62 | margin_left = 39.0 63 | margin_right = 75.0 64 | margin_bottom = 20.0 65 | rect_pivot_offset = Vector2( 0, 0 ) 66 | rect_clip_content = false 67 | mouse_filter = 0 68 | mouse_default_cursor_shape = 0 69 | size_flags_horizontal = 1 70 | size_flags_vertical = 1 71 | toggle_mode = false 72 | action_mode = 0 73 | enabled_focus_mode = 0 74 | shortcut = null 75 | group = null 76 | text = "Edit" 77 | flat = true 78 | align = 1 79 | items = [ ] 80 | 81 | [node name="GraphEdit" type="GraphEdit" parent="." index="1"] 82 | anchor_left = 0.0 83 | anchor_top = 0.0 84 | anchor_right = 0.0 85 | anchor_bottom = 0.0 86 | margin_top = 24.0 87 | margin_right = 1024.0 88 | margin_bottom = 600.0 89 | rect_min_size = Vector2( 0, 300 ) 90 | rect_pivot_offset = Vector2( 0, 0 ) 91 | rect_clip_content = true 92 | focus_mode = 2 93 | mouse_filter = 0 94 | mouse_default_cursor_shape = 0 95 | size_flags_horizontal = 1 96 | size_flags_vertical = 3 97 | right_disconnects = false 98 | scroll_offset = Vector2( 0, -134 ) 99 | snap_distance = 20 100 | use_snap = true 101 | zoom = 1.0 102 | script = ExtResource( 1 ) 103 | 104 | [node name="RightClickMenu" type="PopupMenu" parent="GraphEdit" index="0"] 105 | visible = false 106 | anchor_left = 0.0 107 | anchor_top = 0.0 108 | anchor_right = 0.0 109 | anchor_bottom = 0.0 110 | margin_left = 290.0 111 | margin_top = 107.0 112 | margin_right = 387.0 113 | margin_bottom = 177.0 114 | rect_pivot_offset = Vector2( 0, 0 ) 115 | rect_clip_content = false 116 | focus_mode = 2 117 | mouse_filter = 0 118 | mouse_default_cursor_shape = 0 119 | size_flags_horizontal = 1 120 | size_flags_vertical = 1 121 | popup_exclusive = false 122 | items = [ "New Task", null, 0, false, false, -1, 0, null, "", false, "New Input", null, 0, false, false, -1, 0, null, "", false, "New Output", null, 0, false, false, -1, 0, null, "", false, "New Task Graph", null, 0, false, false, -1, 0, null, "", false, "Constant", null, 0, false, false, -1, 0, null, "", false ] 123 | hide_on_state_item_selection = false 124 | 125 | [node name="ErrorDialog" type="AcceptDialog" parent="GraphEdit" index="1"] 126 | visible = false 127 | anchor_left = 0.0 128 | anchor_top = 0.0 129 | anchor_right = 0.0 130 | anchor_bottom = 0.0 131 | margin_right = 83.0 132 | margin_bottom = 58.0 133 | rect_pivot_offset = Vector2( 0, 0 ) 134 | rect_clip_content = false 135 | mouse_filter = 0 136 | mouse_default_cursor_shape = 0 137 | size_flags_horizontal = 1 138 | size_flags_vertical = 1 139 | popup_exclusive = false 140 | window_title = "Alert!" 141 | resizable = false 142 | dialog_hide_on_ok = true 143 | 144 | [node name="SaveDialog" type="FileDialog" parent="GraphEdit" index="2"] 145 | visible = false 146 | anchor_left = 0.0 147 | anchor_top = 0.0 148 | anchor_right = 0.0 149 | anchor_bottom = 0.0 150 | margin_right = 275.0 151 | margin_bottom = 130.0 152 | rect_min_size = Vector2( 200, 70 ) 153 | rect_pivot_offset = Vector2( 0, 0 ) 154 | rect_clip_content = false 155 | mouse_filter = 0 156 | mouse_default_cursor_shape = 0 157 | size_flags_horizontal = 1 158 | size_flags_vertical = 1 159 | popup_exclusive = false 160 | window_title = "Save a File" 161 | resizable = false 162 | dialog_hide_on_ok = false 163 | mode_overrides_title = true 164 | mode = 4 165 | access = 0 166 | filters = PoolStringArray( "*.tres ; Task Graph Data", "*.tres ; Task Graph Data", "*.tres ; Task Graph Data" ) 167 | show_hidden_files = false 168 | current_dir = "res://" 169 | current_file = "" 170 | current_path = "res://" 171 | 172 | [node name="LoadDialog" type="FileDialog" parent="GraphEdit" index="3"] 173 | visible = false 174 | anchor_left = 0.0 175 | anchor_top = 0.0 176 | anchor_right = 0.0 177 | anchor_bottom = 0.0 178 | margin_right = 275.0 179 | margin_bottom = 130.0 180 | rect_min_size = Vector2( 200, 70 ) 181 | rect_pivot_offset = Vector2( 0, 0 ) 182 | rect_clip_content = false 183 | mouse_filter = 0 184 | mouse_default_cursor_shape = 0 185 | size_flags_horizontal = 1 186 | size_flags_vertical = 1 187 | popup_exclusive = false 188 | window_title = "Open a File" 189 | resizable = false 190 | dialog_hide_on_ok = false 191 | mode_overrides_title = true 192 | mode = 0 193 | access = 0 194 | filters = PoolStringArray( "*.tres ; Task Graph Data", "*.tres ; Task Graph Data", "*.tres ; Task Graph Data" ) 195 | show_hidden_files = false 196 | current_dir = "res://" 197 | current_file = "" 198 | current_path = "res://" 199 | 200 | [node name="FileDialog" type="FileDialog" parent="GraphEdit" index="4"] 201 | visible = false 202 | anchor_left = 0.0 203 | anchor_top = 0.0 204 | anchor_right = 0.0 205 | anchor_bottom = 0.0 206 | margin_right = 275.0 207 | margin_bottom = 130.0 208 | rect_min_size = Vector2( 200, 70 ) 209 | rect_pivot_offset = Vector2( 0, 0 ) 210 | rect_clip_content = false 211 | mouse_filter = 0 212 | mouse_default_cursor_shape = 0 213 | size_flags_horizontal = 1 214 | size_flags_vertical = 1 215 | popup_exclusive = false 216 | window_title = "Open a File" 217 | resizable = false 218 | dialog_hide_on_ok = false 219 | mode_overrides_title = true 220 | mode = 0 221 | access = 0 222 | filters = PoolStringArray( ) 223 | show_hidden_files = false 224 | current_dir = "res://" 225 | current_file = "" 226 | current_path = "res://" 227 | 228 | -------------------------------------------------------------------------------- /addons/task_graph/editor/task_node.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | 4 | const TaskGraphData = preload("res://addons/task_graph/task_graph_data.gd") 5 | const GraphNodeRow = preload("res://addons/task_graph/editor/graph_node_row.tscn") 6 | 7 | var task_class 8 | 9 | var inputs 10 | var outputs 11 | 12 | func _ready(): 13 | connect("close_request", get_parent(), "delete_node", [self]) 14 | connect("resize_request", self, "resize_requested") 15 | 16 | func choose_class(): 17 | get_parent().show_file_dialog(self, "select_task_class") 18 | 19 | func select_task_class(class_file): 20 | var check = load(class_file) 21 | if task_class == check: return 22 | # TODO Add option to disable these checks in File menu or File Dialog 23 | # TODO These checks won't work in other languages like C# 24 | # if check == null: 25 | # error("Error loading!") 26 | # return 27 | # if not check is Script: 28 | # error("This is not a script!") 29 | # return 30 | # if not has_function(check, "_run"): 31 | # error("This class doesn't have a '_run' method!\nRead the doc for more info...") 32 | # return 33 | # if not has_function(check, "_inputs", true): 34 | # error("This class doesn't have an '_inputs' method!\nRead the doc for more info...") 35 | # return 36 | # if not has_function(check, "_outputs", true): 37 | # error("This class doesn't have an '_outputs' method!\nRead the doc for more info...") 38 | # return 39 | 40 | task_class = check 41 | 42 | 43 | update_node() 44 | 45 | func update_node(): 46 | get_parent().clear_node(self) 47 | clear_all_slots() 48 | for child in get_children(): 49 | if child is HBoxContainer: child.free() 50 | 51 | $TaskFile.text = task_class.resource_path 52 | 53 | inputs = task_class._inputs() 54 | outputs = task_class._outputs() 55 | var amount = max(inputs.size(), outputs.size()) 56 | 57 | var rows = [] 58 | while amount > 0: 59 | rows.push_back(GraphNodeRow.instance()) 60 | amount -= 1 61 | 62 | var i = 0 63 | for input in inputs: 64 | rows[i].get_node("LeftLabel").text = input 65 | i += 1 66 | 67 | while i < rows.size(): 68 | rows[i].get_node("LeftLabel").visible = false 69 | i += 1 70 | 71 | i = 0 72 | for output in outputs: 73 | rows[i].get_node("RightLabel").text = output 74 | i += 1 75 | 76 | while i < rows.size(): 77 | rows[i].get_node("RightLabel").visible = false 78 | i += 1 79 | 80 | for row in rows: 81 | add_child(row) 82 | set_slot(row.get_position_in_parent(), row.get_node("LeftLabel").visible, 0, Color(1, 1, 1), 83 | row.get_node("RightLabel").visible, 0, Color(1, 1, 1)) 84 | 85 | func error(msg): 86 | get_parent().error(msg) 87 | 88 | #func has_function(script, func_name, is_static = false): 89 | # # TODO Replace with RegEx to parse out whitspaces and stuff 90 | # var has_static = script.source_code.find("static func " + func_name + "():") != -1 91 | # if is_static: 92 | # return has_static 93 | # else: 94 | # return script.source_code.find("func " + func_name + "():") != -1 and not has_static 95 | 96 | func resize_requested(size): 97 | rect_size.x = size.x 98 | 99 | func set_data(data): 100 | if not data: return 101 | task_class = load(data) 102 | update_node() 103 | 104 | func get_data(): 105 | return task_class.resource_path if task_class else null 106 | 107 | func get_type(): 108 | return TaskGraphData.TASK_NODE 109 | 110 | func get_input_from_index(idx): 111 | return inputs[idx] 112 | 113 | func get_output_from_index(idx): 114 | return outputs[idx] 115 | 116 | func get_index_from_input(input): 117 | return inputs.find(input) 118 | 119 | func get_index_from_output(output): 120 | return outputs.find(output) 121 | 122 | 123 | -------------------------------------------------------------------------------- /addons/task_graph/editor/task_node.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/task_graph/editor/task_node.gd" type="Script" id=1] 4 | 5 | [node name="TaskNode" type="GraphNode"] 6 | anchor_left = 0.0 7 | anchor_top = 0.0 8 | anchor_right = 0.0 9 | anchor_bottom = 0.0 10 | margin_left = -2.0 11 | margin_right = 271.0 12 | margin_bottom = 64.0 13 | rect_pivot_offset = Vector2( 0, 0 ) 14 | rect_clip_content = false 15 | mouse_filter = 0 16 | mouse_default_cursor_shape = 0 17 | size_flags_horizontal = 1 18 | size_flags_vertical = 1 19 | title = "Task Node" 20 | offset = Vector2( 0, 0 ) 21 | show_close = true 22 | resizable = true 23 | selected = false 24 | comment = false 25 | overlay = 0 26 | slot/0/left_enabled = false 27 | slot/0/left_type = 0 28 | slot/0/left_color = Color( 1, 1, 1, 1 ) 29 | slot/0/right_enabled = false 30 | slot/0/right_type = 0 31 | slot/0/right_color = Color( 1, 1, 1, 1 ) 32 | slot/1/left_enabled = false 33 | slot/1/left_type = 0 34 | slot/1/left_color = Color( 1, 1, 1, 1 ) 35 | slot/1/right_enabled = false 36 | slot/1/right_type = 0 37 | slot/1/right_color = Color( 1, 1, 1, 1 ) 38 | script = ExtResource( 1 ) 39 | _sections_unfolded = [ "0", "1", "Slot" ] 40 | 41 | [node name="TaskFile" type="Label" parent="." index="0"] 42 | anchor_left = 0.0 43 | anchor_top = 0.0 44 | anchor_right = 0.0 45 | anchor_bottom = 0.0 46 | margin_left = 16.0 47 | margin_top = 24.0 48 | margin_right = 257.0 49 | margin_bottom = 49.0 50 | rect_min_size = Vector2( 0, 25 ) 51 | rect_pivot_offset = Vector2( 0, 0 ) 52 | rect_clip_content = false 53 | mouse_filter = 2 54 | mouse_default_cursor_shape = 0 55 | size_flags_horizontal = 1 56 | size_flags_vertical = 4 57 | text = "Empty" 58 | align = 1 59 | valign = 1 60 | percent_visible = 1.0 61 | lines_skipped = 0 62 | max_lines_visible = -1 63 | _sections_unfolded = [ "Rect" ] 64 | 65 | [node name="Choose" type="Button" parent="." index="1"] 66 | anchor_left = 0.0 67 | anchor_top = 0.0 68 | anchor_right = 0.0 69 | anchor_bottom = 0.0 70 | margin_left = 16.0 71 | margin_top = 49.0 72 | margin_right = 257.0 73 | margin_bottom = 69.0 74 | rect_pivot_offset = Vector2( 0, 0 ) 75 | rect_clip_content = false 76 | focus_mode = 2 77 | mouse_filter = 0 78 | mouse_default_cursor_shape = 0 79 | size_flags_horizontal = 1 80 | size_flags_vertical = 1 81 | toggle_mode = false 82 | enabled_focus_mode = 2 83 | shortcut = null 84 | group = null 85 | text = "Choose..." 86 | flat = false 87 | align = 1 88 | 89 | [connection signal="pressed" from="Choose" to="." method="choose_class"] 90 | -------------------------------------------------------------------------------- /addons/task_graph/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Task Graph" 4 | description="A task graph that takes advantage of multithreading to complete task as fast as possible" 5 | author="Daniel Eliasinski" 6 | version="1.1" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/task_graph/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | const TaskGraphData = preload("task_graph_data.gd") 5 | 6 | var editor_panel = preload("editor/task_graph_editor.tscn").instance() 7 | var editor_panel_button 8 | 9 | func _enter_tree(): 10 | name = "TaskGraphPlugin" 11 | 12 | editor_panel_button = add_control_to_bottom_panel(editor_panel, "Task Graph") 13 | editor_panel_button.visible = false 14 | 15 | add_custom_type("TaskGraphData", "Resource", preload("task_graph_data.gd"), preload("task_graph_data_icon.png")) 16 | add_custom_type("TaskManager", "Node", preload("task_manager.gd"), preload("task_manager_icon.png")) 17 | 18 | 19 | func _exit_tree(): 20 | remove_control_from_bottom_panel(editor_panel) 21 | editor_panel.queue_free() 22 | remove_custom_type("TaskGraphData") 23 | remove_custom_type("TaskManager") 24 | 25 | func handles(object): 26 | return object is TaskGraphData 27 | 28 | func edit(object): 29 | editor_panel.get_node("GraphEdit").load_graph(object) 30 | 31 | func make_visible(visible): 32 | editor_panel_button.visible = visible 33 | if not visible: editor_panel.visible = visible -------------------------------------------------------------------------------- /addons/task_graph/task_graph_data.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | 3 | enum Type{ 4 | TASK_NODE 5 | INPUT_NODE 6 | OUTPUT_NODE 7 | TASK_GRAPH_NODE 8 | CONSTANT_NODE 9 | } 10 | 11 | const EXTENSION = "tres" 12 | 13 | # TODO Change to storage nodes 14 | # TODO Optimize data storage 15 | #{ 16 | # "name": String 17 | # "type": Type 18 | #} 19 | export var nodes = [] 20 | 21 | export var connections = [] 22 | 23 | func get_inputs(): 24 | var inputs = [] 25 | for node in nodes: 26 | if node["type"] == INPUT_NODE: 27 | inputs.push_back(node) 28 | return inputs 29 | 30 | func get_outputs(): 31 | var outputs = [] 32 | for node in nodes: 33 | if node["type"] == OUTPUT_NODE: 34 | outputs.push_back(node) 35 | return outputs 36 | 37 | func get_tasks(): 38 | var tasks = [] 39 | for node in nodes: 40 | if node["type"] == TASK_NODE: 41 | tasks.push_back(node) 42 | return tasks 43 | 44 | # Instantiates all task classes 45 | func load_tasks(): 46 | var tasks = get_tasks() 47 | for idx in tasks.size(): 48 | tasks[idx] = load(tasks[idx]["data"]).new() 49 | return tasks 50 | 51 | # TODO Check if valid task graph (Useful for assertion) 52 | func validate(): 53 | pass 54 | -------------------------------------------------------------------------------- /addons/task_graph/task_graph_data_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopoldek/Multi-Task-Manager/8cac5f1102a43d74d9843887196d87015d80c46c/addons/task_graph/task_graph_data_icon.png -------------------------------------------------------------------------------- /addons/task_graph/task_graph_data_icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/task_graph_data_icon.png-01fedea3da3cc5c30d87a62ca99390c9.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://addons/task_graph/task_graph_data_icon.png" 10 | dest_files=[ "res://.import/task_graph_data_icon.png-01fedea3da3cc5c30d87a62ca99390c9.stex" ] 11 | 12 | [params] 13 | 14 | compress/mode=0 15 | compress/lossy_quality=0.7 16 | compress/hdr_mode=0 17 | compress/normal_map=0 18 | flags/repeat=0 19 | flags/filter=true 20 | flags/mipmaps=false 21 | flags/anisotropic=false 22 | flags/srgb=2 23 | process/fix_alpha_border=true 24 | process/premult_alpha=false 25 | process/HDR_as_SRGB=false 26 | stream=false 27 | size_limit=0 28 | detect_3d=true 29 | svg/scale=1.0 30 | -------------------------------------------------------------------------------- /addons/task_graph/task_manager.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | #const TaskGraphData = preload("res://addons/task_graph/task_graph_data.gd") 5 | const TaskGraphData = preload("task_graph_data.gd") 6 | const ThreadPool = preload("thread_utils/thread_pool.gd") 7 | 8 | #signal task_finished(task) 9 | signal output_ready(output_name, output) 10 | signal finished() 11 | 12 | export(int, 1000) var max_threads = 0 # 0x7FFFFFFF <- Max integer 13 | #var resource setget set_resource # Only for 3.1 14 | export(Resource) var resource setget set_resource 15 | 16 | 17 | var _inputs = {} 18 | var _outputs = {} 19 | var _outputs_left 20 | 21 | var thread_pool 22 | 23 | var nodes 24 | 25 | func start(): 26 | if resource == null: 27 | printerr("No resource available! Could not start.") 28 | return 29 | 30 | _outputs_left = _outputs.size() 31 | 32 | nodes = [] 33 | for node in resource.nodes: 34 | if node["type"] == TaskGraphData.TASK_NODE: 35 | nodes.append(Task.new(self, load(node["data"]).new())) 36 | elif node["type"] == TaskGraphData.INPUT_NODE: 37 | nodes.append(InputTask.new(self)) 38 | elif node["type"] == TaskGraphData.OUTPUT_NODE: 39 | nodes.append(OutputTask.new(self)) 40 | 41 | for connection in resource.connections: 42 | var from = nodes[connection["from"]] 43 | var to = nodes[connection["to"]] 44 | from.outputs.append([connection["from_port"], to, connection["to_port"]]) 45 | to.inputs_needed += 1 46 | 47 | var start_nodes = [] 48 | for node in nodes: 49 | if not "inputs_needed" in node or node.inputs_needed == 0: 50 | start_nodes.append(node) 51 | 52 | thread_pool = ThreadPool.new(min(nodes.size(), max_threads if max_threads != 0 else OS.get_processor_count() * 2)) 53 | 54 | for node in start_nodes: 55 | thread_pool.add_task(node, "_run") 56 | 57 | func set_resource(p_resource): 58 | resource = p_resource 59 | 60 | _inputs.clear() 61 | _outputs.clear() 62 | 63 | if resource == null: return 64 | 65 | for node in resource.nodes: 66 | if node["type"] == TaskGraphData.TASK_NODE: 67 | pass 68 | elif node["type"] == TaskGraphData.INPUT_NODE: 69 | _inputs[node["data"]] = null 70 | elif node["type"] == TaskGraphData.OUTPUT_NODE: 71 | _outputs[node["data"]] = null 72 | else: 73 | printerr("Unknown type") 74 | 75 | func set_input(key, value): 76 | if not _inputs.has(key): 77 | printerr("Input not found.") 78 | return 79 | _inputs[key] = value 80 | 81 | func get_output(key): 82 | return _outputs[key] 83 | 84 | func get_progress(): 85 | var progress = 0.0 86 | var total_progress = 0.0 87 | for node in nodes: 88 | var node_total = node.get_total_progress() 89 | total_progress += node_total 90 | if node.status == STATUS_RUNNING: 91 | progress += node.get_progress() 92 | elif node.status == STATUS_FINISHED: 93 | progress += node_total 94 | return progress / total_progress 95 | 96 | 97 | 98 | 99 | 100 | 101 | enum TaskStatus{ 102 | STATUS_WAITING 103 | STATUS_RUNNING 104 | STATUS_FINISHED 105 | } 106 | 107 | class Task extends Reference: 108 | var status = STATUS_WAITING 109 | 110 | var inputs_needed = 0 111 | var outputs = [] 112 | 113 | var outer 114 | var task 115 | var mutex = Mutex.new() 116 | 117 | func _init(p_outer, p_task): 118 | outer = p_outer 119 | task = p_task 120 | 121 | func add_input(input_name, value): 122 | task.set(input_name, value) 123 | mutex.lock() 124 | inputs_needed -= 1 125 | if inputs_needed == 0: 126 | outer.thread_pool.add_task(self, "_run") 127 | mutex.unlock() 128 | 129 | func _run(): 130 | status = STATUS_RUNNING 131 | task._run() 132 | status = STATUS_FINISHED 133 | for output in outputs: 134 | output[1].add_input(output[2], task.get(output[0])) 135 | 136 | func get_progress(): 137 | return task.progress if "progress" in task else 0 138 | 139 | func get_total_progress(): 140 | return task.total_progress if "total_progress" in task else 1 141 | 142 | class InputTask extends Reference: 143 | var status = STATUS_WAITING 144 | 145 | var outputs = [] 146 | 147 | var outer 148 | 149 | func _init(p_outer): 150 | outer = p_outer 151 | 152 | func _run(): 153 | status = STATUS_RUNNING 154 | for output in outputs: 155 | output[1].add_input(output[2], outer._inputs[output[0]]) 156 | status = STATUS_FINISHED 157 | 158 | func get_progress(): 159 | return 1 if status == STATUS_FINISHED else 0 160 | 161 | func get_total_progress(): 162 | return 1 163 | 164 | class OutputTask extends Reference: 165 | var status = STATUS_WAITING 166 | 167 | var inputs_needed = 0 168 | 169 | var outer 170 | 171 | func _init(p_outer): 172 | outer = p_outer 173 | 174 | func add_input(input_name, value): 175 | status = STATUS_RUNNING 176 | outer.call_deferred("_set_output", input_name, value) 177 | status = STATUS_FINISHED 178 | 179 | func get_progress(): 180 | return 1 if status == STATUS_FINISHED else 0 181 | 182 | func get_total_progress(): 183 | return 1 184 | 185 | func _set_output(output_name, value): 186 | _outputs[output_name] = value 187 | _outputs_left -= 1 188 | emit_signal("output_ready", output_name, value) 189 | if _outputs_left == 0: 190 | thread_pool.quit() 191 | emit_signal("finished") 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | # Editor functions 200 | # Only in 3.1 201 | #func _get_property_list(): 202 | # var properties = [ 203 | # { 204 | # "name": "resource", 205 | # "type": TYPE_OBJECT, 206 | # "hint": PROPERTY_HINT_RESOURCE_TYPE, 207 | # "hint_string": "TaskGraphData", 208 | # "usage": PROPERTY_USAGE_DEFAULT 209 | # } 210 | # ] 211 | # return properties 212 | # 213 | #func _get(key): 214 | # if key == "resource": 215 | # return resource 216 | # 217 | #func _set(key, value): 218 | # if key == "resource": 219 | # set_resource(value) 220 | # else: 221 | # return false 222 | # return true 223 | 224 | 225 | -------------------------------------------------------------------------------- /addons/task_graph/task_manager_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopoldek/Multi-Task-Manager/8cac5f1102a43d74d9843887196d87015d80c46c/addons/task_graph/task_manager_icon.png -------------------------------------------------------------------------------- /addons/task_graph/task_manager_icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/task_manager_icon.png-75afaaf04af99edca80632d634542d15.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://addons/task_graph/task_manager_icon.png" 10 | dest_files=[ "res://.import/task_manager_icon.png-75afaaf04af99edca80632d634542d15.stex" ] 11 | 12 | [params] 13 | 14 | compress/mode=0 15 | compress/lossy_quality=0.7 16 | compress/hdr_mode=0 17 | compress/normal_map=0 18 | flags/repeat=0 19 | flags/filter=true 20 | flags/mipmaps=false 21 | flags/anisotropic=false 22 | flags/srgb=2 23 | process/fix_alpha_border=true 24 | process/premult_alpha=false 25 | process/HDR_as_SRGB=false 26 | stream=false 27 | size_limit=0 28 | detect_3d=true 29 | svg/scale=1.0 30 | -------------------------------------------------------------------------------- /addons/task_graph/thread_utils/atomic_list.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | var _list = [] 4 | var _mutex = Mutex.new() 5 | 6 | func push_front(value): 7 | _mutex.lock() 8 | _list.push_front(value) 9 | _mutex.unlock() 10 | 11 | func push_back(value): 12 | _mutex.lock() 13 | _list.push_back(value) 14 | _mutex.unlock() 15 | 16 | func pop_front(): 17 | _mutex.lock() 18 | var value = _list.pop_front() 19 | _mutex.unlock() 20 | return value 21 | 22 | func pop_back(): 23 | _mutex.lock() 24 | var value = _list.pop_back() 25 | _mutex.unlock() 26 | return value 27 | 28 | func erase(value): 29 | _mutex.lock() 30 | _list.erase(value) 31 | _mutex.unlock() 32 | 33 | func get(idx): 34 | _mutex.lock() 35 | var value = _list[idx] 36 | _mutex.unlock() 37 | return value 38 | 39 | func set(idx, value): 40 | _mutex.lock() 41 | _list[idx] = value 42 | _mutex.unlock() 43 | 44 | func size(): 45 | _mutex.lock() 46 | var size = _list.size() 47 | _mutex.unlock() 48 | return size 49 | -------------------------------------------------------------------------------- /addons/task_graph/thread_utils/thread_gate.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func _ready(): 4 | pass 5 | -------------------------------------------------------------------------------- /addons/task_graph/thread_utils/thread_pool.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | const AtomicList = preload("atomic_list.gd") 4 | 5 | var _amount 6 | var _sem = Semaphore.new() 7 | var _quit = false 8 | var _tasks = AtomicList.new() 9 | 10 | func _init(p_amount = OS.get_processor_count() * 2): 11 | _amount = p_amount 12 | var x = 0 13 | while x < _amount: 14 | var thread = Thread.new() 15 | thread.start(self, "_run", thread) 16 | x += 1 17 | 18 | func _run(thread): 19 | while true: 20 | _sem.wait() 21 | if _quit: 22 | thread.call_deferred("wait_to_finish") 23 | return 24 | var task = _tasks.pop_front() 25 | task[0].callv(task[1], task[2]) 26 | 27 | func add_task(object, function, args = []): 28 | #object.callv(function, args) # For calling on the main thread 29 | _tasks.push_back([object, function, args]) 30 | _sem.post() 31 | 32 | func quit(): 33 | _quit = true 34 | var x = 0 35 | while x < _amount: 36 | _sem.post() 37 | x += 1 38 | 39 | func _notification(what): 40 | if what == NOTIFICATION_PREDELETE and not _quit: 41 | quit() --------------------------------------------------------------------------------