Docs: doxygen file descriptions for BLF, GPU and WM
[blender.git] / source / blender / windowmanager / intern / wm_jobs.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2009 Blender Foundation.
19  * All rights reserved.
20  *
21  * 
22  * Contributor(s): Blender Foundation
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/windowmanager/intern/wm_jobs.c
28  *  \ingroup wm
29  *
30  * Threaded job manager (high level job access).
31  */
32
33 #include <string.h>
34
35 #include "DNA_windowmanager_types.h"
36
37 #include "MEM_guardedalloc.h"
38
39 #include "BLI_blenlib.h"
40 #include "BLI_threads.h"
41 #include "BLI_utildefines.h"
42
43 #include "BKE_blender.h"
44 #include "BKE_context.h"
45 #include "BKE_idprop.h"
46 #include "BKE_global.h"
47 #include "BKE_library.h"
48 #include "BKE_main.h"
49 #include "BKE_report.h"
50
51 #include "WM_api.h"
52 #include "WM_types.h"
53 #include "wm_window.h"
54 #include "wm_event_system.h"
55 #include "wm_event_types.h"
56 #include "wm.h"
57
58 #include "PIL_time.h"
59
60 /*
61  * Add new job
62  * - register in WM
63  * - configure callbacks
64  *
65  * Start or re-run job
66  * - if job running
67  *   - signal job to end
68  *   - add timer notifier to verify when it has ended, to start it
69  * - else
70  *   - start job
71  *   - add timer notifier to handle progress
72  *
73  * Stop job
74  *   - signal job to end
75  *  on end, job will tag itself as sleeping
76  *
77  * Remove job
78  * - signal job to end
79  *  on end, job will remove itself
80  *
81  * When job is done:
82  * - it puts timer to sleep (or removes?)
83  *
84  */
85  
86 struct wmJob {
87         struct wmJob *next, *prev;
88         
89         /* job originating from, keep track of this when deleting windows */
90         wmWindow *win;
91         
92         /* should store entire own context, for start, update, free */
93         void *customdata;
94         /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
95         void (*initjob)(void *);
96         /* this runs inside thread, and does full job */
97         void (*startjob)(void *, short *stop, short *do_update, float *progress);
98         /* update gets called if thread defines so, and max once per timerstep */
99         /* it runs outside thread, blocking blender, no drawing! */
100         void (*update)(void *);
101         /* free entire customdata, doesn't run in thread */
102         void (*free)(void *);
103         /* gets called when job is stopped, not in thread */
104         void (*endjob)(void *);
105         
106         /* running jobs each have own timer */
107         double timestep;
108         wmTimer *wt;
109         /* the notifier event timers should send */
110         unsigned int note, endnote;
111         
112         
113 /* internal */
114         void *owner;
115         int flag;
116         short suspended, running, ready, do_update, stop, job_type;
117         float progress;
118
119         /* for display in header, identification */
120         char name[128];
121         
122         /* once running, we store this separately */
123         void *run_customdata;
124         void (*run_free)(void *);
125         
126         /* we use BLI_threads api, but per job only 1 thread runs */
127         ListBase threads;
128
129         double start_time;
130
131         /* ticket mutex for main thread locking while some job accesses
132          * data that the main thread might modify at the same time */
133         TicketMutex *main_thread_mutex;
134         bool main_thread_mutex_ending;
135 };
136
137 /* Main thread locking */
138
139 void WM_job_main_thread_lock_acquire(wmJob *wm_job)
140 {
141         BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
142
143         /* if BLI_end_threads is being called to stop the job before it's finished,
144          * we no longer need to lock to get access to the main thread as it's
145          * waiting and can't respond */
146         if (wm_job->main_thread_mutex_ending)
147                 BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
148 }
149
150 void WM_job_main_thread_lock_release(wmJob *wm_job)
151 {
152         if (!wm_job->main_thread_mutex_ending)
153                 BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
154 }
155
156 static void wm_job_main_thread_yield(wmJob *wm_job, bool ending)
157 {
158         if (ending)
159                 wm_job->main_thread_mutex_ending = true;
160
161         /* unlock and lock the ticket mutex. because it's a fair mutex any job that
162          * is waiting to acquire the lock will get it first, before we can lock */
163         BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
164         BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
165 }
166
167 /* finds:
168  * if type or owner, compare for it, otherwise any matching job
169  */
170 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const int job_type)
171 {
172         wmJob *wm_job;
173         
174         if (owner && job_type) {
175                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
176                         if (wm_job->owner == owner && wm_job->job_type == job_type)
177                                 return wm_job;
178         }
179         else if (owner) {
180                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
181                         if (wm_job->owner == owner)
182                                 return wm_job;
183         }
184         else if (job_type) {
185                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
186                         if (wm_job->job_type == job_type)
187                                 return wm_job;
188         }
189         
190         return NULL;
191 }
192
193 /* ******************* public API ***************** */
194
195 /* returns current or adds new job, but doesnt run it */
196 /* every owner only gets a single job, adding a new one will stop running job and 
197  * when stopped it starts the new one */
198 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag, int job_type)
199 {
200         wmJob *wm_job = wm_job_find(wm, owner, job_type);
201         
202         if (wm_job == NULL) {
203                 wm_job = MEM_callocN(sizeof(wmJob), "new job");
204
205                 BLI_addtail(&wm->jobs, wm_job);
206                 wm_job->win = win;
207                 wm_job->owner = owner;
208                 wm_job->flag = flag;
209                 wm_job->job_type = job_type;
210                 BLI_strncpy(wm_job->name, name, sizeof(wm_job->name));
211
212                 wm_job->main_thread_mutex = BLI_ticket_mutex_alloc();
213                 BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
214         }
215         /* else: a running job, be careful */
216         
217         /* prevent creating a job with an invalid type */
218         BLI_assert(wm_job->job_type != WM_JOB_TYPE_ANY);
219
220         return wm_job;
221 }
222
223 /* returns true if job runs, for UI (progress) indicators */
224 bool WM_jobs_test(wmWindowManager *wm, void *owner, int job_type)
225 {
226         wmJob *wm_job;
227         
228         /* job can be running or about to run (suspended) */
229         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
230                 if (wm_job->owner == owner) {
231                         if (job_type == WM_JOB_TYPE_ANY || (wm_job->job_type == job_type)) {
232                                 if (wm_job->running || wm_job->suspended) {
233                                         return TRUE;
234                                 }
235                         }
236                 }
237         }
238
239         return FALSE;
240 }
241
242 float WM_jobs_progress(wmWindowManager *wm, void *owner)
243 {
244         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
245         
246         if (wm_job && wm_job->flag & WM_JOB_PROGRESS)
247                 return wm_job->progress;
248         
249         return 0.0;
250 }
251
252 char *WM_jobs_name(wmWindowManager *wm, void *owner)
253 {
254         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
255         
256         if (wm_job)
257                 return wm_job->name;
258         
259         return NULL;
260 }
261
262 void *WM_jobs_customdata(wmWindowManager *wm, void *owner)
263 {
264         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
265         
266         if (wm_job)
267                 return WM_jobs_customdata_get(wm_job);
268         
269         return NULL;
270 }
271
272 void *WM_jobs_customdata_from_type(wmWindowManager *wm, int job_type)
273 {
274         wmJob *wm_job = wm_job_find(wm, NULL, job_type);
275         
276         if (wm_job)
277                 return WM_jobs_customdata_get(wm_job);
278         
279         return NULL;
280 }
281
282 bool WM_jobs_is_running(wmJob *wm_job)
283 {
284         return wm_job->running;
285 }
286
287 void *WM_jobs_customdata_get(wmJob *wm_job)
288 {
289         if (!wm_job->customdata) {
290                 return wm_job->run_customdata;
291         }
292         else {
293                 return wm_job->customdata;
294         }
295 }
296
297 void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void (*free)(void *))
298 {
299         /* pending job? just free */
300         if (wm_job->customdata)
301                 wm_job->free(wm_job->customdata);
302         
303         wm_job->customdata = customdata;
304         wm_job->free = free;
305
306         if (wm_job->running) {
307                 /* signal job to end */
308                 wm_job->stop = TRUE;
309         }
310 }
311
312 void WM_jobs_timer(wmJob *wm_job, double timestep, unsigned int note, unsigned int endnote)
313 {
314         wm_job->timestep = timestep;
315         wm_job->note = note;
316         wm_job->endnote = endnote;
317 }
318
319 void WM_jobs_callbacks(wmJob *wm_job,
320                        void (*startjob)(void *, short *, short *, float *),
321                        void (*initjob)(void *),
322                        void (*update)(void *),
323                        void (*endjob)(void *))
324 {
325         wm_job->startjob = startjob;
326         wm_job->initjob = initjob;
327         wm_job->update = update;
328         wm_job->endjob = endjob;
329 }
330
331 static void *do_job_thread(void *job_v)
332 {
333         wmJob *wm_job = job_v;
334         
335         wm_job->startjob(wm_job->run_customdata, &wm_job->stop, &wm_job->do_update, &wm_job->progress);
336         wm_job->ready = TRUE;
337         
338         return NULL;
339 }
340
341 /* don't allow same startjob to be executed twice */
342 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
343 {
344         wmJob *wm_job;
345         int suspend = FALSE;
346         
347         /* job added with suspend flag, we wait 1 timer step before activating it */
348         if (test->flag & WM_JOB_SUSPEND) {
349                 suspend = TRUE;
350                 test->flag &= ~WM_JOB_SUSPEND;
351         }
352         else {
353                 /* check other jobs */
354                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
355                         /* obvious case, no test needed */
356                         if (wm_job == test || !wm_job->running) {
357                                 continue;
358                         }
359                         
360                         /* if new job is not render, then check for same startjob */
361                         if (0 == (test->flag & WM_JOB_EXCL_RENDER))
362                                 if (wm_job->startjob != test->startjob)
363                                         continue;
364                         
365                         /* if new job is render, any render job should be stopped */
366                         if (test->flag & WM_JOB_EXCL_RENDER)
367                                 if (0 == (wm_job->flag & WM_JOB_EXCL_RENDER))
368                                         continue;
369
370                         suspend = TRUE;
371
372                         /* if this job has higher priority, stop others */
373                         if (test->flag & WM_JOB_PRIORITY) {
374                                 wm_job->stop = TRUE;
375                                 // printf("job stopped: %s\n", wm_job->name);
376                         }
377                 }
378         }
379         
380         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
381         test->suspended = suspend;
382         // if (suspend) printf("job suspended: %s\n", test->name);
383 }
384
385 /* if job running, the same owner gave it a new job */
386 /* if different owner starts existing startjob, it suspends itself */
387 void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
388 {
389         if (wm_job->running) {
390                 /* signal job to end and restart */
391                 wm_job->stop = TRUE;
392                 // printf("job started a running job, ending... %s\n", wm_job->name);
393         }
394         else {
395                 
396                 if (wm_job->customdata && wm_job->startjob) {
397                         
398                         wm_jobs_test_suspend_stop(wm, wm_job);
399                         
400                         if (wm_job->suspended == FALSE) {
401                                 /* copy to ensure proper free in end */
402                                 wm_job->run_customdata = wm_job->customdata;
403                                 wm_job->run_free = wm_job->free;
404                                 wm_job->free = NULL;
405                                 wm_job->customdata = NULL;
406                                 wm_job->running = TRUE;
407                                 
408                                 if (wm_job->initjob)
409                                         wm_job->initjob(wm_job->run_customdata);
410                                 
411                                 wm_job->stop = FALSE;
412                                 wm_job->ready = FALSE;
413                                 wm_job->progress = 0.0;
414
415                                 // printf("job started: %s\n", wm_job->name);
416                                 
417                                 BLI_init_threads(&wm_job->threads, do_job_thread, 1);
418                                 BLI_insert_thread(&wm_job->threads, wm_job);
419                         }
420                         
421                         /* restarted job has timer already */
422                         if (wm_job->wt == NULL)
423                                 wm_job->wt = WM_event_add_timer(wm, wm_job->win, TIMERJOBS, wm_job->timestep);
424
425                         if (G.debug & G_DEBUG_JOBS)
426                                 wm_job->start_time = PIL_check_seconds_timer();
427                 }
428                 else {
429                         printf("job fails, not initialized\n");
430                 }
431         }
432 }
433
434 static void wm_job_free(wmWindowManager *wm, wmJob *wm_job)
435 {
436         BLI_remlink(&wm->jobs, wm_job);
437         BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
438         BLI_ticket_mutex_free(wm_job->main_thread_mutex);
439         MEM_freeN(wm_job);
440 }
441
442 /* stop job, end thread, free data completely */
443 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job)
444 {
445         if (wm_job->running) {
446                 /* signal job to end */
447                 wm_job->stop = TRUE;
448                 wm_job_main_thread_yield(wm_job, true);
449                 BLI_end_threads(&wm_job->threads);
450
451                 if (wm_job->endjob)
452                         wm_job->endjob(wm_job->run_customdata);
453         }
454         
455         if (wm_job->wt)
456                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
457         if (wm_job->customdata)
458                 wm_job->free(wm_job->customdata);
459         if (wm_job->run_customdata)
460                 wm_job->run_free(wm_job->run_customdata);
461         
462         /* remove wm_job */
463         wm_job_free(wm, wm_job);
464 }
465
466 /* wait until every job ended */
467 void WM_jobs_kill_all(wmWindowManager *wm)
468 {
469         wmJob *wm_job;
470         
471         while ((wm_job = wm->jobs.first))
472                 wm_jobs_kill_job(wm, wm_job);
473         
474 }
475
476 /* wait until every job ended, except for one owner (used in undo to keep screen job alive) */
477 void WM_jobs_kill_all_except(wmWindowManager *wm, void *owner)
478 {
479         wmJob *wm_job, *next_job;
480         
481         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
482                 next_job = wm_job->next;
483
484                 if (wm_job->owner != owner)
485                         wm_jobs_kill_job(wm, wm_job);
486         }
487 }
488
489
490 void WM_jobs_kill_type(struct wmWindowManager *wm, void *owner, int job_type)
491 {
492         wmJob *wm_job, *next_job;
493         
494         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
495                 next_job = wm_job->next;
496
497                 if (!owner || wm_job->owner == owner)
498                         if (wm_job->job_type == job_type)
499                                 wm_jobs_kill_job(wm, wm_job);
500         }
501 }
502
503 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
504 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
505 {
506         wmJob *wm_job;
507         
508         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
509                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
510                         if (wm_job->running) {
511                                 wm_job->stop = TRUE;
512                         }
513                 }
514         }
515 }
516
517 /* actually terminate thread and job timer */
518 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
519 {
520         wmJob *wm_job;
521         
522         wm_job = wm->jobs.first;
523         while (wm_job) {
524                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
525                         wmJob *wm_job_kill = wm_job;
526                         wm_job = wm_job->next;
527                         wm_jobs_kill_job(wm, wm_job_kill);
528                 }
529                 else {
530                         wm_job = wm_job->next;
531                 }
532         }
533 }
534
535
536 /* kill job entirely, also removes timer itself */
537 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
538 {
539         wmJob *wm_job;
540         
541         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
542                 if (wm_job->wt == wt) {
543                         wm_jobs_kill_job(wm, wm_job);
544                         return;
545                 }
546         }
547 }
548
549 /* hardcoded to event TIMERJOBS */
550 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
551 {
552         wmJob *wm_job, *wm_jobnext;
553         float total_progress = 0.f;
554         float jobs_progress = 0;
555         
556         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_jobnext) {
557                 wm_jobnext = wm_job->next;
558                 
559                 if (wm_job->wt == wt) {
560                         
561                         /* running threads */
562                         if (wm_job->threads.first) {
563
564                                 /* let threads get temporary lock over main thread if needed */
565                                 wm_job_main_thread_yield(wm_job, false);
566                                 
567                                 /* always call note and update when ready */
568                                 if (wm_job->do_update || wm_job->ready) {
569                                         if (wm_job->update)
570                                                 wm_job->update(wm_job->run_customdata);
571                                         if (wm_job->note)
572                                                 WM_event_add_notifier(C, wm_job->note, NULL);
573
574                                         if (wm_job->flag & WM_JOB_PROGRESS)
575                                                 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
576                                         wm_job->do_update = FALSE;
577                                 }
578                                 
579                                 if (wm_job->ready) {
580                                         if (wm_job->endjob)
581                                                 wm_job->endjob(wm_job->run_customdata);
582
583                                         /* free own data */
584                                         wm_job->run_free(wm_job->run_customdata);
585                                         wm_job->run_customdata = NULL;
586                                         wm_job->run_free = NULL;
587                                         
588                                         // if (wm_job->stop) printf("job ready but stopped %s\n", wm_job->name);
589                                         // else printf("job finished %s\n", wm_job->name);
590
591                                         if (G.debug & G_DEBUG_JOBS) {
592                                                 printf("Job '%s' finished in %f seconds\n", wm_job->name,
593                                                        PIL_check_seconds_timer() - wm_job->start_time);
594                                         }
595
596                                         wm_job->running = FALSE;
597                                         wm_job_main_thread_yield(wm_job, true);
598                                         BLI_end_threads(&wm_job->threads);
599                                         wm_job->main_thread_mutex_ending = false;
600                                         
601                                         if (wm_job->endnote)
602                                                 WM_event_add_notifier(C, wm_job->endnote, NULL);
603                                         
604                                         WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
605
606                                         /* new job added for wm_job? */
607                                         if (wm_job->customdata) {
608                                                 // printf("job restarted with new data %s\n", wm_job->name);
609                                                 WM_jobs_start(wm, wm_job);
610                                         }
611                                         else {
612                                                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
613                                                 wm_job->wt = NULL;
614                                                 
615                                                 /* remove wm_job */
616                                                 wm_job_free(wm, wm_job);
617                                         }
618                                 }
619                                 else if (wm_job->flag & WM_JOB_PROGRESS) {
620                                         /* accumulate global progress for running jobs */
621                                         jobs_progress++;
622                                         total_progress += wm_job->progress;
623                                 }
624                         }
625                         else if (wm_job->suspended) {
626                                 WM_jobs_start(wm, wm_job);
627                         }
628                 }
629                 else if (wm_job->threads.first && !wm_job->ready) {
630                         if (wm_job->flag & WM_JOB_PROGRESS) {
631                                 /* accumulate global progress for running jobs */
632                                 jobs_progress++;
633                                 total_progress += wm_job->progress;
634                         }
635                 }
636         }
637         
638         /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
639         if (wm->winactive) {
640                 /* if there are running jobs, set the global progress indicator */
641                 if (jobs_progress > 0) {
642                         float progress = total_progress / (float)jobs_progress;
643                         WM_progress_set(wm->winactive, progress);
644                 }
645                 else {
646                         WM_progress_clear(wm->winactive);
647                 }
648         }
649 }
650
651 bool WM_jobs_has_running(wmWindowManager *wm)
652 {
653         wmJob *wm_job;
654
655         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
656                 if (wm_job->running) {
657                         return TRUE;
658                 }
659         }
660
661         return FALSE;
662 }