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_worker_thread.h>
21 
22 #include <stdlib.h>
23 
24 void ags_worker_thread_class_init(AgsWorkerThreadClass *worker_thread);
25 void ags_worker_thread_init(AgsWorkerThread *worker_thread);
26 void ags_worker_thread_finalize(GObject *gobject);
27 
28 void ags_worker_thread_start(AgsThread *thread);
29 void ags_worker_thread_run(AgsThread *thread);
30 void ags_worker_thread_stop(AgsThread *thread);
31 
32 void* ags_woker_thread_do_poll_loop(void *ptr);
33 
34 /**
35  * SECTION:ags_worker_thread
36  * @short_description: worker thread
37  * @title: AgsWorkerThread
38  * @section_id:
39  * @include: ags/thread/ags_worker_thread.h
40  *
41  * The #AgsWorkerThread does non-realtime work. You might want
42  * to synchronize to the run signal within your ::do_poll() method.
43  */
44 
45 enum{
46   DO_POLL,
47   LAST_SIGNAL,
48 };
49 
50 static gpointer ags_worker_thread_parent_class = NULL;
51 static guint worker_thread_signals[LAST_SIGNAL];
52 
53 GType
ags_worker_thread_get_type()54 ags_worker_thread_get_type()
55 {
56   static volatile gsize g_define_type_id__volatile = 0;
57 
58   if(g_once_init_enter (&g_define_type_id__volatile)){
59     GType ags_type_worker_thread = 0;
60 
61     static const GTypeInfo ags_worker_thread_info = {
62       sizeof (AgsWorkerThreadClass),
63       NULL, /* base_init */
64       NULL, /* base_finalize */
65       (GClassInitFunc) ags_worker_thread_class_init,
66       NULL, /* class_finalize */
67       NULL, /* class_data */
68       sizeof (AgsWorkerThread),
69       0,    /* n_preallocs */
70       (GInstanceInitFunc) ags_worker_thread_init,
71     };
72 
73     ags_type_worker_thread = g_type_register_static(AGS_TYPE_THREAD,
74 						    "AgsWorkerThread",
75 						    &ags_worker_thread_info,
76 						    0);
77 
78     g_once_init_leave(&g_define_type_id__volatile, ags_type_worker_thread);
79   }
80 
81   return g_define_type_id__volatile;
82 }
83 
84 GType
ags_worker_thread_status_flags_get_type()85 ags_worker_thread_status_flags_get_type()
86 {
87   static volatile gsize g_flags_type_id__volatile;
88 
89   if(g_once_init_enter (&g_flags_type_id__volatile)){
90     static const GFlagsValue values[] = {
91       { AGS_WORKER_THREAD_STATUS_RUNNING, "AGS_WORKER_THREAD_STATUS_RUNNING", "worker-thread-status-running" },
92       { AGS_WORKER_THREAD_STATUS_RUN_WAIT, "AGS_WORKER_THREAD_STATUS_RUN_WAIT", "worker-thread-status-run-wait" },
93       { AGS_WORKER_THREAD_STATUS_RUN_DONE, "AGS_WORKER_THREAD_STATUS_RUN_DONE", "worker-thread-status-run-done" },
94       { AGS_WORKER_THREAD_STATUS_RUN_SYNC, "AGS_WORKER_THREAD_STATUS_RUN_SYNC", "worker-thread-status-run-sync" },
95       { 0, NULL, NULL }
96     };
97 
98     GType g_flags_type_id = g_flags_register_static(g_intern_static_string("AgsWorkerThreadStatusFlags"), values);
99 
100     g_once_init_leave (&g_flags_type_id__volatile, g_flags_type_id);
101   }
102 
103   return g_flags_type_id__volatile;
104 }
105 
106 void
ags_worker_thread_class_init(AgsWorkerThreadClass * worker_thread)107 ags_worker_thread_class_init(AgsWorkerThreadClass *worker_thread)
108 {
109   GObjectClass *gobject;
110   AgsThreadClass *thread;
111 
112   ags_worker_thread_parent_class = g_type_class_peek_parent(worker_thread);
113 
114   /* GObject */
115   gobject = (GObjectClass *) worker_thread;
116 
117   gobject->finalize = ags_worker_thread_finalize;
118 
119   /* AgsThread */
120   thread = (AgsThreadClass *) worker_thread;
121 
122   thread->start = ags_worker_thread_start;
123   thread->run = ags_worker_thread_run;
124   thread->stop = ags_worker_thread_stop;
125 
126   /* AgsWorkerThread */
127   worker_thread->do_poll = NULL;
128 
129   /* signals */
130   /**
131    * AgsWorkerThread::do-poll:
132    * @thread: the #AgsWorkerThread
133    *
134    * The ::do_poll() signal runs independently of ::run() but
135    * might be synchronized using a conditional lock.
136    *
137    * Since: 3.0.0
138    */
139   worker_thread_signals[DO_POLL] =
140     g_signal_new("do-poll",
141 		 G_TYPE_FROM_CLASS (thread),
142 		 G_SIGNAL_RUN_LAST,
143 		 G_STRUCT_OFFSET (AgsWorkerThreadClass, do_poll),
144 		 NULL, NULL,
145 		 g_cclosure_marshal_VOID__VOID,
146 		 G_TYPE_NONE, 0);
147 }
148 
149 void
ags_worker_thread_init(AgsWorkerThread * worker_thread)150 ags_worker_thread_init(AgsWorkerThread *worker_thread)
151 {
152   AgsThread *thread;
153 
154   thread = (AgsThread *) worker_thread;
155 
156   g_object_set(thread,
157 	       "frequency", AGS_WORKER_THREAD_DEFAULT_JIFFIE,
158 	       NULL);
159 
160   g_atomic_int_set(&(worker_thread->status_flags),
161 		   0);
162 
163   /* synchronization */
164   g_mutex_init(&(worker_thread->run_mutex));
165 
166   g_cond_init(&(worker_thread->run_cond));
167 
168   /* worker thread */
169   worker_thread->worker_thread = NULL;
170 }
171 
172 void
ags_worker_thread_finalize(GObject * gobject)173 ags_worker_thread_finalize(GObject *gobject)
174 {
175   AgsWorkerThread *worker_thread;
176 
177   gboolean running;
178   gboolean do_exit;
179 
180   worker_thread = AGS_WORKER_THREAD(gobject);
181 
182   if(worker_thread == ags_thread_self()){
183     do_exit = TRUE;
184   }else{
185     do_exit = FALSE;
186   }
187 
188   running = ags_worker_thread_test_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUNNING);
189 
190   /* call parent */
191   G_OBJECT_CLASS(ags_worker_thread_parent_class)->finalize(gobject);
192 
193   if(do_exit){
194     g_thread_exit(NULL);
195   }
196 }
197 
198 void
ags_worker_thread_start(AgsThread * thread)199 ags_worker_thread_start(AgsThread *thread)
200 {
201   AgsWorkerThread *worker_thread;
202 
203   worker_thread = AGS_WORKER_THREAD(thread);
204 
205   /*  */
206   AGS_THREAD_CLASS(ags_worker_thread_parent_class)->start(thread);
207 
208   ags_worker_thread_set_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUNNING);
209 
210   worker_thread->worker_thread = g_thread_new("Advanced Gtk+ Sequencer - worker",
211 					      ags_woker_thread_do_poll_loop,
212 					      worker_thread);
213 }
214 
215 void
ags_worker_thread_run(AgsThread * thread)216 ags_worker_thread_run(AgsThread *thread)
217 {
218   AgsWorkerThread *worker_thread;
219 
220   worker_thread = AGS_WORKER_THREAD(thread);
221 
222   /* synchronization point */
223   if(ags_worker_thread_test_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUN_WAIT) &&
224      !ags_worker_thread_test_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUN_DONE)){
225     g_mutex_lock(&(worker_thread->run_mutex));
226 
227     ags_worker_thread_set_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUN_SYNC);
228 
229     while(ags_worker_thread_test_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUN_WAIT) &&
230 	  !ags_worker_thread_test_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUN_DONE)){
231       g_cond_wait(&(worker_thread->run_cond),
232 		  &(worker_thread->run_mutex));
233     }
234 
235     ags_worker_thread_set_status_flags(worker_thread, (AGS_WORKER_THREAD_STATUS_RUN_WAIT |
236 						       AGS_WORKER_THREAD_STATUS_RUN_DONE));
237 
238     g_mutex_unlock(&(worker_thread->run_mutex));
239   }
240 }
241 
242 void
ags_worker_thread_stop(AgsThread * thread)243 ags_worker_thread_stop(AgsThread *thread)
244 {
245   AgsWorkerThread *worker_thread;
246 
247   worker_thread = AGS_WORKER_THREAD(thread);
248 
249   ags_worker_thread_unset_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUNNING);
250 
251   /* call parent */
252   AGS_THREAD_CLASS(ags_worker_thread_parent_class)->stop(thread);
253 }
254 
255 /**
256  * ags_worker_thread_test_status_flags:
257  * @worker_thread: the #AgsWorkerThread
258  * @status_flags: status flags
259  *
260  * Test @status_flags of @worker_thread.
261  *
262  * Returns: %TRUE if status flags set, otherwise %FALSE
263  *
264  * Since: 3.0.0
265  */
266 gboolean
ags_worker_thread_test_status_flags(AgsWorkerThread * worker_thread,guint status_flags)267 ags_worker_thread_test_status_flags(AgsWorkerThread *worker_thread, guint status_flags)
268 {
269   gboolean retval;
270 
271   if(!AGS_IS_WORKER_THREAD(worker_thread)){
272     return(FALSE);
273   }
274 
275   retval = ((status_flags & (g_atomic_int_get(&(worker_thread->status_flags)))) != 0) ? TRUE: FALSE;
276 
277   return(retval);
278 }
279 
280 /**
281  * ags_worker_thread_set_status_flags:
282  * @worker_thread: the #AgsWorkerThread
283  * @status_flags: status flags
284  *
285  * Set status flags.
286  *
287  * Since: 3.0.0
288  */
289 void
ags_worker_thread_set_status_flags(AgsWorkerThread * worker_thread,guint status_flags)290 ags_worker_thread_set_status_flags(AgsWorkerThread *worker_thread, guint status_flags)
291 {
292   if(!AGS_IS_WORKER_THREAD(worker_thread)){
293     return;
294   }
295 
296   g_atomic_int_or(&(worker_thread->status_flags),
297 		  status_flags);
298 }
299 
300 /**
301  * ags_worker_thread_unset_status_flags:
302  * @worker_thread: the #AgsWorkerThread
303  * @status_flags: status flags
304  *
305  * Unset status flags.
306  *
307  * Since: 3.0.0
308  */
309 void
ags_worker_thread_unset_status_flags(AgsWorkerThread * worker_thread,guint status_flags)310 ags_worker_thread_unset_status_flags(AgsWorkerThread *worker_thread, guint status_flags)
311 {
312   if(!AGS_IS_WORKER_THREAD(worker_thread)){
313     return;
314   }
315 
316   g_atomic_int_and(&(worker_thread->status_flags),
317 		   (~status_flags));
318 }
319 
320 /**
321  * ags_woker_thread_do_poll_loop:
322  * @ptr: the #AgsWorkerThread
323  *
324  * Do loop and invoke ags_worker_thread_do_poll() unless flag
325  * AGS_WORKER_THREAD_RUNNING was unset.
326  *
327  * Since: 3.0.0
328  */
329 void*
ags_woker_thread_do_poll_loop(void * ptr)330 ags_woker_thread_do_poll_loop(void *ptr)
331 {
332   AgsWorkerThread *worker_thread;
333 
334   worker_thread = (AgsWorkerThread *) ptr;
335 
336   while(ags_worker_thread_test_status_flags(worker_thread, AGS_WORKER_THREAD_STATUS_RUNNING)){
337     ags_worker_thread_do_poll(worker_thread);
338   }
339 
340   g_thread_exit(NULL);
341 
342   return(NULL);
343 }
344 
345 /**
346  * ags_worker_thread_do_poll:
347  * @worker_thread: the #AgsWorkerThread
348  *
349  * Do poll your work. It is called of the worker thread.
350  *
351  * Since: 3.0.0
352  */
353 void
ags_worker_thread_do_poll(AgsWorkerThread * worker_thread)354 ags_worker_thread_do_poll(AgsWorkerThread *worker_thread)
355 {
356   g_return_if_fail(AGS_IS_WORKER_THREAD(worker_thread));
357   g_object_ref(worker_thread);
358   g_signal_emit(worker_thread,
359 		worker_thread_signals[DO_POLL], 0);
360   g_object_unref(worker_thread);
361 }
362 
363 /**
364  * ags_worker_thread_new:
365  *
366  * Create a new instance of #AgsWorkerThread.
367  *
368  * Returns: the new #AgsWorkerThread
369  *
370  * Since: 3.0.0
371  */
372 AgsWorkerThread*
ags_worker_thread_new()373 ags_worker_thread_new()
374 {
375   AgsWorkerThread *worker_thread;
376 
377   worker_thread = (AgsWorkerThread *) g_object_new(AGS_TYPE_WORKER_THREAD,
378 						   NULL);
379 
380   return(worker_thread);
381 }
382