4 * ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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.
20 * The Original Code is Copyright (C) 2009 Blender Foundation.
21 * All rights reserved.
24 * Contributor(s): Blender Foundation
26 * ***** END GPL LICENSE BLOCK *****
29 #include "DNA_windowmanager_types.h"
31 #include "MEM_guardedalloc.h"
33 #include "BLI_blenlib.h"
34 #include "BLI_threads.h"
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"
42 #include "BKE_report.h"
46 #include "wm_window.h"
47 #include "wm_event_system.h"
48 #include "wm_event_types.h"
53 /* ********************** Threaded Jobs Manager ****************************** */
63 - add timer notifier to verify when it has ended, to start it
66 - add timer notifier to handle progress
70 on end, job will tag itself as sleeping
74 on end, job will remove itself
77 - it puts timer to sleep (or removes?)
82 struct wmJob *next, *prev;
84 /* job originating from, keep track of this when deleting windows */
87 /* should store entire own context, for start, update, free */
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 */
99 /* running jobs each have own timer */
102 /* the notifier event timers should send */
103 unsigned int note, endnote;
109 short suspended, running, ready, do_update, stop;
111 /* once running, we store this separately */
112 void *run_customdata;
113 void (*run_free)(void *);
115 /* we use BLI_threads api, but per job only 1 thread runs */
120 /* ******************* public API ***************** */
122 /* returns current or adds new job, but doesnt run it */
123 /* every owner only gets a single job, adding a new one will stop running stop and
124 when stopped it starts the new one */
125 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, int flag)
129 for(steve= wm->jobs.first; steve; steve= steve->next)
130 if(steve->owner==owner)
134 steve= MEM_callocN(sizeof(wmJob), "new job");
136 BLI_addtail(&wm->jobs, steve);
145 /* returns true if job runs, for UI (progress) indicators */
146 int WM_jobs_test(wmWindowManager *wm, void *owner)
150 for(steve= wm->jobs.first; steve; steve= steve->next)
151 if(steve->owner==owner)
157 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
159 /* pending job? just free */
160 if(steve->customdata)
161 steve->free(steve->customdata);
163 steve->customdata= customdata;
167 /* signal job to end */
172 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
174 steve->timestep = timestep;
176 steve->endnote = endnote;
179 void WM_jobs_callbacks(wmJob *steve,
180 void (*startjob)(void *, short *, short *),
181 void (*initjob)(void *),
182 void (*update)(void *))
184 steve->startjob= startjob;
185 steve->initjob= initjob;
186 steve->update= update;
189 static void *do_job_thread(void *job_v)
193 steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update);
199 /* dont allow same startjob to be executed twice */
200 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
205 for(steve= wm->jobs.first; steve; steve= steve->next) {
206 if(steve==test || !steve->running) continue;
207 if(steve->startjob!=test->startjob && !(test->flag & WM_JOB_EXCL_RENDER)) continue;
208 if((test->flag & WM_JOB_EXCL_RENDER) && !(steve->flag & WM_JOB_EXCL_RENDER)) continue;
212 /* if this job has higher priority, stop others */
213 if(test->flag & WM_JOB_PRIORITY)
217 /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
218 test->suspended= suspend;
221 /* if job running, the same owner gave it a new job */
222 /* if different owner starts existing startjob, it suspends itself */
223 void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
226 /* signal job to end and restart */
230 if(steve->customdata && steve->startjob) {
232 wm_jobs_test_suspend_stop(wm, steve);
234 if(steve->suspended==0) {
235 /* copy to ensure proper free in end */
236 steve->run_customdata= steve->customdata;
237 steve->run_free= steve->free;
239 steve->customdata= NULL;
243 steve->initjob(steve->run_customdata);
248 BLI_init_threads(&steve->threads, do_job_thread, 1);
249 BLI_insert_thread(&steve->threads, steve);
251 // printf("job started\n");
254 /* restarted job has timer already */
256 steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
258 else printf("job fails, not initialized\n");
262 /* stop job, free data completely */
263 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
266 /* signal job to end */
268 BLI_end_threads(&steve->threads);
272 WM_event_remove_timer(wm, steve->win, steve->wt);
273 if(steve->customdata)
274 steve->free(steve->customdata);
275 if(steve->run_customdata)
276 steve->run_free(steve->run_customdata);
279 BLI_remlink(&wm->jobs, steve);
284 void WM_jobs_stop_all(wmWindowManager *wm)
288 while((steve= wm->jobs.first))
289 wm_jobs_kill_job(wm, steve);
293 /* signal job(s) from this owner to stop, timer is required to get handled */
294 void WM_jobs_stop(wmWindowManager *wm, void *owner)
298 for(steve= wm->jobs.first; steve; steve= steve->next)
299 if(steve->owner==owner)
304 /* actually terminate thread and job timer */
305 void WM_jobs_kill(wmWindowManager *wm, void *owner)
309 for(steve= wm->jobs.first; steve; steve= steve->next)
310 if(steve->owner==owner)
314 wm_jobs_kill_job(wm, steve);
318 /* kill job entirely, also removes timer itself */
319 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
323 for(steve= wm->jobs.first; steve; steve= steve->next) {
325 wm_jobs_kill_job(wm, steve);
331 /* hardcoded to event TIMERJOBS */
332 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
334 wmJob *steve= wm->jobs.first, *stevenext;
336 for(; steve; steve= stevenext) {
337 stevenext= steve->next;
341 /* running threads */
342 if(steve->threads.first) {
344 /* always call note and update when ready */
345 if(steve->do_update || steve->ready) {
347 steve->update(steve->run_customdata);
349 WM_event_add_notifier(C, steve->note, NULL);
355 steve->run_free(steve->run_customdata);
356 steve->run_customdata= NULL;
357 steve->run_free= NULL;
359 // if(steve->stop) printf("job stopped\n");
360 // else printf("job finished\n");
363 BLI_end_threads(&steve->threads);
366 WM_event_add_notifier(C, steve->endnote, NULL);
368 /* new job added for steve? */
369 if(steve->customdata) {
370 WM_jobs_start(wm, steve);
373 WM_event_remove_timer(wm, steve->win, steve->wt);
377 BLI_remlink(&wm->jobs, steve);
382 else if(steve->suspended) {
383 WM_jobs_start(wm, steve);