1 /* GSequencer - Advanced GTK Sequencer
2 * Copyright (C) 2005-2020 Joël Krähemann
3 *
4 * This file is part of GSequencer.
5 *
6 * GSequencer is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GSequencer is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GSequencer. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <ags/thread/ags_returnable_thread.h>
21
22 #include <ags/object/ags_connectable.h>
23
24 #include <ags/thread/ags_thread_pool.h>
25
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 #include <math.h>
30
31 #include <unistd.h>
32 #include <errno.h>
33
34 #include <ags/i18n.h>
35
36 void ags_returnable_thread_class_init(AgsReturnableThreadClass *returnable_thread);
37 void ags_returnable_thread_init(AgsReturnableThread *returnable_thread);
38 void ags_returnable_thread_set_property(GObject *gobject,
39 guint prop_id,
40 const GValue *value,
41 GParamSpec *param_spec);
42 void ags_returnable_thread_get_property(GObject *gobject,
43 guint prop_id,
44 GValue *value,
45 GParamSpec *param_spec);
46 void ags_returnable_thread_dispose(GObject *gobject);
47 void ags_returnable_thread_finalize(GObject *gobject);
48
49 void ags_returnable_thread_start(AgsThread *thread);
50 void ags_returnable_thread_run(AgsThread *thread);
51 void ags_returnable_thread_stop(AgsThread *thread);
52 void ags_returnable_thread_resume(AgsThread *thread);
53
54 /**
55 * SECTION:ags_returnable_thread
56 * @short_description: returnable thread
57 * @title: AgsReturnableThread
58 * @section_id:
59 * @include: ags/thread/ags_returnable_thread.h
60 *
61 * The #AgsReturnableThread acts as thread. It should return after a short
62 * while because of limited thread pool.
63 */
64
65 enum{
66 PROP_0,
67 PROP_THREAD_POOL,
68 PROP_SAFE_DATA,
69 };
70
71 enum{
72 SAFE_RUN,
73 LAST_SIGNAL,
74 };
75
76 static gpointer ags_returnable_thread_parent_class = NULL;
77 static guint returnable_thread_signals[LAST_SIGNAL];
78
79 GType
ags_returnable_thread_get_type()80 ags_returnable_thread_get_type()
81 {
82 static volatile gsize g_define_type_id__volatile = 0;
83
84 if(g_once_init_enter (&g_define_type_id__volatile)){
85 GType ags_type_returnable_thread = 0;
86
87 static const GTypeInfo ags_returnable_thread_info = {
88 sizeof (AgsReturnableThreadClass),
89 NULL, /* base_init */
90 NULL, /* base_finalize */
91 (GClassInitFunc) ags_returnable_thread_class_init,
92 NULL, /* class_finalize */
93 NULL, /* class_data */
94 sizeof (AgsReturnableThread),
95 0, /* n_preallocs */
96 (GInstanceInitFunc) ags_returnable_thread_init,
97 };
98
99 ags_type_returnable_thread = g_type_register_static(AGS_TYPE_THREAD,
100 "AgsReturnableThread",
101 &ags_returnable_thread_info,
102 0);
103
104 g_once_init_leave(&g_define_type_id__volatile, ags_type_returnable_thread);
105 }
106
107 return g_define_type_id__volatile;
108 }
109
110 GType
ags_returnable_thread_flags_get_type()111 ags_returnable_thread_flags_get_type()
112 {
113 static volatile gsize g_flags_type_id__volatile;
114
115 if(g_once_init_enter (&g_flags_type_id__volatile)){
116 static const GFlagsValue values[] = {
117 { AGS_RETURNABLE_THREAD_IN_USE, "AGS_RETURNABLE_THREAD_IN_USE", "returnable-thread-in-use" },
118 { AGS_RETURNABLE_THREAD_RESET, "AGS_RETURNABLE_THREAD_RESET", "returnable-thread-reset" },
119 { AGS_RETURNABLE_THREAD_RUN_ONCE, "AGS_RETURNABLE_THREAD_RUN_ONCE", "returnable-thread-run-once" },
120 { 0, NULL, NULL }
121 };
122
123 GType g_flags_type_id = g_flags_register_static(g_intern_static_string("AgsReturnableThreadFlags"), values);
124
125 g_once_init_leave (&g_flags_type_id__volatile, g_flags_type_id);
126 }
127
128 return g_flags_type_id__volatile;
129 }
130
131 void
ags_returnable_thread_class_init(AgsReturnableThreadClass * returnable_thread)132 ags_returnable_thread_class_init(AgsReturnableThreadClass *returnable_thread)
133 {
134 AgsThreadClass *thread;
135
136 GObjectClass *gobject;
137
138 GParamSpec *param_spec;
139
140 ags_returnable_thread_parent_class = g_type_class_peek_parent(returnable_thread);
141
142 /* GObjectClass */
143 gobject = (GObjectClass *) returnable_thread;
144
145 gobject->set_property = ags_returnable_thread_set_property;
146 gobject->get_property = ags_returnable_thread_get_property;
147
148 gobject->dispose = ags_returnable_thread_dispose;
149 gobject->finalize = ags_returnable_thread_finalize;
150
151 /* properties */
152 /**
153 * AgsReturnableThread:thread-pool:
154 *
155 * The assigned #AgsThreadPool providing default settings.
156 *
157 * Since: 3.0.0
158 */
159 param_spec = g_param_spec_object("thread-pool",
160 i18n_pspec("assigned thread pool"),
161 i18n_pspec("The thread pool it is assigned with"),
162 G_TYPE_OBJECT,
163 G_PARAM_READABLE | G_PARAM_WRITABLE);
164 g_object_class_install_property(gobject,
165 PROP_THREAD_POOL,
166 param_spec);
167
168 /* AgsThreadClass */
169 thread = (AgsThreadClass *) returnable_thread;
170
171 thread->start = ags_returnable_thread_start;
172 thread->run = ags_returnable_thread_run;
173 thread->stop = ags_returnable_thread_stop;
174
175 /* AgsReturnableThreadClass */
176 returnable_thread->safe_run = NULL;
177
178 /* signals */
179 /**
180 * AgsReturnableThread::safe-run:
181 * @returnable_thread: the #AgsReturnableThread
182 *
183 * The ::safe-run is invoked durin AgsThread::run as
184 * a context safe wrapper.
185 *
186 * Since: 3.0.0
187 */
188 returnable_thread_signals[SAFE_RUN] =
189 g_signal_new("safe-run",
190 G_TYPE_FROM_CLASS (returnable_thread),
191 G_SIGNAL_RUN_LAST,
192 G_STRUCT_OFFSET (AgsReturnableThreadClass, safe_run),
193 NULL, NULL,
194 g_cclosure_marshal_VOID__VOID,
195 G_TYPE_NONE, 0);
196
197 }
198
199 void
ags_returnable_thread_init(AgsReturnableThread * returnable_thread)200 ags_returnable_thread_init(AgsReturnableThread *returnable_thread)
201 {
202 AgsThread *thread;
203
204 thread = AGS_THREAD(returnable_thread);
205
206 ags_thread_set_flags(thread, AGS_THREAD_UNREF_ON_EXIT);
207
208 g_object_set(thread,
209 "frequency", AGS_RETURNABLE_THREAD_DEFAULT_JIFFIE,
210 NULL);
211
212 g_atomic_int_set(&(returnable_thread->flags),
213 AGS_RETURNABLE_THREAD_RUN_ONCE);
214
215 returnable_thread->thread_pool = NULL;
216
217 g_rec_mutex_init(&(returnable_thread->reset_mutex));
218
219 g_atomic_pointer_set(&(returnable_thread->safe_data),
220 NULL);
221
222 returnable_thread->handler = 0;
223 }
224
225 void
ags_returnable_thread_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * param_spec)226 ags_returnable_thread_set_property(GObject *gobject,
227 guint prop_id,
228 const GValue *value,
229 GParamSpec *param_spec)
230 {
231 AgsReturnableThread *returnable_thread;
232
233 returnable_thread = AGS_RETURNABLE_THREAD(gobject);
234
235 switch(prop_id){
236 case PROP_THREAD_POOL:
237 {
238 GObject *thread_pool;
239
240 thread_pool = g_value_get_object(value);
241
242 if(returnable_thread->thread_pool == thread_pool)
243 return;
244
245 if(returnable_thread->thread_pool != NULL){
246 g_object_unref(returnable_thread->thread_pool);
247 }
248
249 if(thread_pool != NULL){
250 g_object_ref(thread_pool);
251 }
252
253 returnable_thread->thread_pool = thread_pool;
254 }
255 break;
256 default:
257 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
258 break;
259 }
260 }
261
262 void
ags_returnable_thread_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * param_spec)263 ags_returnable_thread_get_property(GObject *gobject,
264 guint prop_id,
265 GValue *value,
266 GParamSpec *param_spec)
267 {
268 AgsReturnableThread *returnable_thread;
269
270 returnable_thread = AGS_RETURNABLE_THREAD(gobject);
271
272 switch(prop_id){
273 case PROP_THREAD_POOL:
274 {
275 g_value_set_object(value, returnable_thread->thread_pool);
276 }
277 break;
278 default:
279 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
280 break;
281 }
282 }
283
284 void
ags_returnable_thread_dispose(GObject * gobject)285 ags_returnable_thread_dispose(GObject *gobject)
286 {
287 AgsReturnableThread *returnable_thread;
288
289 returnable_thread = (AgsReturnableThread *) gobject;
290
291 /* thread pool */
292 if(returnable_thread->thread_pool != NULL){
293 g_object_unref(returnable_thread->thread_pool);
294
295 returnable_thread->thread_pool = NULL;
296 }
297
298 /* call parent */
299 G_OBJECT_CLASS(ags_returnable_thread_parent_class)->dispose(gobject);
300 }
301
302 void
ags_returnable_thread_finalize(GObject * gobject)303 ags_returnable_thread_finalize(GObject *gobject)
304 {
305 AgsReturnableThread *returnable_thread;
306
307 returnable_thread = (AgsReturnableThread *) gobject;
308
309 /* thread pool */
310 if(returnable_thread->thread_pool != NULL){
311 g_object_unref(returnable_thread->thread_pool);
312 }
313
314 /* call parent */
315 G_OBJECT_CLASS(ags_returnable_thread_parent_class)->finalize(gobject);
316 }
317
318 void
ags_returnable_thread_start(AgsThread * thread)319 ags_returnable_thread_start(AgsThread *thread)
320 {
321 AGS_THREAD_CLASS(ags_returnable_thread_parent_class)->start(thread);
322 }
323
324 void
ags_returnable_thread_run(AgsThread * thread)325 ags_returnable_thread_run(AgsThread *thread)
326 {
327 AgsThreadPool *thread_pool;
328 AgsReturnableThread *returnable_thread;
329
330 GRecMutex *thread_mutex;
331
332 // g_message("reset:0");
333
334 /* retrieve some variables */
335 returnable_thread = AGS_RETURNABLE_THREAD(thread);
336
337 /* safe run */
338 if((AGS_RETURNABLE_THREAD_IN_USE & (g_atomic_int_get(&(returnable_thread->flags)))) != 0){
339 ags_returnable_thread_safe_run(returnable_thread);
340
341 if((AGS_RETURNABLE_THREAD_RUN_ONCE & (g_atomic_int_get(&(returnable_thread->flags)))) != 0){
342 g_atomic_int_and(&(returnable_thread->flags),
343 (~AGS_RETURNABLE_THREAD_IN_USE));
344
345 /* return to thread pool */
346 thread_mutex = AGS_THREAD_GET_OBJ_MUTEX(thread);
347
348 g_rec_mutex_lock(thread_mutex);
349
350 thread_pool = returnable_thread->thread_pool;
351
352 g_rec_mutex_unlock(thread_mutex);
353
354 /* give returnable thread back to thread pool */
355 g_atomic_pointer_set(&(returnable_thread->safe_data),
356 NULL);
357 ags_returnable_thread_disconnect_safe_run(returnable_thread);
358
359 g_atomic_pointer_set(&(thread_pool->returnable_thread),
360 g_list_prepend(g_atomic_pointer_get(&(thread_pool->returnable_thread)),
361 returnable_thread));
362 }
363 }
364 }
365
366 void
ags_returnable_thread_safe_run(AgsReturnableThread * returnable_thread)367 ags_returnable_thread_safe_run(AgsReturnableThread *returnable_thread)
368 {
369 guint returnable_thread_signal;
370
371 returnable_thread_signal = returnable_thread_signals[SAFE_RUN];
372
373 g_return_if_fail(AGS_IS_RETURNABLE_THREAD(returnable_thread));
374
375 g_object_ref(returnable_thread);
376 g_signal_emit(returnable_thread,
377 returnable_thread_signal, 0);
378 g_object_unref(returnable_thread);
379 }
380
381 void
ags_returnable_thread_stop(AgsThread * thread)382 ags_returnable_thread_stop(AgsThread *thread)
383 {
384 AGS_THREAD_CLASS(ags_returnable_thread_parent_class)->stop(thread);
385 }
386
387 void
ags_returnable_thread_resume(AgsThread * thread)388 ags_returnable_thread_resume(AgsThread *thread)
389 {
390 /* empty */
391 }
392
393 /**
394 * ags_returnable_thread_test_flags:
395 * @returnable_thread: the #AgsReturnableThread
396 * @flags: the flags
397 *
398 * Test @flags to be set on @returnable_thread.
399 *
400 * Returns: %TRUE if flags are set, else %FALSE
401 *
402 * Since: 3.0.0
403 */
404 gboolean
ags_returnable_thread_test_flags(AgsReturnableThread * returnable_thread,guint flags)405 ags_returnable_thread_test_flags(AgsReturnableThread *returnable_thread, guint flags)
406 {
407 gboolean retval;
408
409 if(!AGS_IS_RETURNABLE_THREAD(returnable_thread)){
410 return(FALSE);
411 }
412
413 retval = ((flags & (g_atomic_int_get(&(returnable_thread->flags)))) != 0) ? TRUE: FALSE;
414
415 return(retval);
416 }
417
418 /**
419 * ags_returnable_thread_set_flags:
420 * @returnable_thread: the #AgsReturnableThread
421 * @flags: the flags
422 *
423 * Set flags.
424 *
425 * Since: 3.0.0
426 */
427 void
ags_returnable_thread_set_flags(AgsReturnableThread * returnable_thread,guint flags)428 ags_returnable_thread_set_flags(AgsReturnableThread *returnable_thread, guint flags)
429 {
430 if(!AGS_IS_RETURNABLE_THREAD(returnable_thread)){
431 return;
432 }
433
434 g_atomic_int_or(&(returnable_thread->flags), flags);
435 }
436
437 /**
438 * ags_returnable_thread_unset_flags:
439 * @returnable_thread: the #AgsReturnableThread
440 * @flags: the flags
441 *
442 * Unset flags.
443 *
444 * Since: 3.0.0
445 */
446 void
ags_returnable_thread_unset_flags(AgsReturnableThread * returnable_thread,guint flags)447 ags_returnable_thread_unset_flags(AgsReturnableThread *returnable_thread, guint flags)
448 {
449 if(!AGS_IS_RETURNABLE_THREAD(returnable_thread)){
450 return;
451 }
452
453 g_atomic_int_and(&(returnable_thread->flags), (~flags));
454 }
455
456 /**
457 * ags_returnable_thread_connect_safe_run:
458 * @returnable_thread: the thread to connect
459 * @callback: (scope call): the callback
460 *
461 * Connects @callback to @thread.
462 *
463 * Since: 3.0.0
464 */
465 void
ags_returnable_thread_connect_safe_run(AgsReturnableThread * returnable_thread,AgsReturnableThreadCallback callback)466 ags_returnable_thread_connect_safe_run(AgsReturnableThread *returnable_thread, AgsReturnableThreadCallback callback)
467 {
468 if(returnable_thread->handler > 0){
469 return;
470 }
471
472 returnable_thread->handler = g_signal_connect(G_OBJECT(returnable_thread), "safe-run",
473 G_CALLBACK(callback), returnable_thread);
474 }
475
476 /**
477 * ags_returnable_thread_disconnect_safe_run:
478 * @returnable_thread: the thread to disconnect
479 *
480 * Disconnects callback of @thread.
481 *
482 * Since: 3.0.0
483 */
484 void
ags_returnable_thread_disconnect_safe_run(AgsReturnableThread * returnable_thread)485 ags_returnable_thread_disconnect_safe_run(AgsReturnableThread *returnable_thread)
486 {
487 if(returnable_thread->handler == 0){
488 return;
489 }
490
491 g_signal_handler_disconnect(G_OBJECT(returnable_thread),
492 returnable_thread->handler);
493
494 returnable_thread->handler = 0;
495 }
496
497 /**
498 * ags_returnable_thread_new:
499 * @thread_pool: the #AgsThreadPool
500 *
501 * Create a new instance of #AgsReturnableThread.
502 *
503 * Returns: the new #AgsReturnableThread
504 *
505 * Since: 3.0.0
506 */
507 AgsReturnableThread*
ags_returnable_thread_new(GObject * thread_pool)508 ags_returnable_thread_new(GObject *thread_pool)
509 {
510 AgsReturnableThread *returnable_thread;
511
512 returnable_thread = (AgsReturnableThread *) g_object_new(AGS_TYPE_RETURNABLE_THREAD,
513 "thread-pool", thread_pool,
514 NULL);
515
516 return(returnable_thread);
517 }
518