Merging r46203 through r46413 from trunk into soc-2011-tomato
[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
41 #include "BKE_blender.h"
42 #include "BKE_context.h"
43 #include "BKE_idprop.h"
44 #include "BKE_global.h"
45 #include "BKE_library.h"
46 #include "BKE_main.h"
47 #include "BKE_report.h"
48
49 #include "WM_api.h"
50 #include "WM_types.h"
51 #include "wm_window.h"
52 #include "wm_event_system.h"
53 #include "wm_event_types.h"
54 #include "wm.h"
55
56
57
58 /* ********************** Threaded Jobs Manager ****************************** */
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;
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 };
130
131 /* finds:
132  * 1st priority: job with same owner and name
133  * 2nd priority: job with same owner
134  */
135 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const char *name)
136 {
137         wmJob *steve, *found = NULL;
138         
139         for (steve = wm->jobs.first; steve; steve = steve->next)
140                 if (steve->owner == owner) {
141                         found = steve;
142                         if (name && strcmp(steve->name, name) == 0)
143                                 return steve;
144                 }
145         
146         return found;
147 }
148
149 /* ******************* public API ***************** */
150
151 /* returns current or adds new job, but doesnt run it */
152 /* every owner only gets a single job, adding a new one will stop running stop and 
153  * when stopped it starts the new one */
154 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag)
155 {
156         wmJob *steve = wm_job_find(wm, owner, name);
157         
158         if (steve == NULL) {
159                 steve = MEM_callocN(sizeof(wmJob), "new job");
160         
161                 BLI_addtail(&wm->jobs, steve);
162                 steve->win = win;
163                 steve->owner = owner;
164                 steve->flag = flag;
165                 BLI_strncpy(steve->name, name, sizeof(steve->name));
166         }
167         
168         return steve;
169 }
170
171 /* returns true if job runs, for UI (progress) indicators */
172 int WM_jobs_test(wmWindowManager *wm, void *owner)
173 {
174         wmJob *steve;
175         
176         /* job can be running or about to run (suspended) */
177         for (steve = wm->jobs.first; steve; steve = steve->next)
178                 if (steve->owner == owner)
179                         if (steve->running || steve->suspended)
180                                 return 1;
181
182         return 0;
183 }
184
185 float WM_jobs_progress(wmWindowManager *wm, void *owner)
186 {
187         wmJob *steve = wm_job_find(wm, owner, NULL);
188         
189         if (steve && steve->flag & WM_JOB_PROGRESS)
190                 return steve->progress;
191         
192         return 0.0;
193 }
194
195 char *WM_jobs_name(wmWindowManager *wm, void *owner)
196 {
197         wmJob *steve = wm_job_find(wm, owner, NULL);
198         
199         if (steve)
200                 return steve->name;
201         
202         return NULL;
203 }
204
205 int WM_jobs_is_running(wmJob *steve)
206 {
207         return steve->running;
208 }
209
210 void *WM_jobs_get_customdata(wmJob *steve)
211 {
212         if (!steve->customdata) {
213                 return steve->run_customdata;
214         }
215         else {
216                 return steve->customdata;
217         }
218 }
219
220 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
221 {
222         /* pending job? just free */
223         if (steve->customdata)
224                 steve->free(steve->customdata);
225         
226         steve->customdata = customdata;
227         steve->free = free;
228
229         if (steve->running) {
230                 /* signal job to end */
231                 steve->stop = 1;
232         }
233 }
234
235 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
236 {
237         steve->timestep = timestep;
238         steve->note = note;
239         steve->endnote = endnote;
240 }
241
242 void WM_jobs_callbacks(wmJob *steve, 
243                        void (*startjob)(void *, short *, short *, float *),
244                        void (*initjob)(void *),
245                        void (*update)(void *),
246                        void (*endjob)(void *))
247 {
248         steve->startjob = startjob;
249         steve->initjob = initjob;
250         steve->update = update;
251         steve->endjob = endjob;
252 }
253
254 static void *do_job_thread(void *job_v)
255 {
256         wmJob *steve = job_v;
257         
258         steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update, &steve->progress);
259         steve->ready = 1;
260         
261         return NULL;
262 }
263
264 /* don't allow same startjob to be executed twice */
265 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
266 {
267         wmJob *steve;
268         int suspend = 0;
269         
270         /* job added with suspend flag, we wait 1 timer step before activating it */
271         if (test->flag & WM_JOB_SUSPEND) {
272                 suspend = 1;
273                 test->flag &= ~WM_JOB_SUSPEND;
274         }
275         else {
276                 /* check other jobs */
277                 for (steve = wm->jobs.first; steve; steve = steve->next) {
278                         /* obvious case, no test needed */
279                         if (steve == test || !steve->running) continue;
280                         
281                         /* if new job is not render, then check for same startjob */
282                         if (0 == (test->flag & WM_JOB_EXCL_RENDER))
283                                 if (steve->startjob != test->startjob)
284                                         continue;
285                         
286                         /* if new job is render, any render job should be stopped */
287                         if (test->flag & WM_JOB_EXCL_RENDER)
288                                 if (0 == (steve->flag & WM_JOB_EXCL_RENDER))
289                                         continue;
290
291                         suspend = 1;
292
293                         /* if this job has higher priority, stop others */
294                         if (test->flag & WM_JOB_PRIORITY) {
295                                 steve->stop = 1;
296                                 // printf("job stopped: %s\n", steve->name);
297                         }
298                 }
299         }
300         
301         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
302         test->suspended = suspend;
303         // if (suspend) printf("job suspended: %s\n", test->name);
304 }
305
306 /* if job running, the same owner gave it a new job */
307 /* if different owner starts existing startjob, it suspends itself */
308 void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
309 {
310         if (steve->running) {
311                 /* signal job to end and restart */
312                 steve->stop = 1;
313                 // printf("job started a running job, ending... %s\n", steve->name);
314         }
315         else {
316                 
317                 if (steve->customdata && steve->startjob) {
318                         
319                         wm_jobs_test_suspend_stop(wm, steve);
320                         
321                         if (steve->suspended == 0) {
322                                 /* copy to ensure proper free in end */
323                                 steve->run_customdata = steve->customdata;
324                                 steve->run_free = steve->free;
325                                 steve->free = NULL;
326                                 steve->customdata = NULL;
327                                 steve->running = 1;
328                                 
329                                 if (steve->initjob)
330                                         steve->initjob(steve->run_customdata);
331                                 
332                                 steve->stop = 0;
333                                 steve->ready = 0;
334                                 steve->progress = 0.0;
335
336                                 // printf("job started: %s\n", steve->name);
337                                 
338                                 BLI_init_threads(&steve->threads, do_job_thread, 1);
339                                 BLI_insert_thread(&steve->threads, steve);
340                         }
341                         
342                         /* restarted job has timer already */
343                         if (steve->wt == NULL)
344                                 steve->wt = WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
345                 }
346                 else printf("job fails, not initialized\n");
347         }
348 }
349
350 /* stop job, free data completely */
351 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
352 {
353         if (steve->running) {
354                 /* signal job to end */
355                 steve->stop = 1;
356                 BLI_end_threads(&steve->threads);
357
358                 if (steve->endjob)
359                         steve->endjob(steve->run_customdata);
360         }
361         
362         if (steve->wt)
363                 WM_event_remove_timer(wm, steve->win, steve->wt);
364         if (steve->customdata)
365                 steve->free(steve->customdata);
366         if (steve->run_customdata)
367                 steve->run_free(steve->run_customdata);
368         
369         /* remove steve */
370         BLI_remlink(&wm->jobs, steve);
371         MEM_freeN(steve);
372         
373 }
374
375 void WM_jobs_stop_all(wmWindowManager *wm)
376 {
377         wmJob *steve;
378         
379         while ((steve = wm->jobs.first))
380                 wm_jobs_kill_job(wm, steve);
381         
382 }
383
384 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
385 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
386 {
387         wmJob *steve;
388         
389         for (steve = wm->jobs.first; steve; steve = steve->next)
390                 if (steve->owner == owner || steve->startjob == startjob)
391                         if (steve->running)
392                                 steve->stop = 1;
393 }
394
395 /* actually terminate thread and job timer */
396 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
397 {
398         wmJob *steve;
399         
400         steve = wm->jobs.first;
401         while (steve) {
402                 if (steve->owner == owner || steve->startjob == startjob) {
403                         wmJob *bill = steve;
404                         steve = steve->next;
405                         wm_jobs_kill_job(wm, bill);
406                 }
407                 else {
408                         steve = steve->next;
409                 }
410         }
411 }
412
413
414 /* kill job entirely, also removes timer itself */
415 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
416 {
417         wmJob *steve;
418         
419         for (steve = wm->jobs.first; steve; steve = steve->next) {
420                 if (steve->wt == wt) {
421                         wm_jobs_kill_job(wm, steve);
422                         return;
423                 }
424         }
425 }
426
427 /* hardcoded to event TIMERJOBS */
428 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
429 {
430         wmJob *steve = wm->jobs.first, *stevenext;
431         float total_progress = 0.f;
432         float jobs_progress = 0;
433         
434         
435         for (; steve; steve = stevenext) {
436                 stevenext = steve->next;
437                 
438                 if (steve->wt == wt) {
439                         
440                         /* running threads */
441                         if (steve->threads.first) {
442                                 
443                                 /* always call note and update when ready */
444                                 if (steve->do_update || steve->ready) {
445                                         if (steve->update)
446                                                 steve->update(steve->run_customdata);
447                                         if (steve->note)
448                                                 WM_event_add_notifier(C, steve->note, NULL);
449
450                                         if (steve->flag & WM_JOB_PROGRESS)
451                                                 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
452                                         steve->do_update = 0;
453                                 }       
454                                 
455                                 if (steve->ready) {
456                                         if (steve->endjob)
457                                                 steve->endjob(steve->run_customdata);
458
459                                         /* free own data */
460                                         steve->run_free(steve->run_customdata);
461                                         steve->run_customdata = NULL;
462                                         steve->run_free = NULL;
463                                         
464                                         // if (steve->stop) printf("job ready but stopped %s\n", steve->name);
465                                         // else printf("job finished %s\n", steve->name);
466
467                                         steve->running = 0;
468                                         BLI_end_threads(&steve->threads);
469                                         
470                                         if (steve->endnote)
471                                                 WM_event_add_notifier(C, steve->endnote, NULL);
472                                         
473                                         WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
474                                         
475                                         /* new job added for steve? */
476                                         if (steve->customdata) {
477                                                 // printf("job restarted with new data %s\n", steve->name);
478                                                 WM_jobs_start(wm, steve);
479                                         }
480                                         else {
481                                                 WM_event_remove_timer(wm, steve->win, steve->wt);
482                                                 steve->wt = NULL;
483                                                 
484                                                 /* remove steve */
485                                                 BLI_remlink(&wm->jobs, steve);
486                                                 MEM_freeN(steve);
487                                         }
488                                 }
489                                 else if (steve->flag & WM_JOB_PROGRESS) {
490                                         /* accumulate global progress for running jobs */
491                                         jobs_progress++;
492                                         total_progress += steve->progress;
493                                 }
494                         }
495                         else if (steve->suspended) {
496                                 WM_jobs_start(wm, steve);
497                         }
498                 }
499                 else if (steve->threads.first && !steve->ready) {
500                         if (steve->flag & WM_JOB_PROGRESS) {
501                                 /* accumulate global progress for running jobs */
502                                 jobs_progress++;
503                                 total_progress += steve->progress;
504                         }
505                 }
506         }
507         
508         /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
509         if (wm->winactive) {
510                 /* if there are running jobs, set the global progress indicator */
511                 if (jobs_progress > 0) {
512                         float progress = total_progress / (float)jobs_progress;
513                         WM_progress_set(wm->winactive, progress);
514                 }
515                 else {
516                         WM_progress_clear(wm->winactive);
517                 }
518         }
519 }
520
521 int WM_jobs_has_running(wmWindowManager *wm)
522 {
523         wmJob *steve;
524
525         for (steve = wm->jobs.first; steve; steve = steve->next)
526                 if (steve->running)
527                         return 1;
528
529         return 0;
530 }