2.5
[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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, 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         short running, ready, do_update, stop;
111         
112         /* once running, we store this separately */
113         void *run_customdata;
114         void (*run_free)(void *);
115         
116         /* we use BLI_threads api, but per job only 1 thread runs */
117         ListBase threads;
118
119 };
120
121 /* ******************* public API ***************** */
122
123 /* returns current or adds new job, but doesnt run it */
124 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner)
125 {
126         wmJob *steve;
127         
128         for(steve= wm->jobs.first; steve; steve= steve->next)
129                 if(steve->owner==owner)
130                         break;
131         
132         if(steve==NULL) {
133                 steve= MEM_callocN(sizeof(wmJob), "new job");
134         
135                 BLI_addtail(&wm->jobs, steve);
136                 steve->win= win;
137                 steve->owner= owner;
138         }
139         
140         return steve;
141 }
142
143 /* returns true if job runs, for UI (progress) indicators */
144 int WM_jobs_test(wmWindowManager *wm, void *owner)
145 {
146         wmJob *steve;
147         
148         for(steve= wm->jobs.first; steve; steve= steve->next)
149                 if(steve->owner==owner)
150                         if(steve->running)
151                                 return 1;
152         return 0;
153 }
154
155 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
156 {
157         /* pending job? just free */
158         if(steve->customdata)
159                 steve->free(steve->customdata);
160         
161         steve->customdata= customdata;
162         steve->free= free;
163
164         if(steve->running) {
165                 /* signal job to end */
166                 steve->stop= 1;
167         }
168 }
169
170 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
171 {
172         steve->timestep = timestep;
173         steve->note = note;
174         steve->endnote = endnote;
175 }
176
177 void WM_jobs_callbacks(wmJob *steve, 
178                                            void (*startjob)(void *, short *, short *),
179                                            void (*initjob)(void *),
180                                            void (*update)(void  *))
181 {
182         steve->startjob= startjob;
183         steve->initjob= initjob;
184         steve->update= update;
185 }
186
187 static void *do_job_thread(void *job_v)
188 {
189         wmJob *steve= job_v;
190         
191         steve->stop= steve->ready= 0;
192         steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update);
193         steve->ready= 1;
194         
195         return NULL;
196 }
197
198 void WM_jobs_start(wmJob *steve)
199 {
200         if(steve->running) {
201                 /* signal job to end and restart */
202                 steve->stop= 1;
203         }
204         else {
205                 if(steve->customdata && steve->startjob) {
206                         
207                         /* copy to ensure proper free in end */
208                         steve->run_customdata= steve->customdata;
209                         steve->run_free= steve->free;
210                         steve->free= NULL;
211                         steve->customdata= NULL;
212                         steve->running= 1;
213                         
214                         if(steve->initjob)
215                                 steve->initjob(steve->run_customdata);
216                         
217                         BLI_init_threads(&steve->threads, do_job_thread, 1);
218                         BLI_insert_thread(&steve->threads, steve);
219
220                         // printf("job started\n");
221                         
222                         /* restarted job has timer already */
223                         if(steve->wt==NULL)
224                                 steve->wt= WM_event_add_window_timer(steve->win, TIMERJOBS, steve->timestep);
225                 }
226                 else printf("job fails, not initialized\n");
227         }
228 }
229
230 void WM_jobs_stop_all(wmWindowManager *wm)
231 {
232         wmJob *steve= wm->jobs.first;
233         
234         for(; steve; steve= steve->next) {
235                 if(steve->running) {
236                         /* signal job to end */
237                         steve->stop= 1;
238                         BLI_end_threads(&steve->threads);
239                 }
240                 
241                 if(steve->wt)
242                         WM_event_remove_window_timer(steve->win, steve->wt);
243                 if(steve->customdata)
244                         steve->free(steve->customdata);
245                 if(steve->run_customdata)
246                         steve->run_free(steve->run_customdata);
247         }       
248         
249         BLI_freelistN(&wm->jobs);
250 }
251
252 /* stops job(s) from this owner */
253 void WM_jobs_stop(wmWindowManager *wm, void *owner)
254 {
255         wmJob *steve;
256         
257         for(steve= wm->jobs.first; steve; steve= steve->next)
258                 if(steve->owner==owner)
259                         if(steve->running)
260                                 steve->stop= 1;
261 }
262
263 /* hardcoded to event TIMERJOBS */
264 static int wm_jobs_timer(bContext *C, wmOperator *op, wmEvent *evt)
265 {
266         wmWindowManager *wm= CTX_wm_manager(C);
267         wmJob *steve= wm->jobs.first;
268         
269         for(; steve; steve= steve->next) {
270                 
271                 if(evt->customdata==steve->wt) {
272                         /* running threads */
273                         if(steve->threads.first) {
274                                 
275                                 /* always call note and update when ready */
276                                 if(steve->do_update || steve->ready) {
277                                         if(steve->update)
278                                                 steve->update(steve->run_customdata);
279                                         if(steve->note)
280                                                 WM_event_add_notifier(C, steve->note, NULL);
281                                         steve->do_update= 0;
282                                 }       
283                                 
284                                 if(steve->ready) {
285                                         /* free own data */
286                                         steve->run_free(steve->run_customdata);
287                                         steve->run_customdata= NULL;
288                                         steve->run_free= NULL;
289                                         
290                                         //      if(steve->stop) printf("job stopped\n");
291                                         //      else printf("job finished\n");
292
293                                         steve->running= 0;
294                                         BLI_end_threads(&steve->threads);
295                                         
296                                         if(steve->endnote)
297                                                 WM_event_add_notifier(C, steve->endnote, NULL);
298                                         
299                                         /* new job added for steve? */
300                                         if(steve->customdata) {
301                                                 WM_jobs_start(steve);
302                                         }
303                                         else {
304                                                 WM_event_remove_window_timer(steve->win, steve->wt);
305                                                 steve->wt= NULL;
306                                                 
307                                                 /* remove steve */
308                                                 BLI_remlink(&wm->jobs, steve);
309                                                 MEM_freeN(steve);
310                                         }
311                                 }
312                         }
313                         return OPERATOR_FINISHED;
314                 }
315         }
316         return OPERATOR_PASS_THROUGH;
317 }
318
319 void WM_OT_jobs_timer(wmOperatorType *ot)
320 {
321         /* identifiers */
322         ot->name= "Jobs timer";
323         ot->idname= "WM_OT_jobs_timer";
324         
325         /* api callbacks */
326         ot->invoke= wm_jobs_timer;
327         
328         ot->poll= ED_operator_screenactive;
329         
330 }