-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdispatch_queue.gd
267 lines (197 loc) · 5.91 KB
/
dispatch_queue.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
extends Reference
signal all_tasks_finished()
class TaskGroup:
"""
Helper object that emits `finished` after all Tasks in a list finish.
"""
extends Reference
signal finished(results)
var task_count = 0
var task_results = []
var mutex: Mutex = null
func _init(threaded: bool) -> void:
if threaded:
mutex = Mutex.new()
func then(signal_responder: Object, method: String, binds: Array = [], flags: int = 0) -> int:
"""
Helper method for connecting to the `finished` signal.
This enables the following pattern:
dispatch_queue.dispatch(object, method).then(signal_responder, method)
"""
if signal_responder.has_method(method):
return connect("finished", signal_responder, method, binds, flags | CONNECT_ONESHOT)
else:
push_error("Object '%s' has no method named %s" % [signal_responder, method])
return ERR_METHOD_NOT_FOUND
func then_deferred(signal_responder: Object, method: String, binds: Array = [], flags: int = 0) -> int:
"""
Helper method for connecting to the `finished` signal with deferred flag
"""
return then(signal_responder, method, binds, flags | CONNECT_DEFERRED)
func add_task(task) -> void:
task.group = self
task.id_in_group = task_count
task_count += 1
task_results.resize(task_count)
func mark_task_finished(task, result) -> void:
if mutex:
mutex.lock()
task_count -= 1
task_results[task.id_in_group] = result
var is_last_task = task_count == 0
if mutex:
mutex.unlock()
if is_last_task:
emit_signal("finished", task_results)
class Task:
"""
A single task to be executed.
Connect to the `finished` signal to receive the result either manually
or by calling `then`/`then_deferred`.
"""
extends Reference
signal finished(result)
var object: Object
var method: String
var args: Array
var group: TaskGroup = null
var id_in_group: int = -1
func then(signal_responder: Object, method: String, binds: Array = [], flags: int = 0) -> int:
"""
Helper method for connecting to the `finished` signal.
This enables the following pattern:
dispatch_queue.dispatch(object, method).then(signal_responder, method)
"""
if signal_responder.has_method(method):
return connect("finished", signal_responder, method, binds, flags | CONNECT_ONESHOT)
else:
push_error("Object '%s' has no method named %s" % [signal_responder, method])
return ERR_METHOD_NOT_FOUND
func then_deferred(signal_responder: Object, method: String, binds: Array = [], flags: int = 0) -> int:
"""
Helper method for connecting to the `finished` signal with deferred flag
"""
return then(signal_responder, method, binds, flags | CONNECT_DEFERRED)
func execute() -> void:
var result = object.callv(method, args)
emit_signal("finished", result)
if group:
group.mark_task_finished(self, result)
class _WorkerPool:
extends Reference
var threads = []
var should_shutdown = false
var mutex = Mutex.new()
var semaphore = Semaphore.new()
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and self:
shutdown()
func shutdown() -> void:
if threads.empty():
return
should_shutdown = true
for i in threads.size():
semaphore.post()
for t in threads:
if t.is_active():
t.wait_to_finish()
threads.clear()
should_shutdown = false
var _task_queue = []
var _workers: _WorkerPool = null
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and self:
shutdown()
func create_serial() -> void:
"""Attempt to create a threaded Dispatch Queue with 1 Thread"""
create_concurrent(1)
func create_concurrent(thread_count: int = 1) -> void:
"""Attempt to create a threaded Dispatch Queue with thread_count Threads"""
if not OS.can_use_threads() or thread_count == get_thread_count():
return
if is_threaded():
shutdown()
_workers = _WorkerPool.new()
for i in max(1, thread_count):
var thread = Thread.new()
_workers.threads.append(thread)
thread.start(self, "_run_loop", _workers)
func dispatch(object: Object, method: String, args: Array = []) -> Task:
var task = Task.new()
if object.has_method(method):
task.object = object
task.method = method
task.args = args
if is_threaded():
_workers.mutex.lock()
_task_queue.append(task)
_workers.mutex.unlock()
_workers.semaphore.call_deferred("post")
else:
if _task_queue.empty():
call_deferred("_sync_run_next_task")
_task_queue.append(task)
else:
push_error("Object '%s' has no method named %s" % [object, method])
return task
func dispatch_group(task_list: Array) -> TaskGroup:
var group = TaskGroup.new(is_threaded())
for args in task_list:
var task = callv("dispatch", args)
if task.object:
group.add_task(task)
return group
func is_threaded() -> bool:
return _workers != null
func get_thread_count() -> int:
if is_threaded():
return _workers.threads.size()
else:
return 0
func size() -> int:
var result
if is_threaded():
_workers.mutex.lock()
result = _task_queue.size()
_workers.mutex.unlock()
else:
result = _task_queue.size()
return result
func is_empty() -> bool:
return size() <= 0
func clear() -> void:
if is_threaded():
_workers.mutex.lock()
_task_queue.clear()
_workers.mutex.unlock()
else:
_task_queue.clear()
func shutdown() -> void:
clear()
if is_threaded():
var current_workers = _workers
_workers = null
current_workers.shutdown()
func _run_loop(pool: _WorkerPool) -> void:
while true:
pool.semaphore.wait()
if pool.should_shutdown:
break
pool.mutex.lock()
var task = _pop_task()
pool.mutex.unlock()
if task:
task.execute()
func _sync_run_next_task() -> void:
var task = _pop_task()
if task:
task.execute()
call_deferred("_sync_run_next_task")
func _pop_task() -> Task:
var task: Task = _task_queue.pop_front()
if task and _task_queue.empty():
task.then_deferred(self, "_on_last_task_finished")
return task
func _on_last_task_finished(_result):
if is_empty():
emit_signal("all_tasks_finished")