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