├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── addons └── jqs │ ├── icons │ ├── job.png │ ├── job.png.import │ ├── queue.png │ └── queue.png.import │ ├── plugin.cfg │ ├── plugin.gd │ └── scripts │ ├── job.gd │ ├── job_group.gd │ ├── job_node.gd │ ├── job_queue.gd │ ├── job_queue_manager.gd │ └── worker_pool.gd ├── dem5749.tmp ├── demo_scene.tscn ├── icon.svg ├── icon.svg.import ├── images ├── Callback - Code.png ├── Callback - Code.png.import ├── JobNode-Code.png ├── JobNode-Code.png.import ├── JobNodeInspector.png ├── JobNodeInspector.png.import ├── JobQueueingSystemInspector.png ├── JobQueueingSystemInspector.png.import ├── JobQueueingSystemNodeTree.png ├── JobQueueingSystemNodeTree.png.import ├── MyJob - Code.png ├── MyJob - Code.png.import ├── high-level-design.png ├── high-level-design.png.import └── queue-asset-icon.png ├── project.godot └── scripts ├── callback_job.gd ├── demo_scene.gd ├── my_job.gd └── other_job.gd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Properly detect languages on Github 2 | *.h linguist-language=cpp 3 | *.inc linguist-language=cpp 4 | thirdparty/* linguist-vendored 5 | 6 | # Normalize EOL for all files that Git considers text files 7 | * text=auto eol=lf 8 | # Except for bat files, which are Windows only files 9 | *.bat eol=crlf 10 | # And some test files where the EOL matters 11 | *.test.txt -text 12 | 13 | # The above only works properly for Git 2.10+, so for older versions 14 | # we need to manually list the binary files we don't want modified. 15 | *.icns binary 16 | *.ico binary 17 | *.jar binary 18 | *.png binary 19 | *.ttf binary 20 | *.tza binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .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 16 | 17 | build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 João Henrique Machado Silva 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node Based Job Queueing System 2 | === 3 | [![Maintenance](https://img.shields.io/badge/maintenance-actively%20maintained-brightgreen.svg)](https://deps.rs/repo/github/joaoh82/nodebased-job-queueing-system) 4 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 5 | 6 | `Godot Job Queueing System`, is a node based thread-safe and synchronous Dispatch Job System for Godot. 7 | 8 | > What I cannot create, I do not understand. 9 | > — Richard Feynman 10 | 11 | High Level Design 15 | 16 | ### Requirements 17 | Before you begin, ensure you have met the following requirements: 18 | * Godot Engine 4.2.1+ 19 | 20 | ## How to Setup? 21 | 22 | 1. If it does not yet exists, create a folder called `addons\` in the root of your project 23 | 2. Copy the `addons\jqs\` to your `addons\` folder in your project 24 | 3. Go to Project -> Project Settings -> Plugins -> And Enable the Plugin 25 | 4. Setup is DONE! 26 | 27 | ## Usage 28 | 29 | ### Using the Node System 30 | 31 | Node System 35 | 36 | 37 | You can also use the Job Queueing System using nodes and in a very easy way. 38 | 39 | #### Add a JobQueueManager node to your node tree and configure it in the inspector. 40 | 41 | **Node: JobQueueManager** 42 | 43 | Parent node that manages the jobs and abstract the JobQueue class 44 | 45 | Node System 49 | 50 | Properties: 51 | 52 | `Auto Start` - Determines is jobs should start running right at the start. 53 | 54 | `Thread Count` - Determines how many threads should be used by the Job Queueing System 55 | 56 | Methods: 57 | 58 | `start_job_queue() -> void:` - Starts the Jobs Queue. Only needed if Auto Start is not true. 59 | 60 | **Node: JobNode** 61 | 62 | Node System 66 | 67 | Before adding this node to the tree you should create a script, extend from `JobNode` and implement the `execute` method. 68 | 69 | Node System 73 | 74 | Properties: 75 | 76 | Node System 80 | 81 | 82 | `Args` - Array of values of any type that will be passed as parameters to the `execute` method you implement for the node. The number of parameters and types should match the ones in the `execute` method. 83 | 84 | `Then` - Callback JobNode to be called when this job is done. Ideally you would nest these nodes so it is easier to understand when looking at the tree. 85 | 86 | `Is Callback` - You should check this is this JobNode is a callback of another JobNode 87 | 88 | Node System 92 | 93 | The callback JobNode `execute` method should always have a result parameter that is passed from the parent JobNode. And the parent JobNode `execute` should always have a result value as a return type. 94 | 95 | **This uses a Unix approach where if you pipe one program into another, the output of one is always the input of the next one.** 96 | 97 | ### Using the classes directly 98 | 99 | #### Creating a new JobQueue 100 | 101 | ```gdscript 102 | # Instantiate - Ideally in the top of the class (Check Example) 103 | var job_queue : JobQueue = JobQueue.new() 104 | # Create a serial queue, which call concurrent but sets the thread number to 1 105 | job_queue.create_serial() 106 | # Or concurrent queue, with the number of available processors or however many threads you would like. 107 | job_queue.create_concurrent(OS.get_processor_count()) 108 | # (if you do neither, JobQueue will run in synchronous mode) 109 | ``` 110 | 111 | #### Distaching Single Jobs 112 | 113 | ```gdscript 114 | job_queue.dispatch(self.job_method_name.bind("optional", "method", "arguments")) 115 | ``` 116 | 117 | #### Distaching Jobs with callbacks 118 | The callback job will only start after the first has finished 119 | ```gdscript 120 | job_queue.dispatch(self.job_method_name.bind("optional", "method", "arguments")).then(self.result_callback) 121 | ``` 122 | **Important:** The `self.result_callback` takes an `results` array an argument and should have the following signature, and since it is called via a signal call it needs to have the `result : Array` as a parameter: 123 | ```gdscript 124 | func result_callback(result : Array) -> void: 125 | ... 126 | ``` 127 | `results` - Is of type array and has the results from the first job method, if any, otherwise will be null. 128 | 129 | 130 | #### Distaching Jobs in Groups 131 | 132 | ```gdscript 133 | job_queue.dispatch_group([ 134 | self.method_name1.bind("optional", "arguments"), 135 | self.method_name2, 136 | self.method_name3, 137 | ]).then_deferred(self.group_results_callback) 138 | ``` 139 | 140 | - `dispatch_group` takes an Array of `Callable` with the option of having a callback method to be called after all jobs are done. 141 | - The jobs passed in the `dispatch_group` will be called one after the other. This may also vary depending if you are running with more than 1 thread or not. 142 | 143 | #### Distaching Jobs in Couroutine Style 144 | ```gdscript 145 | var job = job_queue.dispatch(self.mymethod) 146 | # Waits for job to be finished 147 | var mymethod_result = await job.job_finished 148 | # Dispaches a group of jobs after `job_finished` signal is called. 149 | var job_group = job_queue.dispatch_group([self.method1, self.method2]) 150 | # Waits for all jobs in group to be finished 151 | var group_method_results = await job_group.finished 152 | ``` 153 | 154 | #### Automatically detecting when all jobs queued are finished 155 | 156 | ```gdscript 157 | # Connecting to signal 158 | job_queue.all_jobs_finished.connect(self._on_all_jobs_finished) 159 | 160 | await job_queue.all_jobs_finished 161 | ``` 162 | 163 | - JobQueue, Job and all classes extends RefCounted, so no need to worry about freeing it manually 164 | 165 | ## API 166 | 167 | ### **JobQueue** ([addons/jqs/scripts/job_queue.gd](addons/jqs/scripts/job_queue.gd)): 168 | 169 | `signal all_jobs_finished()` 170 | - Emitted when the last queued Job finishes. 171 | This signal is emitted deferred, so it is safe to call non 172 | [Thread-safe APIs](https://docs.godotengine.org/en/stable/tutorials/performance/thread_safe_apis.html). 173 | 174 | 175 | `create_serial()` 176 | - Creates a Thread of execution to process jobs. 177 | If threading is not supported, fallback to synchronous mode. 178 | If queue was already serial, this is a no-op, otherwise 179 | calls `shutdown` and create a new Thread. 180 | 181 | `create_concurrent(thread_count: int = 1)` 182 | - Creates `thread_count` Threads of execution to process jobs. 183 | If threading is not supported, fallback to synchronous mode. 184 | If queue was already concurrent with `thread_count` Threads, 185 | this is a no-op, otherwise calls `shutdown` and create new Threads. 186 | If `thread_count <= 1`, creates a serial queue. 187 | 188 | 189 | `dispatch(callable: Callable) -> Job` 190 | - Create a Job for executing `callable`. 191 | On threaded mode, the Job will be executed on a Thread when there is one available. 192 | On synchronous mode, the Job will be executed on the next frame. 193 | 194 | `dispatch_group(job_list: Array[Callable]) -> JobGroup` 195 | - Create all jobs in `job_list` by calling `dispatch` on each value, returning the JobGroup associated with them. 196 | 197 | `is_threaded() -> bool` 198 | - Returns whether queue is threaded or synchronous. 199 | 200 | `get_thread_count() -> int` 201 | - Returns the current Thread count. 202 | Returns 0 on synchronous mode. 203 | 204 | `size() -> int` 205 | - Returns the number of queued jobs. 206 | 207 | `is_empty() -> bool` 208 | - Returns whether queue is empty, that is, there are no jobs queued. 209 | 210 | `clear()` 211 | - Cancel pending Jobs, clearing the current queue. 212 | Jobs that are being processed will still run to completion. 213 | 214 | `shutdown()` 215 | - Cancel pending Jobs, wait and release the used Threads. 216 | The queue now runs in synchronous mode, so that new jobs will run in the main thread. 217 | Call `create_serial` or `create_concurrent` to recreate the worker threads. 218 | This method is called automatically on `NOTIFICATION_PREDELETE`. 219 | It is safe to call this more than once. 220 | 221 | 222 | ### **Job** 223 | 224 | `signal job_finished(result)` 225 | - Emitted after Job executes, passing the result as argument. 226 | The signal is emitted in the same Thread that executed the Job, so you 227 | need to connect with `CONNECT_DEFERRED` if you want to call non [Thread-safe 228 | APIs](https://docs.godotengine.org/en/stable/tutorials/performance/thread_safe_apis.html). 229 | 230 | `then(callable: Callable, flags: int = 0)` 231 | - Helper method for connecting to the "finished" signal. 232 | This enables the following pattern: 233 | ```gdscript 234 | job_queue.dispatch(job).then(continuation_callable) 235 | ``` 236 | 237 | `then_deferred(callable: Callable, flags: int = 0)` 238 | - Alias for `then` that also adds `CONNECT_DEFERRED` to flags. 239 | ```gdscript 240 | job_queue.dispatch(job).then_deferred(continuation_callable) 241 | ``` 242 | 243 | 244 | ### **JobGroup** 245 | 246 | `signal finished(results)` 247 | - Emitted after all Jobs in the group finish, passing the results Array as argument. 248 | The signal is emitted in the same Thread that executed the last pending Job, so you 249 | need to connect with `CONNECT_DEFERRED` if you want to call non [Thread-safe 250 | APIs](https://docs.godotengine.org/en/stable/tutorials/performance/thread_safe_apis.html). 251 | 252 | `then(callable: Callable, flags: int = 0)` 253 | - Helper method for connecting to the "finished" signal. 254 | This enables the following pattern: 255 | ```gdscript 256 | job_queue.dispatch_group(job_list).then(continuation_callable) 257 | ``` 258 | 259 | `then_deferred(callable: Callable, flags: int = 0)` 260 | - Alias for `then` that also adds `CONNECT_DEFERRED` to flags. 261 | ```gdscript 262 | job_queue.dispatch_group(job_list).then_deferred(continuation_callable) 263 | ``` 264 | 265 | 266 | ## Contributing 267 | **Pull requests are warmly welcome!!!** 268 | 269 | For major changes, please [open an issue](https://github.com/joaoh82/nodebased-job-queueing-system/issues/new) first and let's talk about it. We are all ears! 270 | 271 | If you'd like to contribute, please fork the repository and make changes as you'd like and shoot a Pull Request our way! 272 | 273 | **Please make sure to update tests as appropriate.** 274 | 275 | If you feel like you need it go check the GitHub documentation on [creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). 276 | 277 | ### Code of Conduct 278 | 279 | Contribution to the project is organized under the terms of the 280 | Contributor Covenant, the maintainer of Godot Job Queueing System, [@joaoh82](https://github.com/joaoh82), promises to intervene to uphold that code of conduct. 281 | 282 | ### Contact 283 | 284 | If you want to contact me you can reach me at . 285 | 286 | ### Work based and inspired by: 287 | https://github.com/gilzoide/godot-dispatch-queue -------------------------------------------------------------------------------- /addons/jqs/icons/job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/addons/jqs/icons/job.png -------------------------------------------------------------------------------- /addons/jqs/icons/job.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cnf08wtfsb4or" 6 | path="res://.godot/imported/job.png-384ed33a0e9eb45fc1408e2031e11928.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/jqs/icons/job.png" 14 | dest_files=["res://.godot/imported/job.png-384ed33a0e9eb45fc1408e2031e11928.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 | -------------------------------------------------------------------------------- /addons/jqs/icons/queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/addons/jqs/icons/queue.png -------------------------------------------------------------------------------- /addons/jqs/icons/queue.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cygsh1e0wrrae" 6 | path="res://.godot/imported/queue.png-000eacd3759693daf66e2b635bbe28df.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/jqs/icons/queue.png" 14 | dest_files=["res://.godot/imported/queue.png-000eacd3759693daf66e2b635bbe28df.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 | -------------------------------------------------------------------------------- /addons/jqs/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Node Based Job Queueing System" 4 | description="A node based thread-safe and synchronous Dispatch Job System for Godot." 5 | author="The Polyglot Programmer" 6 | version="1.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/jqs/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | class_name JQS 4 | 5 | func _enter_tree(): 6 | add_custom_type("JobQueueManager", "Node", preload("res://addons/jqs/scripts/job_queue_manager.gd"), preload("res://addons/jqs/icons/queue.png")) 7 | add_custom_type("Job", "Node", preload("res://addons/jqs/scripts/job_node.gd"), preload("res://addons/jqs/icons/job.png")) 8 | 9 | 10 | func _exit_tree(): 11 | remove_custom_type("JobQueueManager") 12 | remove_custom_type("Job") 13 | -------------------------------------------------------------------------------- /addons/jqs/scripts/job.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | ## A single job to be executed. 3 | ## 4 | ## Connect to the `finished` signal to receive the result either manually 5 | ## or by calling `then`/`then_deferred`. 6 | class_name Job 7 | 8 | ## Emitted after all Jobs in the group finish, passing the results Array as argument. 9 | ## The signal is emitted in the same Thread that executed the last pending Job, so you 10 | ## need to connect with CONNECT_DEFERRED if you want to call non Thread-safe APIs. 11 | ## The job being connected via then/then_deferred will need to have the following signature: 12 | ## func task_02(result: Array) 13 | signal job_finished(result) 14 | 15 | var callable: Callable 16 | var group: JobGroup = null 17 | var id_in_group: int = -1 18 | 19 | 20 | ## Helper method for connecting to the "finished" signal. 21 | ## 22 | ## This enables the following pattern: 23 | ## job_queue.dispatch(callable).then(continuation_callable) 24 | func then(_callable: Callable, flags: int = 0) -> int: 25 | return job_finished.connect(_callable, flags | CONNECT_ONE_SHOT) 26 | 27 | 28 | ## Alias for `then` that also adds CONNECT_DEFERRED to flags. 29 | func then_deferred(_callable: Callable, flags: int = 0) -> int: 30 | return then(_callable, flags | CONNECT_DEFERRED) 31 | 32 | 33 | func execute() -> void: 34 | var result = callable.call() 35 | job_finished.emit(result) 36 | if group: 37 | group.mark_job_finished(self, result) 38 | -------------------------------------------------------------------------------- /addons/jqs/scripts/job_group.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | ## Helper object that emits "finished" after all Jobs in a list finish. 3 | class_name JobGroup 4 | 5 | ## Emitted after Job executes, passing the result as argument. 6 | ## The signal is emitted in the same Thread that executed the Job, so you 7 | ## need to connect with CONNECT_DEFERRED if you want to call non Thread-safe APIs. 8 | signal finished(results) 9 | 10 | var job_count := 0 11 | var job_results = [] 12 | var mutex: Mutex = null 13 | 14 | 15 | func _init(threaded: bool) -> void: 16 | if threaded: 17 | mutex = Mutex.new() 18 | 19 | 20 | ## Helper method for connecting to the "finished" signal. 21 | ## 22 | ## This enables the following pattern: 23 | ## job_queue.dispatch_group(job_list).then(continuation_callable) 24 | func then(callable: Callable, flags: int = 0) -> int: 25 | return finished.connect(callable, flags | CONNECT_ONE_SHOT) 26 | 27 | 28 | ## Alias for `then` that also adds CONNECT_DEFERRED to flags. 29 | func then_deferred(callable: Callable, flags: int = 0) -> int: 30 | return then(callable, flags | CONNECT_DEFERRED) 31 | 32 | 33 | func add_job(job: Job) -> void: 34 | job.group = self 35 | job.id_in_group = job_count 36 | job_count += 1 37 | job_results.resize(job_count) 38 | 39 | 40 | func mark_job_finished(job: Job, result) -> void: 41 | if mutex: 42 | mutex.lock() 43 | job_count -= 1 44 | job_results[job.id_in_group] = result 45 | var is_last_job = job_count == 0 46 | if mutex: 47 | mutex.unlock() 48 | if is_last_job: 49 | finished.emit(job_results) 50 | -------------------------------------------------------------------------------- /addons/jqs/scripts/job_node.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/jqs/icons/job.png") 2 | extends Node 3 | class_name JobNode 4 | 5 | ## Arguments to be passed to the `execute` method of this job. 6 | @export var args : Array[Variant] 7 | ## JobNode to run when this is finished. And feed as parameters, 8 | ## the return value of this job execute. 9 | @export var then : JobNode 10 | ## If true, this is a callback. 11 | @export var is_callback : bool = false 12 | 13 | # INTERFACE 14 | 15 | #func execute(params...) -> void: 16 | #pass 17 | -------------------------------------------------------------------------------- /addons/jqs/scripts/job_queue.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | class_name JobQueue 3 | 4 | ## Signal emitted when the last job in queue finishes 5 | ## This signal is emitted deferred, so it is safe to call non Thread-safe APIs. 6 | signal all_jobs_finished() 7 | 8 | var _job_queue = [] 9 | var _workers: WorkerPool = null 10 | 11 | 12 | ## Creates a Thread of execution to process jobs. 13 | ## If queue was already serial, this is a no-op, otherwise calls `shutdown` and create a new Thread. 14 | func create_serial() -> void: 15 | create_concurrent(1) 16 | 17 | 18 | ## Creates `thread_count` Threads of execution to process jobs. 19 | ## If queue was already concurrent with `thread_count` Threads, this is a no-op. 20 | ## Otherwise calls `shutdown` and create new Threads. 21 | ## If `thread_count <= 1`, creates a serial queue. 22 | func create_concurrent(thread_count: int = 1) -> void: 23 | if thread_count == get_thread_count(): 24 | return 25 | 26 | if is_threaded(): 27 | shutdown() 28 | 29 | _workers = WorkerPool.new() 30 | var run_loop = self._run_loop.bind(_workers) 31 | for i in max(1, thread_count): 32 | var thread = Thread.new() 33 | _workers.threads.append(thread) 34 | thread.start(run_loop) 35 | 36 | 37 | func _run_loop(pool: WorkerPool) -> void: 38 | while true: 39 | pool.semaphore.wait() 40 | if pool.should_shutdown: 41 | break 42 | 43 | pool.mutex.lock() 44 | var job = _pop_job() 45 | pool.mutex.unlock() 46 | if job: 47 | job.execute() 48 | 49 | 50 | ## Create a Job for executing `callable`. 51 | ## On threaded mode, the Job will be executed on a Thread when there is one available. 52 | ## On synchronous mode, the Job will be executed on the next frame. 53 | func dispatch(callable: Callable) -> Job: 54 | var job = Job.new() 55 | if callable.is_valid(): 56 | job.callable = callable 57 | if is_threaded(): 58 | _workers.mutex.lock() 59 | _job_queue.append(job) 60 | _workers.mutex.unlock() 61 | _workers.semaphore.call_deferred("post") 62 | else: 63 | if _job_queue.is_empty(): 64 | call_deferred("_sync_run_next_job") 65 | _job_queue.append(job) 66 | else: 67 | push_error("Trying to dispatch an invalid callable, ignoring it") 68 | return job 69 | 70 | 71 | ## Create all jobs in `job_list` by calling `dispatch` on each value, 72 | ## returning the JobGroup associated with them. 73 | func dispatch_group(job_list: Array[Callable]) -> JobGroup: 74 | var group = JobGroup.new(is_threaded()) 75 | for callable in job_list: 76 | var job: Job = dispatch(callable) 77 | group.add_job(job) 78 | 79 | return group 80 | 81 | 82 | func _sync_run_next_job() -> void: 83 | var job = _pop_job() 84 | if job: 85 | job.execute() 86 | call_deferred("_sync_run_next_job") 87 | 88 | 89 | func _notification(what: int) -> void: 90 | if what == NOTIFICATION_PREDELETE and self: 91 | shutdown() 92 | 93 | 94 | ## Cancel pending Jobs, clearing the current queue. 95 | ## Jobs that are being processed will still run to completion. 96 | func clear() -> void: 97 | if is_threaded(): 98 | _workers.mutex.lock() 99 | _job_queue.clear() 100 | _workers.mutex.unlock() 101 | else: 102 | _job_queue.clear() 103 | 104 | 105 | ## Cancel pending Jobs, wait and release the used Threads. 106 | ## The queue now runs in synchronous mode, so that new jobs will run in the main thread. 107 | ## Call `create_serial` or `create_concurrent` to recreate the worker threads. 108 | ## This method is called automatically on `NOTIFICATION_PREDELETE`. 109 | ## It is safe to call this more than once. 110 | func shutdown() -> void: 111 | clear() 112 | if is_threaded(): 113 | var current_workers = _workers 114 | _workers = null 115 | current_workers.shutdown() 116 | 117 | ## Returns whether queue is threaded or synchronous. 118 | func is_threaded() -> bool: 119 | return _workers != null 120 | 121 | 122 | ## Returns the current Thread count. 123 | ## Returns 0 on synchronous mode. 124 | func get_thread_count() -> int: 125 | if is_threaded(): 126 | return _workers.threads.size() 127 | else: 128 | return 0 129 | 130 | 131 | ## Returns the number of queued jobs. 132 | func size() -> int: 133 | var result 134 | if is_threaded(): 135 | _workers.mutex.lock() 136 | result = _job_queue.size() 137 | _workers.mutex.unlock() 138 | else: 139 | result = _job_queue.size() 140 | return result 141 | 142 | 143 | ## Returns whether queue is empty, that is, there are no jobs queued. 144 | func is_empty() -> bool: 145 | return size() <= 0 146 | 147 | 148 | ## This function pops a job (job) from the job queue. 149 | ## If the job queue is empty after the pop, it defers the _on_last_job_finished function. 150 | ## It then returns the popped job. 151 | func _pop_job() -> Job: 152 | var job: Job = _job_queue.pop_front() 153 | if job and _job_queue.is_empty(): 154 | job.then_deferred(self._on_last_job_finished) 155 | return job 156 | 157 | 158 | func _on_last_job_finished(_result): 159 | if is_empty(): 160 | all_jobs_finished.emit() 161 | -------------------------------------------------------------------------------- /addons/jqs/scripts/job_queue_manager.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/jqs/icons/queue.png") 2 | ## Node that wraps a JobQueue. 3 | ## Creates the Threads when entering tree and shuts down when exiting tree. 4 | ## If `thread_count == 0`, runs queue in synchronous mode. 5 | ## If `thread_count < 0`, creates `OS.get_processor_count()` Threads. 6 | extends Node 7 | class_name JobQueueManager 8 | 9 | signal all_jobs_finished() 10 | 11 | ## Automatically starts the JobQueue 12 | @export var _auto_start : bool = true 13 | 14 | ## Number of Threads to use. 15 | @export var thread_count: int = -1: set = set_thread_count 16 | 17 | ## The JobQueue 18 | var _job_queue = JobQueue.new() 19 | 20 | func _enter_tree() -> void: 21 | set_thread_count(thread_count) 22 | 23 | 24 | func _exit_tree() -> void: 25 | _job_queue.shutdown() 26 | 27 | 28 | func _ready(): 29 | if _auto_start: 30 | # Starts the JobQueue if auto_start is true 31 | start_job_queue() 32 | 33 | 34 | ## Starts the JobQueue 35 | func start_job_queue() -> void: 36 | _traverse_job_nodes() 37 | 38 | 39 | ## Traverses the JobNode tree passing execute methods to the JobQueue 40 | ## and calls `then` if it exists 41 | func _traverse_job_nodes() -> void: 42 | for job_node in get_children(): 43 | if (job_node as JobNode).then and ((job_node as JobNode).then as JobNode).is_callback: 44 | dispatch((job_node as JobNode).execute.bindv((job_node as JobNode).args)).then(((job_node as JobNode).then as JobNode).execute) 45 | else: 46 | dispatch((job_node as JobNode).execute.bindv((job_node as JobNode).args)) 47 | 48 | 49 | ## Traverses the JobNode tree passing execute methods to the JobQueue recursively 50 | func _traverse_job_nodes_recursive(job_node : Node) -> void: 51 | if job_node is JobNode: 52 | dispatch((job_node as JobNode).execute.bindv((job_node as JobNode).args)) 53 | 54 | for child in job_node.get_children(): 55 | _traverse_job_nodes_recursive(child) 56 | 57 | 58 | ## Set the number of Threads 59 | func set_thread_count(value: int) -> void: 60 | if value < 0: 61 | value = OS.get_processor_count() 62 | thread_count = value 63 | if thread_count == 0: 64 | _job_queue.shutdown() 65 | else: 66 | _job_queue.create_concurrent(thread_count) 67 | 68 | 69 | # JobQueue wrappers 70 | func dispatch(callable: Callable) -> Job: 71 | return _job_queue.dispatch(callable) 72 | 73 | 74 | ## Create all jobs in `job_list` by calling `dispatch` on each value, 75 | func dispatch_group(job_list: Array[Callable]) -> JobGroup: 76 | return _job_queue.dispatch_group(job_list) 77 | 78 | 79 | ## Returns true if the JobQueue is threaded 80 | func is_threaded() -> bool: 81 | return _job_queue.is_threaded() 82 | 83 | 84 | ## Returns the current Thread count 85 | func get_thread_count() -> int: 86 | return _job_queue.get_thread_count() 87 | 88 | 89 | ## Returns the number of queued jobs 90 | func size() -> int: 91 | return _job_queue.size() 92 | 93 | 94 | ## Returns true if the JobQueue is empty 95 | func is_empty() -> bool: 96 | return _job_queue.is_empty() 97 | 98 | 99 | ## Cancel pending Jobs, clearing the current queue. 100 | func clear() -> void: 101 | _job_queue.clear() 102 | 103 | 104 | ## Cancel pending Jobs, wait and release the used Threads. 105 | ## The queue now runs in synchronous mode, so that new jobs will run in the main thread. 106 | ## Call `create_serial` or `create_concurrent` to recreate the worker threads. 107 | ## This method is called automatically on `NOTIFICATION_PREDELETE`. 108 | ## It is safe to call this more than once. 109 | func shutdown() -> void: 110 | _job_queue.shutdown() 111 | 112 | 113 | # Private functions 114 | func _on_all_jobs_finished() -> void: 115 | all_jobs_finished.emit() 116 | -------------------------------------------------------------------------------- /addons/jqs/scripts/worker_pool.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | class_name WorkerPool 3 | 4 | var threads: Array[Thread] = [] 5 | var should_shutdown := false 6 | var mutex := Mutex.new() 7 | var semaphore := Semaphore.new() 8 | 9 | 10 | func _notification(what: int) -> void: 11 | if what == NOTIFICATION_PREDELETE and self: 12 | shutdown() 13 | 14 | 15 | # Shuts down the worker pool. 16 | # 17 | # This function is called automatically when the worker pool is about to be 18 | # deleted. It ensures that all threads are stopped and the worker pool is 19 | # cleared. 20 | # 21 | # This function does not return anything. 22 | func shutdown() -> void: 23 | if threads.is_empty(): 24 | return 25 | should_shutdown = true 26 | for i in threads.size(): 27 | semaphore.post() 28 | for t in threads: 29 | if t.is_alive(): 30 | t.wait_to_finish() 31 | threads.clear() 32 | should_shutdown = false 33 | -------------------------------------------------------------------------------- /dem5749.tmp: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dkyeoj7hlvl1y"] 2 | 3 | [ext_resource type="Script" path="res://scripts/demo_scene.gd" id="1_8euh2"] 4 | [ext_resource type="Script" path="res://scripts/jqs/job_queue_manager.gd" id="1_trvsb"] 5 | 6 | [node name="DemoScene" type="Node3D"] 7 | script = ExtResource("1_8euh2") 8 | 9 | [node name="JobQueueManager" type="Node" parent="."] 10 | script = ExtResource("1_trvsb") 11 | thread_count = 1 12 | -------------------------------------------------------------------------------- /demo_scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=3 uid="uid://dkyeoj7hlvl1y"] 2 | 3 | [ext_resource type="Script" path="res://scripts/demo_scene.gd" id="1_8euh2"] 4 | [ext_resource type="Script" path="res://addons/jqs/scripts/job_queue_manager.gd" id="1_trvsb"] 5 | [ext_resource type="Script" path="res://scripts/my_job.gd" id="3_nmfh5"] 6 | [ext_resource type="Script" path="res://scripts/callback_job.gd" id="4_dlhpc"] 7 | [ext_resource type="Script" path="res://scripts/other_job.gd" id="4_w78a2"] 8 | 9 | [node name="DemoScene" type="Node3D" node_paths=PackedStringArray("_job_queue_manager")] 10 | script = ExtResource("1_8euh2") 11 | _job_queue_manager = NodePath("JobQueueManager") 12 | 13 | [node name="JobQueueManager" type="Node" parent="."] 14 | script = ExtResource("1_trvsb") 15 | thread_count = 2 16 | 17 | [node name="MyJob" type="Node" parent="JobQueueManager" node_paths=PackedStringArray("then")] 18 | script = ExtResource("3_nmfh5") 19 | args = [0, "josh"] 20 | then = NodePath("CallbackJob") 21 | 22 | [node name="CallbackJob" type="Node" parent="JobQueueManager/MyJob"] 23 | script = ExtResource("4_dlhpc") 24 | is_callback = true 25 | 26 | [node name="OtherJob" type="Node" parent="JobQueueManager"] 27 | script = ExtResource("4_w78a2") 28 | args = [0] 29 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ca6gc6u0eilyy" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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 38 | -------------------------------------------------------------------------------- /images/Callback - Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/Callback - Code.png -------------------------------------------------------------------------------- /images/Callback - Code.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cpltbyud0ew71" 6 | path="res://.godot/imported/Callback - Code.png-e90a2b6a933cae19471e203c887b3698.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/Callback - Code.png" 14 | dest_files=["res://.godot/imported/Callback - Code.png-e90a2b6a933cae19471e203c887b3698.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 | -------------------------------------------------------------------------------- /images/JobNode-Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/JobNode-Code.png -------------------------------------------------------------------------------- /images/JobNode-Code.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dvnttu72jnypa" 6 | path="res://.godot/imported/JobNode-Code.png-1f3483f4b872aee08569ad7da8481bcc.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/JobNode-Code.png" 14 | dest_files=["res://.godot/imported/JobNode-Code.png-1f3483f4b872aee08569ad7da8481bcc.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 | -------------------------------------------------------------------------------- /images/JobNodeInspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/JobNodeInspector.png -------------------------------------------------------------------------------- /images/JobNodeInspector.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://mkwdbovkt48w" 6 | path="res://.godot/imported/JobNodeInspector.png-d71cdd3b194255b56f8686540493ba58.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/JobNodeInspector.png" 14 | dest_files=["res://.godot/imported/JobNodeInspector.png-d71cdd3b194255b56f8686540493ba58.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 | -------------------------------------------------------------------------------- /images/JobQueueingSystemInspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/JobQueueingSystemInspector.png -------------------------------------------------------------------------------- /images/JobQueueingSystemInspector.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cn57pe71i1ocn" 6 | path="res://.godot/imported/JobQueueingSystemInspector.png-774021f6a48bdac29b923c49aa2d4d9b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/JobQueueingSystemInspector.png" 14 | dest_files=["res://.godot/imported/JobQueueingSystemInspector.png-774021f6a48bdac29b923c49aa2d4d9b.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 | -------------------------------------------------------------------------------- /images/JobQueueingSystemNodeTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/JobQueueingSystemNodeTree.png -------------------------------------------------------------------------------- /images/JobQueueingSystemNodeTree.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://clthgmfd48u0d" 6 | path="res://.godot/imported/JobQueueingSystemNodeTree.png-c51732fa6acc36dd1117bf47acafefec.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/JobQueueingSystemNodeTree.png" 14 | dest_files=["res://.godot/imported/JobQueueingSystemNodeTree.png-c51732fa6acc36dd1117bf47acafefec.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 | -------------------------------------------------------------------------------- /images/MyJob - Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/MyJob - Code.png -------------------------------------------------------------------------------- /images/MyJob - Code.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dny3vebgt5pu7" 6 | path="res://.godot/imported/MyJob - Code.png-490f901d542013b46a7a94cd0c440c69.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/MyJob - Code.png" 14 | dest_files=["res://.godot/imported/MyJob - Code.png-490f901d542013b46a7a94cd0c440c69.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 | -------------------------------------------------------------------------------- /images/high-level-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/high-level-design.png -------------------------------------------------------------------------------- /images/high-level-design.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ceo4s6i26cdcj" 6 | path="res://.godot/imported/high-level-design.png-8b65facae08b4dda840a14939f535274.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://images/high-level-design.png" 14 | dest_files=["res://.godot/imported/high-level-design.png-8b65facae08b4dda840a14939f535274.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 | -------------------------------------------------------------------------------- /images/queue-asset-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoh82/nodebased-job-queueing-system/c60fa0d5950a382d16648833bff69c0a87c195c5/images/queue-asset-icon.png -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="JobQueueingSystem" 14 | run/main_scene="res://demo_scene.tscn" 15 | config/features=PackedStringArray("4.2", "Forward Plus") 16 | config/icon="res://icon.svg" 17 | 18 | [editor_plugins] 19 | 20 | enabled=PackedStringArray("res://addons/jqs/plugin.cfg") 21 | -------------------------------------------------------------------------------- /scripts/callback_job.gd: -------------------------------------------------------------------------------- 1 | extends JobNode 2 | class_name CallbackJob 3 | 4 | func execute(_result): 5 | printt("nested_job","_execute") 6 | for i in range(1000): 7 | printt(i) 8 | -------------------------------------------------------------------------------- /scripts/demo_scene.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | 3 | ## JobQueueManager Node 4 | @export var _job_queue_manager : JobQueueManager 5 | 6 | #var job_queue : JobQueue = JobQueue.new() 7 | 8 | func _ready(): 9 | # Setting JobQueue to serial and single threaded 10 | #job_queue.create_serial() 11 | 12 | # Dispatching a group of jobs 13 | #var job_list : Array[Callable] 14 | #job_list.append(_task_01) 15 | #job_list.append(_task_02.bind(0)) 16 | #job_list.append(_task_03) 17 | #job_queue.dispatch_group(job_list) 18 | 19 | # Dispatching single jobs 20 | #job_queue.dispatch(self._task_01).then(self._task_02) 21 | #job_queue.dispatch(_task_03) 22 | 23 | pass 24 | 25 | 26 | func _task_01() -> int: 27 | printt("_task_01 executing...") 28 | for i in range(1000): 29 | printt(i) 30 | printt("_task_01 done...") 31 | 32 | return 1 33 | 34 | 35 | func _task_02(_result) -> void: 36 | printt("_task_02 executing...") 37 | #printt("result", result) 38 | printt("_task_02 done...") 39 | 40 | 41 | func _task_03() -> void: 42 | printt("_task_03 executing...") 43 | 44 | printt("_task_03 done...") 45 | -------------------------------------------------------------------------------- /scripts/my_job.gd: -------------------------------------------------------------------------------- 1 | extends JobNode 2 | class_name MyJob 3 | 4 | func execute(a, b) -> void: 5 | printt("my_job","_execute", a, b) 6 | 7 | -------------------------------------------------------------------------------- /scripts/other_job.gd: -------------------------------------------------------------------------------- 1 | extends JobNode 2 | class_name OtherJob 3 | 4 | func execute(a): 5 | printt("other_job","_execute", a) 6 | --------------------------------------------------------------------------------