├── .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 | [](https://deps.rs/repo/github/joaoh82/nodebased-job-queueing-system)
4 | [](./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 |
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 |
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 |
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 |
66 |
67 | Before adding this node to the tree you should create a script, extend from `JobNode` and implement the `execute` method.
68 |
69 |
73 |
74 | Properties:
75 |
76 |
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 |
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 |
--------------------------------------------------------------------------------