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