cb229c301b24774ce9e6fe0ff6162b1fcfc10ffa
[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 #include "ED_screen.h"
52
53 #include "RNA_types.h"
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);
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         
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 {
186         steve->startjob= startjob;
187         steve->initjob= initjob;
188         steve->update= update;
189 }
190
191 static void *do_job_thread(void *job_v)
192 {
193         wmJob *steve= job_v;
194         
195         steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update);
196         steve->ready= 1;
197         
198         return NULL;
199 }
200
201 /* dont allow same startjob to be executed twice */
202 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
203 {
204         wmJob *steve;
205         int suspend= 0;
206         
207         for(steve= wm->jobs.first; steve; steve= steve->next) {
208                 if(steve==test || !steve->running) continue;
209                 if(steve->startjob!=test->startjob && !(test->flag & WM_JOB_EXCL_RENDER)) continue;
210                 if((test->flag & WM_JOB_EXCL_RENDER) && !(steve->flag & WM_JOB_EXCL_RENDER)) continue;
211
212                 suspend= 1;
213
214                 /* if this job has higher priority, stop others */
215                 if(test->flag & WM_JOB_PRIORITY)
216                         steve->stop= 1;
217         }
218
219         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
220         test->suspended= suspend;
221 }
222
223 /* if job running, the same owner gave it a new job */
224 /* if different owner starts existing startjob, it suspends itself */
225 void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
226 {
227         if(steve->running) {
228                 /* signal job to end and restart */
229                 steve->stop= 1;
230         }
231         else {
232                 if(steve->customdata && steve->startjob) {
233                         
234                         wm_jobs_test_suspend_stop(wm, steve);
235                         
236                         if(steve->suspended==0) {
237                                 /* copy to ensure proper free in end */
238                                 steve->run_customdata= steve->customdata;
239                                 steve->run_free= steve->free;
240                                 steve->free= NULL;
241                                 steve->customdata= NULL;
242                                 steve->running= 1;
243                                 
244                                 if(steve->initjob)
245                                         steve->initjob(steve->run_customdata);
246                                 
247                                 steve->stop= 0;
248                                 steve->ready= 0;
249
250                                 BLI_init_threads(&steve->threads, do_job_thread, 1);
251                                 BLI_insert_thread(&steve->threads, steve);
252
253                                 // printf("job started\n");
254                         }
255                         
256                         /* restarted job has timer already */
257                         if(steve->wt==NULL)
258                                 steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
259                 }
260                 else printf("job fails, not initialized\n");
261         }
262 }
263
264 /* stop job, free data completely */
265 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
266 {
267         if(steve->running) {
268                 /* signal job to end */
269                 steve->stop= 1;
270                 BLI_end_threads(&steve->threads);
271         }
272         
273         if(steve->wt)
274                 WM_event_remove_timer(wm, steve->win, steve->wt);
275         if(steve->customdata)
276                 steve->free(steve->customdata);
277         if(steve->run_customdata)
278                 steve->run_free(steve->run_customdata);
279         
280         /* remove steve */
281         BLI_remlink(&wm->jobs, steve);
282         MEM_freeN(steve);
283         
284 }
285
286 void WM_jobs_stop_all(wmWindowManager *wm)
287 {
288         wmJob *steve;
289         
290         while((steve= wm->jobs.first))
291                 wm_jobs_kill_job(wm, steve);
292         
293 }
294
295 /* signal job(s) from this owner to stop, timer is required to get handled */
296 void WM_jobs_stop(wmWindowManager *wm, void *owner)
297 {
298         wmJob *steve;
299         
300         for(steve= wm->jobs.first; steve; steve= steve->next)
301                 if(steve->owner==owner)
302                         if(steve->running)
303                                 steve->stop= 1;
304 }
305
306 /* actually terminate thread and job timer */
307 void WM_jobs_kill(wmWindowManager *wm, void *owner)
308 {
309         wmJob *steve;
310         
311         for(steve= wm->jobs.first; steve; steve= steve->next)
312                 if(steve->owner==owner)
313                         break;
314         
315         if (steve) 
316                 wm_jobs_kill_job(wm, steve);
317 }
318
319
320 /* kill job entirely, also removes timer itself */
321 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
322 {
323         wmJob *steve;
324         
325         for(steve= wm->jobs.first; steve; steve= steve->next) {
326                 if(steve->wt==wt) {
327                         wm_jobs_kill_job(wm, steve);
328                         return;
329                 }
330         }
331 }
332
333 /* hardcoded to event TIMERJOBS */
334 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
335 {
336         wmJob *steve= wm->jobs.first, *stevenext;
337         
338         for(; steve; steve= stevenext) {
339                 stevenext= steve->next;
340                 
341                 if(steve->wt==wt) {
342                         
343                         /* running threads */
344                         if(steve->threads.first) {
345                                 
346                                 /* always call note and update when ready */
347                                 if(steve->do_update || steve->ready) {
348                                         if(steve->update)
349                                                 steve->update(steve->run_customdata);
350                                         if(steve->note)
351                                                 WM_event_add_notifier(C, steve->note, NULL);
352                                         steve->do_update= 0;
353                                 }       
354                                 
355                                 if(steve->ready) {
356                                         /* free own data */
357                                         steve->run_free(steve->run_customdata);
358                                         steve->run_customdata= NULL;
359                                         steve->run_free= NULL;
360                                         
361                                         //      if(steve->stop) printf("job stopped\n");
362                                         //      else printf("job finished\n");
363
364                                         steve->running= 0;
365                                         BLI_end_threads(&steve->threads);
366                                         
367                                         if(steve->endnote)
368                                                 WM_event_add_notifier(C, steve->endnote, NULL);
369                                         
370                                         /* new job added for steve? */
371                                         if(steve->customdata) {
372                                                 WM_jobs_start(wm, steve);
373                                         }
374                                         else {
375                                                 WM_event_remove_timer(wm, steve->win, steve->wt);
376                                                 steve->wt= NULL;
377                                                 
378                                                 /* remove steve */
379                                                 BLI_remlink(&wm->jobs, steve);
380                                                 MEM_freeN(steve);
381                                         }
382                                 }
383                         }
384                         else if(steve->suspended) {
385                                 WM_jobs_start(wm, steve);
386                         }
387                 }
388         }
389 }
390