code cleanup: favor braces when blocks have mixed brace use.
[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
31
32 #include <string.h>
33
34 #include "DNA_windowmanager_types.h"
35
36 #include "MEM_guardedalloc.h"
37
38 #include "BLI_blenlib.h"
39 #include "BLI_threads.h"
40 #include "BLI_utildefines.h"
41
42 #include "BKE_blender.h"
43 #include "BKE_context.h"
44 #include "BKE_idprop.h"
45 #include "BKE_global.h"
46 #include "BKE_library.h"
47 #include "BKE_main.h"
48 #include "BKE_report.h"
49
50 #include "WM_api.h"
51 #include "WM_types.h"
52 #include "wm_window.h"
53 #include "wm_event_system.h"
54 #include "wm_event_types.h"
55 #include "wm.h"
56
57 #include "PIL_time.h"
58
59 /* ********************** Threaded Jobs Manager ****************************** */
60
61 /*
62  * Add new job
63  * - register in WM
64  * - configure callbacks
65  *
66  * Start or re-run job
67  * - if job running
68  *   - signal job to end
69  *   - add timer notifier to verify when it has ended, to start it
70  * - else
71  *   - start job
72  *   - add timer notifier to handle progress
73  *
74  * Stop job
75  *   - signal job to end
76  *  on end, job will tag itself as sleeping
77  *
78  * Remove job
79  * - signal job to end
80  *  on end, job will remove itself
81  *
82  * When job is done:
83  * - it puts timer to sleep (or removes?)
84  *
85  */
86  
87 struct wmJob {
88         struct wmJob *next, *prev;
89         
90         /* job originating from, keep track of this when deleting windows */
91         wmWindow *win;
92         
93         /* should store entire own context, for start, update, free */
94         void *customdata;
95         /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
96         void (*initjob)(void *);
97         /* this runs inside thread, and does full job */
98         void (*startjob)(void *, short *stop, short *do_update, float *progress);
99         /* update gets called if thread defines so, and max once per timerstep */
100         /* it runs outside thread, blocking blender, no drawing! */
101         void (*update)(void *);
102         /* free entire customdata, doesn't run in thread */
103         void (*free)(void *);
104         /* gets called when job is stopped, not in thread */
105         void (*endjob)(void *);
106         
107         /* running jobs each have own timer */
108         double timestep;
109         wmTimer *wt;
110         /* the notifier event timers should send */
111         unsigned int note, endnote;
112         
113         
114 /* internal */
115         void *owner;
116         int flag;
117         short suspended, running, ready, do_update, stop, job_type;
118         float progress;
119
120         /* for display in header, identification */
121         char name[128];
122         
123         /* once running, we store this separately */
124         void *run_customdata;
125         void (*run_free)(void *);
126         
127         /* we use BLI_threads api, but per job only 1 thread runs */
128         ListBase threads;
129
130         double start_time;
131 };
132
133 /* finds:
134  * if type, compare for it, otherwise any matching job 
135  */
136 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const int job_type)
137 {
138         wmJob *wm_job;
139         
140         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
141                 if (wm_job->owner == owner) {
142                         
143                         if (job_type) {
144                                 if ( wm_job->job_type == job_type)
145                                         return wm_job;
146                         }
147                         else
148                                 return wm_job;
149                 }
150         
151         return NULL;
152 }
153
154 /* ******************* public API ***************** */
155
156 /* returns current or adds new job, but doesnt run it */
157 /* every owner only gets a single job, adding a new one will stop running job and 
158  * when stopped it starts the new one */
159 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag, int job_type)
160 {
161         wmJob *wm_job = wm_job_find(wm, owner, job_type);
162         
163         if (wm_job == NULL) {
164                 wm_job = MEM_callocN(sizeof(wmJob), "new job");
165         
166                 BLI_addtail(&wm->jobs, wm_job);
167                 wm_job->win = win;
168                 wm_job->owner = owner;
169                 wm_job->flag = flag;
170                 wm_job->job_type = job_type;
171                 BLI_strncpy(wm_job->name, name, sizeof(wm_job->name));
172         }
173         /* else: a running job, be careful */
174         
175         /* prevent creating a job with an invalid type */
176         BLI_assert(wm_job->job_type != WM_JOB_TYPE_ANY);
177
178         return wm_job;
179 }
180
181 /* returns true if job runs, for UI (progress) indicators */
182 int WM_jobs_test(wmWindowManager *wm, void *owner, int job_type)
183 {
184         wmJob *wm_job;
185         
186         /* job can be running or about to run (suspended) */
187         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
188                 if (wm_job->owner == owner) {
189                         if (job_type == WM_JOB_TYPE_ANY || (wm_job->job_type == job_type)) {
190                                 if (wm_job->running || wm_job->suspended) {
191                                         return TRUE;
192                                 }
193                         }
194                 }
195         }
196
197         return FALSE;
198 }
199
200 float WM_jobs_progress(wmWindowManager *wm, void *owner)
201 {
202         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
203         
204         if (wm_job && wm_job->flag & WM_JOB_PROGRESS)
205                 return wm_job->progress;
206         
207         return 0.0;
208 }
209
210 char *WM_jobs_name(wmWindowManager *wm, void *owner)
211 {
212         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
213         
214         if (wm_job)
215                 return wm_job->name;
216         
217         return NULL;
218 }
219
220 int WM_jobs_is_running(wmJob *wm_job)
221 {
222         return wm_job->running;
223 }
224
225 void *WM_jobs_customdata_get(wmJob *wm_job)
226 {
227         if (!wm_job->customdata) {
228                 return wm_job->run_customdata;
229         }
230         else {
231                 return wm_job->customdata;
232         }
233 }
234
235 void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void (*free)(void *))
236 {
237         /* pending job? just free */
238         if (wm_job->customdata)
239                 wm_job->free(wm_job->customdata);
240         
241         wm_job->customdata = customdata;
242         wm_job->free = free;
243
244         if (wm_job->running) {
245                 /* signal job to end */
246                 wm_job->stop = TRUE;
247         }
248 }
249
250 void WM_jobs_timer(wmJob *wm_job, double timestep, unsigned int note, unsigned int endnote)
251 {
252         wm_job->timestep = timestep;
253         wm_job->note = note;
254         wm_job->endnote = endnote;
255 }
256
257 void WM_jobs_callbacks(wmJob *wm_job,
258                        void (*startjob)(void *, short *, short *, float *),
259                        void (*initjob)(void *),
260                        void (*update)(void *),
261                        void (*endjob)(void *))
262 {
263         wm_job->startjob = startjob;
264         wm_job->initjob = initjob;
265         wm_job->update = update;
266         wm_job->endjob = endjob;
267 }
268
269 static void *do_job_thread(void *job_v)
270 {
271         wmJob *wm_job = job_v;
272         
273         wm_job->startjob(wm_job->run_customdata, &wm_job->stop, &wm_job->do_update, &wm_job->progress);
274         wm_job->ready = TRUE;
275         
276         return NULL;
277 }
278
279 /* don't allow same startjob to be executed twice */
280 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
281 {
282         wmJob *wm_job;
283         int suspend = FALSE;
284         
285         /* job added with suspend flag, we wait 1 timer step before activating it */
286         if (test->flag & WM_JOB_SUSPEND) {
287                 suspend = TRUE;
288                 test->flag &= ~WM_JOB_SUSPEND;
289         }
290         else {
291                 /* check other jobs */
292                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
293                         /* obvious case, no test needed */
294                         if (wm_job == test || !wm_job->running) {
295                                 continue;
296                         }
297                         
298                         /* if new job is not render, then check for same startjob */
299                         if (0 == (test->flag & WM_JOB_EXCL_RENDER))
300                                 if (wm_job->startjob != test->startjob)
301                                         continue;
302                         
303                         /* if new job is render, any render job should be stopped */
304                         if (test->flag & WM_JOB_EXCL_RENDER)
305                                 if (0 == (wm_job->flag & WM_JOB_EXCL_RENDER))
306                                         continue;
307
308                         suspend = TRUE;
309
310                         /* if this job has higher priority, stop others */
311                         if (test->flag & WM_JOB_PRIORITY) {
312                                 wm_job->stop = TRUE;
313                                 // printf("job stopped: %s\n", wm_job->name);
314                         }
315                 }
316         }
317         
318         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
319         test->suspended = suspend;
320         // if (suspend) printf("job suspended: %s\n", test->name);
321 }
322
323 /* if job running, the same owner gave it a new job */
324 /* if different owner starts existing startjob, it suspends itself */
325 void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
326 {
327         if (wm_job->running) {
328                 /* signal job to end and restart */
329                 wm_job->stop = TRUE;
330                 // printf("job started a running job, ending... %s\n", wm_job->name);
331         }
332         else {
333                 
334                 if (wm_job->customdata && wm_job->startjob) {
335                         
336                         wm_jobs_test_suspend_stop(wm, wm_job);
337                         
338                         if (wm_job->suspended == FALSE) {
339                                 /* copy to ensure proper free in end */
340                                 wm_job->run_customdata = wm_job->customdata;
341                                 wm_job->run_free = wm_job->free;
342                                 wm_job->free = NULL;
343                                 wm_job->customdata = NULL;
344                                 wm_job->running = TRUE;
345                                 
346                                 if (wm_job->initjob)
347                                         wm_job->initjob(wm_job->run_customdata);
348                                 
349                                 wm_job->stop = FALSE;
350                                 wm_job->ready = FALSE;
351                                 wm_job->progress = 0.0;
352
353                                 // printf("job started: %s\n", wm_job->name);
354                                 
355                                 BLI_init_threads(&wm_job->threads, do_job_thread, 1);
356                                 BLI_insert_thread(&wm_job->threads, wm_job);
357                         }
358                         
359                         /* restarted job has timer already */
360                         if (wm_job->wt == NULL)
361                                 wm_job->wt = WM_event_add_timer(wm, wm_job->win, TIMERJOBS, wm_job->timestep);
362
363                         if (G.debug & G_DEBUG_JOBS)
364                                 wm_job->start_time = PIL_check_seconds_timer();
365                 }
366                 else {
367                         printf("job fails, not initialized\n");
368                 }
369         }
370 }
371
372 /* stop job, end thread, free data completely */
373 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job)
374 {
375         if (wm_job->running) {
376                 /* signal job to end */
377                 wm_job->stop = TRUE;
378                 BLI_end_threads(&wm_job->threads);
379
380                 if (wm_job->endjob)
381                         wm_job->endjob(wm_job->run_customdata);
382         }
383         
384         if (wm_job->wt)
385                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
386         if (wm_job->customdata)
387                 wm_job->free(wm_job->customdata);
388         if (wm_job->run_customdata)
389                 wm_job->run_free(wm_job->run_customdata);
390         
391         /* remove wm_job */
392         BLI_remlink(&wm->jobs, wm_job);
393         MEM_freeN(wm_job);
394         
395 }
396
397 /* wait until every job ended */
398 void WM_jobs_kill_all(wmWindowManager *wm)
399 {
400         wmJob *wm_job;
401         
402         while ((wm_job = wm->jobs.first))
403                 wm_jobs_kill_job(wm, wm_job);
404         
405 }
406
407 /* wait until every job ended, except for one owner (used in undo to keep screen job alive) */
408 void WM_jobs_kill_all_except(wmWindowManager *wm, void *owner)
409 {
410         wmJob *wm_job, *next_job;
411         
412         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
413                 next_job = wm_job->next;
414
415                 if (wm_job->owner != owner)
416                         wm_jobs_kill_job(wm, wm_job);
417         }
418 }
419
420
421 void WM_jobs_kill_type(struct wmWindowManager *wm, int job_type)
422 {
423         wmJob *wm_job, *next_job;
424         
425         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
426                 next_job = wm_job->next;
427
428                 if (wm_job->job_type == job_type)
429                         wm_jobs_kill_job(wm, wm_job);
430         }
431 }
432
433 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
434 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
435 {
436         wmJob *wm_job;
437         
438         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
439                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
440                         if (wm_job->running) {
441                                 wm_job->stop = TRUE;
442                         }
443                 }
444         }
445 }
446
447 /* actually terminate thread and job timer */
448 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
449 {
450         wmJob *wm_job;
451         
452         wm_job = wm->jobs.first;
453         while (wm_job) {
454                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
455                         wmJob *bill = wm_job;
456                         wm_job = wm_job->next;
457                         wm_jobs_kill_job(wm, bill);
458                 }
459                 else {
460                         wm_job = wm_job->next;
461                 }
462         }
463 }
464
465
466 /* kill job entirely, also removes timer itself */
467 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
468 {
469         wmJob *wm_job;
470         
471         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
472                 if (wm_job->wt == wt) {
473                         wm_jobs_kill_job(wm, wm_job);
474                         return;
475                 }
476         }
477 }
478
479 /* hardcoded to event TIMERJOBS */
480 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
481 {
482         wmJob *wm_job, *wm_jobnext;
483         float total_progress = 0.f;
484         float jobs_progress = 0;
485         
486         
487         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_jobnext) {
488                 wm_jobnext = wm_job->next;
489                 
490                 if (wm_job->wt == wt) {
491                         
492                         /* running threads */
493                         if (wm_job->threads.first) {
494                                 
495                                 /* always call note and update when ready */
496                                 if (wm_job->do_update || wm_job->ready) {
497                                         if (wm_job->update)
498                                                 wm_job->update(wm_job->run_customdata);
499                                         if (wm_job->note)
500                                                 WM_event_add_notifier(C, wm_job->note, NULL);
501
502                                         if (wm_job->flag & WM_JOB_PROGRESS)
503                                                 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
504                                         wm_job->do_update = FALSE;
505                                 }
506                                 
507                                 if (wm_job->ready) {
508                                         if (wm_job->endjob)
509                                                 wm_job->endjob(wm_job->run_customdata);
510
511                                         /* free own data */
512                                         wm_job->run_free(wm_job->run_customdata);
513                                         wm_job->run_customdata = NULL;
514                                         wm_job->run_free = NULL;
515                                         
516                                         // if (wm_job->stop) printf("job ready but stopped %s\n", wm_job->name);
517                                         // else printf("job finished %s\n", wm_job->name);
518
519                                         if (G.debug & G_DEBUG_JOBS) {
520                                                 printf("Job '%s' finished in %f seconds\n", wm_job->name,
521                                                        PIL_check_seconds_timer() - wm_job->start_time);
522                                         }
523
524                                         wm_job->running = FALSE;
525                                         BLI_end_threads(&wm_job->threads);
526                                         
527                                         if (wm_job->endnote)
528                                                 WM_event_add_notifier(C, wm_job->endnote, NULL);
529                                         
530                                         WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
531
532                                         /* so the info header updates on job end even if the mouse doesn't move.
533                                          * a rather annoying/obscure bug, see [#32537] (second reply) */
534                                         WM_event_add_mousemove_window(wm_job->win);
535
536                                         /* new job added for wm_job? */
537                                         if (wm_job->customdata) {
538                                                 // printf("job restarted with new data %s\n", wm_job->name);
539                                                 WM_jobs_start(wm, wm_job);
540                                         }
541                                         else {
542                                                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
543                                                 wm_job->wt = NULL;
544                                                 
545                                                 /* remove wm_job */
546                                                 BLI_remlink(&wm->jobs, wm_job);
547                                                 MEM_freeN(wm_job);
548                                         }
549                                 }
550                                 else if (wm_job->flag & WM_JOB_PROGRESS) {
551                                         /* accumulate global progress for running jobs */
552                                         jobs_progress++;
553                                         total_progress += wm_job->progress;
554                                 }
555                         }
556                         else if (wm_job->suspended) {
557                                 WM_jobs_start(wm, wm_job);
558                         }
559                 }
560                 else if (wm_job->threads.first && !wm_job->ready) {
561                         if (wm_job->flag & WM_JOB_PROGRESS) {
562                                 /* accumulate global progress for running jobs */
563                                 jobs_progress++;
564                                 total_progress += wm_job->progress;
565                         }
566                 }
567         }
568         
569         /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
570         if (wm->winactive) {
571                 /* if there are running jobs, set the global progress indicator */
572                 if (jobs_progress > 0) {
573                         float progress = total_progress / (float)jobs_progress;
574                         WM_progress_set(wm->winactive, progress);
575                 }
576                 else {
577                         WM_progress_clear(wm->winactive);
578                 }
579         }
580 }
581
582 int WM_jobs_has_running(wmWindowManager *wm)
583 {
584         wmJob *wm_job;
585
586         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
587                 if (wm_job->running) {
588                         return TRUE;
589                 }
590         }
591
592         return FALSE;
593 }