3c3e2c0feaad7b530b7d76f163ebaef3813ac6b8
[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 printf("job fails, not initialized\n");
367         }
368 }
369
370 /* stop job, end thread, free data completely */
371 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job)
372 {
373         if (wm_job->running) {
374                 /* signal job to end */
375                 wm_job->stop = TRUE;
376                 BLI_end_threads(&wm_job->threads);
377
378                 if (wm_job->endjob)
379                         wm_job->endjob(wm_job->run_customdata);
380         }
381         
382         if (wm_job->wt)
383                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
384         if (wm_job->customdata)
385                 wm_job->free(wm_job->customdata);
386         if (wm_job->run_customdata)
387                 wm_job->run_free(wm_job->run_customdata);
388         
389         /* remove wm_job */
390         BLI_remlink(&wm->jobs, wm_job);
391         MEM_freeN(wm_job);
392         
393 }
394
395 /* wait until every job ended */
396 void WM_jobs_kill_all(wmWindowManager *wm)
397 {
398         wmJob *wm_job;
399         
400         while ((wm_job = wm->jobs.first))
401                 wm_jobs_kill_job(wm, wm_job);
402         
403 }
404
405 /* wait until every job ended, except for one owner (used in undo to keep screen job alive) */
406 void WM_jobs_kill_all_except(wmWindowManager *wm, void *owner)
407 {
408         wmJob *wm_job, *next_job;
409         
410         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
411                 next_job = wm_job->next;
412
413                 if (wm_job->owner != owner)
414                         wm_jobs_kill_job(wm, wm_job);
415         }
416 }
417
418
419 void WM_jobs_kill_type(struct wmWindowManager *wm, int job_type)
420 {
421         wmJob *wm_job, *next_job;
422         
423         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
424                 next_job = wm_job->next;
425
426                 if (wm_job->job_type == job_type)
427                         wm_jobs_kill_job(wm, wm_job);
428         }
429 }
430
431 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
432 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
433 {
434         wmJob *wm_job;
435         
436         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
437                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
438                         if (wm_job->running) {
439                                 wm_job->stop = TRUE;
440                         }
441                 }
442         }
443 }
444
445 /* actually terminate thread and job timer */
446 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
447 {
448         wmJob *wm_job;
449         
450         wm_job = wm->jobs.first;
451         while (wm_job) {
452                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
453                         wmJob *bill = wm_job;
454                         wm_job = wm_job->next;
455                         wm_jobs_kill_job(wm, bill);
456                 }
457                 else {
458                         wm_job = wm_job->next;
459                 }
460         }
461 }
462
463
464 /* kill job entirely, also removes timer itself */
465 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
466 {
467         wmJob *wm_job;
468         
469         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
470                 if (wm_job->wt == wt) {
471                         wm_jobs_kill_job(wm, wm_job);
472                         return;
473                 }
474         }
475 }
476
477 /* hardcoded to event TIMERJOBS */
478 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
479 {
480         wmJob *wm_job, *wm_jobnext;
481         float total_progress = 0.f;
482         float jobs_progress = 0;
483         
484         
485         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_jobnext) {
486                 wm_jobnext = wm_job->next;
487                 
488                 if (wm_job->wt == wt) {
489                         
490                         /* running threads */
491                         if (wm_job->threads.first) {
492                                 
493                                 /* always call note and update when ready */
494                                 if (wm_job->do_update || wm_job->ready) {
495                                         if (wm_job->update)
496                                                 wm_job->update(wm_job->run_customdata);
497                                         if (wm_job->note)
498                                                 WM_event_add_notifier(C, wm_job->note, NULL);
499
500                                         if (wm_job->flag & WM_JOB_PROGRESS)
501                                                 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
502                                         wm_job->do_update = FALSE;
503                                 }
504                                 
505                                 if (wm_job->ready) {
506                                         if (wm_job->endjob)
507                                                 wm_job->endjob(wm_job->run_customdata);
508
509                                         /* free own data */
510                                         wm_job->run_free(wm_job->run_customdata);
511                                         wm_job->run_customdata = NULL;
512                                         wm_job->run_free = NULL;
513                                         
514                                         // if (wm_job->stop) printf("job ready but stopped %s\n", wm_job->name);
515                                         // else printf("job finished %s\n", wm_job->name);
516
517                                         if (G.debug & G_DEBUG_JOBS) {
518                                                 printf("Job '%s' finished in %f seconds\n", wm_job->name,
519                                                        PIL_check_seconds_timer() - wm_job->start_time);
520                                         }
521
522                                         wm_job->running = FALSE;
523                                         BLI_end_threads(&wm_job->threads);
524                                         
525                                         if (wm_job->endnote)
526                                                 WM_event_add_notifier(C, wm_job->endnote, NULL);
527                                         
528                                         WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
529
530                                         /* so the info header updates on job end even if the mouse doesn't move.
531                                          * a rather annoying/obscure bug, see [#32537] (second reply) */
532                                         WM_event_add_mousemove_window(wm_job->win);
533
534                                         /* new job added for wm_job? */
535                                         if (wm_job->customdata) {
536                                                 // printf("job restarted with new data %s\n", wm_job->name);
537                                                 WM_jobs_start(wm, wm_job);
538                                         }
539                                         else {
540                                                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
541                                                 wm_job->wt = NULL;
542                                                 
543                                                 /* remove wm_job */
544                                                 BLI_remlink(&wm->jobs, wm_job);
545                                                 MEM_freeN(wm_job);
546                                         }
547                                 }
548                                 else if (wm_job->flag & WM_JOB_PROGRESS) {
549                                         /* accumulate global progress for running jobs */
550                                         jobs_progress++;
551                                         total_progress += wm_job->progress;
552                                 }
553                         }
554                         else if (wm_job->suspended) {
555                                 WM_jobs_start(wm, wm_job);
556                         }
557                 }
558                 else if (wm_job->threads.first && !wm_job->ready) {
559                         if (wm_job->flag & WM_JOB_PROGRESS) {
560                                 /* accumulate global progress for running jobs */
561                                 jobs_progress++;
562                                 total_progress += wm_job->progress;
563                         }
564                 }
565         }
566         
567         /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
568         if (wm->winactive) {
569                 /* if there are running jobs, set the global progress indicator */
570                 if (jobs_progress > 0) {
571                         float progress = total_progress / (float)jobs_progress;
572                         WM_progress_set(wm->winactive, progress);
573                 }
574                 else {
575                         WM_progress_clear(wm->winactive);
576                 }
577         }
578 }
579
580 int WM_jobs_has_running(wmWindowManager *wm)
581 {
582         wmJob *wm_job;
583
584         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
585                 if (wm_job->running) {
586                         return TRUE;
587                 }
588         }
589
590         return FALSE;
591 }