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