Merge various small changes from render branch:
[blender-staging.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 "DNA_windowmanager_types.h"
30
31 #include "MEM_guardedalloc.h"
32
33 #include "BLI_blenlib.h"
34 #include "BLI_threads.h"
35
36 #include "BKE_blender.h"
37 #include "BKE_context.h"
38 #include "BKE_idprop.h"
39 #include "BKE_global.h"
40 #include "BKE_library.h"
41 #include "BKE_main.h"
42 #include "BKE_report.h"
43
44 #include "WM_api.h"
45 #include "WM_types.h"
46 #include "wm_window.h"
47 #include "wm_event_system.h"
48 #include "wm_event_types.h"
49 #include "wm.h"
50
51
52
53 /* ********************** Threaded Jobs Manager ****************************** */
54
55 /*
56 Add new job
57 - register in WM
58 - configure callbacks
59
60 Start or re-run job
61 - if job running
62   - signal job to end
63   - add timer notifier to verify when it has ended, to start it
64 - else
65   - start job
66   - add timer notifier to handle progress
67
68 Stop job
69   - signal job to end
70         on end, job will tag itself as sleeping
71
72 Remove job
73 - signal job to end
74         on end, job will remove itself
75
76 When job is done:
77 - it puts timer to sleep (or removes?)
78
79  */
80  
81 struct wmJob {
82         struct wmJob *next, *prev;
83         
84         /* job originating from, keep track of this when deleting windows */
85         wmWindow *win;
86         
87         /* should store entire own context, for start, update, free */
88         void *customdata;
89         /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
90         void (*initjob)(void *);
91         /* this runs inside thread, and does full job */
92         void (*startjob)(void *, short *stop, short *do_update);
93         /* update gets called if thread defines so, and max once per timerstep */
94         /* it runs outside thread, blocking blender, no drawing! */
95         void (*update)(void *);
96         /* free entire customdata, doesn't run in thread */
97         void (*free)(void *);
98         /* gets called when job is stopped, not in thread */
99         void (*endjob)(void *);
100         
101         /* running jobs each have own timer */
102         double timestep;
103         wmTimer *wt;
104         /* the notifier event timers should send */
105         unsigned int note, endnote;
106         
107         
108 /* internal */
109         void *owner;
110         int flag;
111         short suspended, running, ready, do_update, stop;
112         
113         /* once running, we store this separately */
114         void *run_customdata;
115         void (*run_free)(void *);
116         
117         /* we use BLI_threads api, but per job only 1 thread runs */
118         ListBase threads;
119
120 };
121
122 /* ******************* public API ***************** */
123
124 /* returns current or adds new job, but doesnt run it */
125 /* every owner only gets a single job, adding a new one will stop running stop and 
126    when stopped it starts the new one */
127 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, int flag)
128 {
129         wmJob *steve;
130         
131         for(steve= wm->jobs.first; steve; steve= steve->next)
132                 if(steve->owner==owner)
133                         break;
134         
135         if(steve==NULL) {
136                 steve= MEM_callocN(sizeof(wmJob), "new job");
137         
138                 BLI_addtail(&wm->jobs, steve);
139                 steve->win= win;
140                 steve->owner= owner;
141                 steve->flag= flag;
142         }
143         
144         return steve;
145 }
146
147 /* returns true if job runs, for UI (progress) indicators */
148 int WM_jobs_test(wmWindowManager *wm, void *owner)
149 {
150         wmJob *steve;
151         
152         for(steve= wm->jobs.first; steve; steve= steve->next)
153                 if(steve->owner==owner)
154                         if(steve->running)
155                                 return 1;
156         return 0;
157 }
158
159 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
160 {
161         /* pending job? just free */
162         if(steve->customdata)
163                 steve->free(steve->customdata);
164         
165         steve->customdata= customdata;
166         steve->free= free;
167
168         if(steve->running) {
169                 /* signal job to end */
170                 steve->stop= 1;
171         }
172 }
173
174 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
175 {
176         steve->timestep = timestep;
177         steve->note = note;
178         steve->endnote = endnote;
179 }
180
181 void WM_jobs_callbacks(wmJob *steve, 
182                                            void (*startjob)(void *, short *, short *),
183                                            void (*initjob)(void *),
184                                            void (*update)(void  *),
185                                            void (*endjob)(void  *))
186 {
187         steve->startjob= startjob;
188         steve->initjob= initjob;
189         steve->update= update;
190         steve->endjob= endjob;
191 }
192
193 static void *do_job_thread(void *job_v)
194 {
195         wmJob *steve= job_v;
196         
197         steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update);
198         steve->ready= 1;
199         
200         return NULL;
201 }
202
203 /* dont allow same startjob to be executed twice */
204 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
205 {
206         wmJob *steve;
207         int suspend= 0;
208         
209         for(steve= wm->jobs.first; steve; steve= steve->next) {
210                 if(steve==test || !steve->running) continue;
211                 if(steve->startjob!=test->startjob && !(test->flag & WM_JOB_EXCL_RENDER)) continue;
212                 if((test->flag & WM_JOB_EXCL_RENDER) && !(steve->flag & WM_JOB_EXCL_RENDER)) continue;
213
214                 suspend= 1;
215
216                 /* if this job has higher priority, stop others */
217                 if(test->flag & WM_JOB_PRIORITY)
218                         steve->stop= 1;
219         }
220
221         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
222         test->suspended= suspend;
223 }
224
225 /* if job running, the same owner gave it a new job */
226 /* if different owner starts existing startjob, it suspends itself */
227 void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
228 {
229         if(steve->running) {
230                 /* signal job to end and restart */
231                 steve->stop= 1;
232         }
233         else {
234                 if(steve->customdata && steve->startjob) {
235                         
236                         wm_jobs_test_suspend_stop(wm, steve);
237                         
238                         if(steve->suspended==0) {
239                                 /* copy to ensure proper free in end */
240                                 steve->run_customdata= steve->customdata;
241                                 steve->run_free= steve->free;
242                                 steve->free= NULL;
243                                 steve->customdata= NULL;
244                                 steve->running= 1;
245                                 
246                                 if(steve->initjob)
247                                         steve->initjob(steve->run_customdata);
248                                 
249                                 steve->stop= 0;
250                                 steve->ready= 0;
251
252                                 BLI_init_threads(&steve->threads, do_job_thread, 1);
253                                 BLI_insert_thread(&steve->threads, steve);
254
255                                 // printf("job started\n");
256                         }
257                         
258                         /* restarted job has timer already */
259                         if(steve->wt==NULL)
260                                 steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
261                 }
262                 else printf("job fails, not initialized\n");
263         }
264 }
265
266 /* stop job, free data completely */
267 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
268 {
269         if(steve->running) {
270                 /* signal job to end */
271                 steve->stop= 1;
272                 BLI_end_threads(&steve->threads);
273
274                 if(steve->endjob)
275                         steve->endjob(steve->run_customdata);
276         }
277         
278         if(steve->wt)
279                 WM_event_remove_timer(wm, steve->win, steve->wt);
280         if(steve->customdata)
281                 steve->free(steve->customdata);
282         if(steve->run_customdata)
283                 steve->run_free(steve->run_customdata);
284         
285         /* remove steve */
286         BLI_remlink(&wm->jobs, steve);
287         MEM_freeN(steve);
288         
289 }
290
291 void WM_jobs_stop_all(wmWindowManager *wm)
292 {
293         wmJob *steve;
294         
295         while((steve= wm->jobs.first))
296                 wm_jobs_kill_job(wm, steve);
297         
298 }
299
300 /* signal job(s) from this owner to stop, timer is required to get handled */
301 void WM_jobs_stop(wmWindowManager *wm, void *owner)
302 {
303         wmJob *steve;
304         
305         for(steve= wm->jobs.first; steve; steve= steve->next)
306                 if(steve->owner==owner)
307                         if(steve->running)
308                                 steve->stop= 1;
309 }
310
311 /* actually terminate thread and job timer */
312 void WM_jobs_kill(wmWindowManager *wm, void *owner)
313 {
314         wmJob *steve;
315         
316         for(steve= wm->jobs.first; steve; steve= steve->next)
317                 if(steve->owner==owner)
318                         break;
319         
320         if (steve) 
321                 wm_jobs_kill_job(wm, steve);
322 }
323
324
325 /* kill job entirely, also removes timer itself */
326 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
327 {
328         wmJob *steve;
329         
330         for(steve= wm->jobs.first; steve; steve= steve->next) {
331                 if(steve->wt==wt) {
332                         wm_jobs_kill_job(wm, steve);
333                         return;
334                 }
335         }
336 }
337
338 /* hardcoded to event TIMERJOBS */
339 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
340 {
341         wmJob *steve= wm->jobs.first, *stevenext;
342         
343         for(; steve; steve= stevenext) {
344                 stevenext= steve->next;
345                 
346                 if(steve->wt==wt) {
347                         
348                         /* running threads */
349                         if(steve->threads.first) {
350                                 
351                                 /* always call note and update when ready */
352                                 if(steve->do_update || steve->ready) {
353                                         if(steve->update)
354                                                 steve->update(steve->run_customdata);
355                                         if(steve->note)
356                                                 WM_event_add_notifier(C, steve->note, NULL);
357                                         steve->do_update= 0;
358                                 }       
359                                 
360                                 if(steve->ready) {
361                                         if(steve->endjob)
362                                                 steve->endjob(steve->run_customdata);
363
364                                         /* free own data */
365                                         steve->run_free(steve->run_customdata);
366                                         steve->run_customdata= NULL;
367                                         steve->run_free= NULL;
368                                         
369                                         //      if(steve->stop) printf("job stopped\n");
370                                         //      else printf("job finished\n");
371
372                                         steve->running= 0;
373                                         BLI_end_threads(&steve->threads);
374                                         
375                                         if(steve->endnote)
376                                                 WM_event_add_notifier(C, steve->endnote, NULL);
377                                         
378                                         /* new job added for steve? */
379                                         if(steve->customdata) {
380                                                 WM_jobs_start(wm, steve);
381                                         }
382                                         else {
383                                                 WM_event_remove_timer(wm, steve->win, steve->wt);
384                                                 steve->wt= NULL;
385                                                 
386                                                 /* remove steve */
387                                                 BLI_remlink(&wm->jobs, steve);
388                                                 MEM_freeN(steve);
389                                         }
390                                 }
391                         }
392                         else if(steve->suspended) {
393                                 WM_jobs_start(wm, steve);
394                         }
395                 }
396         }
397 }
398