Camera tracking integration
[blender.git] / source / blender / windowmanager / intern / wm_jobs.c
1 /*
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version. 
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2009 Blender Foundation.
21  * All rights reserved.
22  *
23  * 
24  * Contributor(s): Blender Foundation
25  *
26  * ***** END GPL LICENSE BLOCK *****
27  */
28
29 /** \file blender/windowmanager/intern/wm_jobs.c
30  *  \ingroup wm
31  */
32
33
34 #include <string.h>
35
36 #include "DNA_windowmanager_types.h"
37
38 #include "MEM_guardedalloc.h"
39
40 #include "BLI_blenlib.h"
41 #include "BLI_threads.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
59
60 /* ********************** Threaded Jobs Manager ****************************** */
61
62 /*
63 Add new job
64 - register in WM
65 - configure callbacks
66
67 Start or re-run job
68 - if job running
69   - signal job to end
70   - add timer notifier to verify when it has ended, to start it
71 - else
72   - start job
73   - add timer notifier to handle progress
74
75 Stop job
76   - signal job to end
77         on end, job will tag itself as sleeping
78
79 Remove job
80 - signal job to end
81         on end, job will remove itself
82
83 When job is done:
84 - it puts timer to sleep (or removes?)
85
86  */
87  
88 struct wmJob {
89         struct wmJob *next, *prev;
90         
91         /* job originating from, keep track of this when deleting windows */
92         wmWindow *win;
93         
94         /* should store entire own context, for start, update, free */
95         void *customdata;
96         /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
97         void (*initjob)(void *);
98         /* this runs inside thread, and does full job */
99         void (*startjob)(void *, short *stop, short *do_update, float *progress);
100         /* update gets called if thread defines so, and max once per timerstep */
101         /* it runs outside thread, blocking blender, no drawing! */
102         void (*update)(void *);
103         /* free entire customdata, doesn't run in thread */
104         void (*free)(void *);
105         /* gets called when job is stopped, not in thread */
106         void (*endjob)(void *);
107         
108         /* running jobs each have own timer */
109         double timestep;
110         wmTimer *wt;
111         /* the notifier event timers should send */
112         unsigned int note, endnote;
113         
114         
115 /* internal */
116         void *owner;
117         int flag;
118         short suspended, running, ready, do_update, stop;
119         float progress;
120
121         /* for display in header, identification */
122         char name[128];
123         
124         /* once running, we store this separately */
125         void *run_customdata;
126         void (*run_free)(void *);
127         
128         /* we use BLI_threads api, but per job only 1 thread runs */
129         ListBase threads;
130
131 };
132
133 /* finds:
134  * 1st priority: job with same owner and name
135  * 2nd priority: job with same owner
136  */
137 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const char *name)
138 {
139         wmJob *steve, *found=NULL;
140         
141         for(steve= wm->jobs.first; steve; steve= steve->next)
142                 if(steve->owner==owner) {
143                         found= steve;
144                         if (name && strcmp(steve->name, name)==0)
145                                 return steve;
146                 }
147         
148         return found;
149 }
150
151 /* ******************* public API ***************** */
152
153 /* returns current or adds new job, but doesnt run it */
154 /* every owner only gets a single job, adding a new one will stop running stop and 
155    when stopped it starts the new one */
156 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag)
157 {
158         wmJob *steve= wm_job_find(wm, owner, name);
159         
160         if(steve==NULL) {
161                 steve= MEM_callocN(sizeof(wmJob), "new job");
162         
163                 BLI_addtail(&wm->jobs, steve);
164                 steve->win= win;
165                 steve->owner= owner;
166                 steve->flag= flag;
167                 BLI_strncpy(steve->name, name, sizeof(steve->name));
168         }
169         
170         return steve;
171 }
172
173 /* returns true if job runs, for UI (progress) indicators */
174 int WM_jobs_test(wmWindowManager *wm, void *owner)
175 {
176         wmJob *steve;
177         
178         for(steve= wm->jobs.first; steve; steve= steve->next)
179                 if(steve->owner==owner)
180                         if(steve->running)
181                                 return 1;
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         } else {
215                 return steve->customdata;
216         }
217 }
218
219 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
220 {
221         /* pending job? just free */
222         if(steve->customdata)
223                 steve->free(steve->customdata);
224         
225         steve->customdata= customdata;
226         steve->free= free;
227
228         if(steve->running) {
229                 /* signal job to end */
230                 steve->stop= 1;
231         }
232 }
233
234 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
235 {
236         steve->timestep = timestep;
237         steve->note = note;
238         steve->endnote = endnote;
239 }
240
241 void WM_jobs_callbacks(wmJob *steve, 
242                                            void (*startjob)(void *, short *, short *, float *),
243                                            void (*initjob)(void *),
244                                            void (*update)(void  *),
245                                            void (*endjob)(void  *))
246 {
247         steve->startjob= startjob;
248         steve->initjob= initjob;
249         steve->update= update;
250         steve->endjob= endjob;
251 }
252
253 static void *do_job_thread(void *job_v)
254 {
255         wmJob *steve= job_v;
256         
257         steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update, &steve->progress);
258         steve->ready= 1;
259         
260         return NULL;
261 }
262
263 /* dont allow same startjob to be executed twice */
264 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
265 {
266         wmJob *steve;
267         int suspend= 0;
268         
269         /* job added with suspend flag, we wait 1 timer step before activating it */
270         if(test->flag & WM_JOB_SUSPEND) {
271                 suspend= 1;
272                 test->flag &= ~WM_JOB_SUSPEND;
273         }
274         else {
275                 /* check other jobs */
276                 for(steve= wm->jobs.first; steve; steve= steve->next) {
277                         /* obvious case, no test needed */
278                         if(steve==test || !steve->running) continue;
279                         
280                         /* if new job is not render, then check for same startjob */
281                         if(0==(test->flag & WM_JOB_EXCL_RENDER)) 
282                                 if(steve->startjob!=test->startjob)
283                                         continue;
284                         
285                         /* if new job is render, any render job should be stopped */
286                         if(test->flag & WM_JOB_EXCL_RENDER)
287                                 if(0==(steve->flag & WM_JOB_EXCL_RENDER))
288                                         continue;
289
290                         suspend= 1;
291
292                         /* if this job has higher priority, stop others */
293                         if(test->flag & WM_JOB_PRIORITY) {
294                                 steve->stop= 1;
295                                 // printf("job stopped: %s\n", steve->name);
296                         }
297                 }
298         }
299         
300         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
301         test->suspended= suspend;
302         // if(suspend) printf("job suspended: %s\n", test->name);
303 }
304
305 /* if job running, the same owner gave it a new job */
306 /* if different owner starts existing startjob, it suspends itself */
307 void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
308 {
309         if(steve->running) {
310                 /* signal job to end and restart */
311                 steve->stop= 1;
312                 // printf("job started a running job, ending... %s\n", steve->name);
313         }
314         else {
315                 
316                 if(steve->customdata && steve->startjob) {
317                         
318                         wm_jobs_test_suspend_stop(wm, steve);
319                         
320                         if(steve->suspended==0) {
321                                 /* copy to ensure proper free in end */
322                                 steve->run_customdata= steve->customdata;
323                                 steve->run_free= steve->free;
324                                 steve->free= NULL;
325                                 steve->customdata= NULL;
326                                 steve->running= 1;
327                                 
328                                 if(steve->initjob)
329                                         steve->initjob(steve->run_customdata);
330                                 
331                                 steve->stop= 0;
332                                 steve->ready= 0;
333                                 steve->progress= 0.0;
334
335                                 // printf("job started: %s\n", steve->name);
336                                 
337                                 BLI_init_threads(&steve->threads, do_job_thread, 1);
338                                 BLI_insert_thread(&steve->threads, steve);
339                         }
340                         
341                         /* restarted job has timer already */
342                         if(steve->wt==NULL)
343                                 steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
344                 }
345                 else printf("job fails, not initialized\n");
346         }
347 }
348
349 /* stop job, free data completely */
350 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
351 {
352         if(steve->running) {
353                 /* signal job to end */
354                 steve->stop= 1;
355                 BLI_end_threads(&steve->threads);
356
357                 if(steve->endjob)
358                         steve->endjob(steve->run_customdata);
359         }
360         
361         if(steve->wt)
362                 WM_event_remove_timer(wm, steve->win, steve->wt);
363         if(steve->customdata)
364                 steve->free(steve->customdata);
365         if(steve->run_customdata)
366                 steve->run_free(steve->run_customdata);
367         
368         /* remove steve */
369         BLI_remlink(&wm->jobs, steve);
370         MEM_freeN(steve);
371         
372 }
373
374 void WM_jobs_stop_all(wmWindowManager *wm)
375 {
376         wmJob *steve;
377         
378         while((steve= wm->jobs.first))
379                 wm_jobs_kill_job(wm, steve);
380         
381 }
382
383 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
384 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
385 {
386         wmJob *steve;
387         
388         for(steve= wm->jobs.first; steve; steve= steve->next)
389                 if(steve->owner==owner || steve->startjob==startjob)
390                         if(steve->running)
391                                 steve->stop= 1;
392 }
393
394 /* actually terminate thread and job timer */
395 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
396 {
397         wmJob *steve;
398         
399         steve= wm->jobs.first;
400         while(steve) {
401                 if(steve->owner==owner || steve->startjob==startjob) {
402                         wmJob* bill = steve;
403                         steve= steve->next;
404                         wm_jobs_kill_job(wm, bill);
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                                 } else if (steve->flag & WM_JOB_PROGRESS) {
487                                         /* accumulate global progress for running jobs */
488                                         jobs_progress++;
489                                         total_progress += steve->progress;
490                                 }
491                         }
492                         else if(steve->suspended) {
493                                 WM_jobs_start(wm, steve);
494                         }
495                 }
496         }
497         
498         /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
499         if(wm->winactive) {
500                 /* if there are running jobs, set the global progress indicator */
501                 if (jobs_progress > 0) {
502                         float progress = total_progress / (float)jobs_progress;
503                         WM_progress_set(wm->winactive, progress);
504                 } else {
505                         WM_progress_clear(wm->winactive);
506                 }
507         }
508 }
509