2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2009 Blender Foundation.
19 * All rights reserved.
22 * Contributor(s): Blender Foundation
24 * ***** END GPL LICENSE BLOCK *****
27 /** \file blender/windowmanager/intern/wm_jobs.c
34 #include "DNA_windowmanager_types.h"
36 #include "MEM_guardedalloc.h"
38 #include "BLI_blenlib.h"
39 #include "BLI_threads.h"
41 #include "BKE_blender.h"
42 #include "BKE_context.h"
43 #include "BKE_idprop.h"
44 #include "BKE_global.h"
45 #include "BKE_library.h"
47 #include "BKE_report.h"
51 #include "wm_window.h"
52 #include "wm_event_system.h"
53 #include "wm_event_types.h"
58 /* ********************** Threaded Jobs Manager ****************************** */
63 * - configure callbacks
68 * - add timer notifier to verify when it has ended, to start it
71 * - add timer notifier to handle progress
75 * on end, job will tag itself as sleeping
79 * on end, job will remove itself
82 * - it puts timer to sleep (or removes?)
87 struct wmJob *next, *prev;
89 /* job originating from, keep track of this when deleting windows */
92 /* should store entire own context, for start, update, free */
94 /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
95 void (*initjob)(void *);
96 /* this runs inside thread, and does full job */
97 void (*startjob)(void *, short *stop, short *do_update, float *progress);
98 /* update gets called if thread defines so, and max once per timerstep */
99 /* it runs outside thread, blocking blender, no drawing! */
100 void (*update)(void *);
101 /* free entire customdata, doesn't run in thread */
102 void (*free)(void *);
103 /* gets called when job is stopped, not in thread */
104 void (*endjob)(void *);
106 /* running jobs each have own timer */
109 /* the notifier event timers should send */
110 unsigned int note, endnote;
116 short suspended, running, ready, do_update, stop;
119 /* for display in header, identification */
122 /* once running, we store this separately */
123 void *run_customdata;
124 void (*run_free)(void *);
126 /* we use BLI_threads api, but per job only 1 thread runs */
132 * 1st priority: job with same owner and name
133 * 2nd priority: job with same owner
135 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const char *name)
137 wmJob *steve, *found = NULL;
139 for (steve = wm->jobs.first; steve; steve = steve->next)
140 if (steve->owner == owner) {
142 if (name && strcmp(steve->name, name) == 0)
149 /* ******************* public API ***************** */
151 /* returns current or adds new job, but doesnt run it */
152 /* every owner only gets a single job, adding a new one will stop running stop and
153 * when stopped it starts the new one */
154 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag)
156 wmJob *steve = wm_job_find(wm, owner, name);
159 steve = MEM_callocN(sizeof(wmJob), "new job");
161 BLI_addtail(&wm->jobs, steve);
163 steve->owner = owner;
165 BLI_strncpy(steve->name, name, sizeof(steve->name));
171 /* returns true if job runs, for UI (progress) indicators */
172 int WM_jobs_test(wmWindowManager *wm, void *owner)
176 for (steve = wm->jobs.first; steve; steve = steve->next)
177 if (steve->owner == owner)
183 float WM_jobs_progress(wmWindowManager *wm, void *owner)
185 wmJob *steve = wm_job_find(wm, owner, NULL);
187 if (steve && steve->flag & WM_JOB_PROGRESS)
188 return steve->progress;
193 char *WM_jobs_name(wmWindowManager *wm, void *owner)
195 wmJob *steve = wm_job_find(wm, owner, NULL);
203 int WM_jobs_is_running(wmJob *steve)
205 return steve->running;
208 void *WM_jobs_get_customdata(wmJob *steve)
210 if (!steve->customdata) {
211 return steve->run_customdata;
214 return steve->customdata;
218 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
220 /* pending job? just free */
221 if (steve->customdata)
222 steve->free(steve->customdata);
224 steve->customdata = customdata;
227 if (steve->running) {
228 /* signal job to end */
233 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
235 steve->timestep = timestep;
237 steve->endnote = endnote;
240 void WM_jobs_callbacks(wmJob *steve,
241 void (*startjob)(void *, short *, short *, float *),
242 void (*initjob)(void *),
243 void (*update)(void *),
244 void (*endjob)(void *))
246 steve->startjob = startjob;
247 steve->initjob = initjob;
248 steve->update = update;
249 steve->endjob = endjob;
252 static void *do_job_thread(void *job_v)
254 wmJob *steve = job_v;
256 steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update, &steve->progress);
262 /* don't allow same startjob to be executed twice */
263 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
268 /* job added with suspend flag, we wait 1 timer step before activating it */
269 if (test->flag & WM_JOB_SUSPEND) {
271 test->flag &= ~WM_JOB_SUSPEND;
274 /* check other jobs */
275 for (steve = wm->jobs.first; steve; steve = steve->next) {
276 /* obvious case, no test needed */
277 if (steve == test || !steve->running) continue;
279 /* if new job is not render, then check for same startjob */
280 if (0 == (test->flag & WM_JOB_EXCL_RENDER))
281 if (steve->startjob != test->startjob)
284 /* if new job is render, any render job should be stopped */
285 if (test->flag & WM_JOB_EXCL_RENDER)
286 if (0 == (steve->flag & WM_JOB_EXCL_RENDER))
291 /* if this job has higher priority, stop others */
292 if (test->flag & WM_JOB_PRIORITY) {
294 // printf("job stopped: %s\n", steve->name);
299 /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
300 test->suspended = suspend;
301 // if (suspend) printf("job suspended: %s\n", test->name);
304 /* if job running, the same owner gave it a new job */
305 /* if different owner starts existing startjob, it suspends itself */
306 void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
308 if (steve->running) {
309 /* signal job to end and restart */
311 // printf("job started a running job, ending... %s\n", steve->name);
315 if (steve->customdata && steve->startjob) {
317 wm_jobs_test_suspend_stop(wm, steve);
319 if (steve->suspended == 0) {
320 /* copy to ensure proper free in end */
321 steve->run_customdata = steve->customdata;
322 steve->run_free = steve->free;
324 steve->customdata = NULL;
328 steve->initjob(steve->run_customdata);
332 steve->progress = 0.0;
334 // printf("job started: %s\n", steve->name);
336 BLI_init_threads(&steve->threads, do_job_thread, 1);
337 BLI_insert_thread(&steve->threads, steve);
340 /* restarted job has timer already */
341 if (steve->wt == NULL)
342 steve->wt = WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
344 else printf("job fails, not initialized\n");
348 /* stop job, free data completely */
349 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
351 if (steve->running) {
352 /* signal job to end */
354 BLI_end_threads(&steve->threads);
357 steve->endjob(steve->run_customdata);
361 WM_event_remove_timer(wm, steve->win, steve->wt);
362 if (steve->customdata)
363 steve->free(steve->customdata);
364 if (steve->run_customdata)
365 steve->run_free(steve->run_customdata);
368 BLI_remlink(&wm->jobs, steve);
373 void WM_jobs_stop_all(wmWindowManager *wm)
377 while ((steve = wm->jobs.first))
378 wm_jobs_kill_job(wm, steve);
382 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
383 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
387 for (steve = wm->jobs.first; steve; steve = steve->next)
388 if (steve->owner == owner || steve->startjob == startjob)
393 /* actually terminate thread and job timer */
394 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
398 steve = wm->jobs.first;
400 if (steve->owner == owner || steve->startjob == startjob) {
403 wm_jobs_kill_job(wm, bill);
412 /* kill job entirely, also removes timer itself */
413 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
417 for (steve = wm->jobs.first; steve; steve = steve->next) {
418 if (steve->wt == wt) {
419 wm_jobs_kill_job(wm, steve);
425 /* hardcoded to event TIMERJOBS */
426 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
428 wmJob *steve = wm->jobs.first, *stevenext;
429 float total_progress = 0.f;
430 float jobs_progress = 0;
433 for (; steve; steve = stevenext) {
434 stevenext = steve->next;
436 if (steve->wt == wt) {
438 /* running threads */
439 if (steve->threads.first) {
441 /* always call note and update when ready */
442 if (steve->do_update || steve->ready) {
444 steve->update(steve->run_customdata);
446 WM_event_add_notifier(C, steve->note, NULL);
448 if (steve->flag & WM_JOB_PROGRESS)
449 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
450 steve->do_update = 0;
455 steve->endjob(steve->run_customdata);
458 steve->run_free(steve->run_customdata);
459 steve->run_customdata = NULL;
460 steve->run_free = NULL;
462 // if (steve->stop) printf("job ready but stopped %s\n", steve->name);
463 // else printf("job finished %s\n", steve->name);
466 BLI_end_threads(&steve->threads);
469 WM_event_add_notifier(C, steve->endnote, NULL);
471 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
473 /* new job added for steve? */
474 if (steve->customdata) {
475 // printf("job restarted with new data %s\n", steve->name);
476 WM_jobs_start(wm, steve);
479 WM_event_remove_timer(wm, steve->win, steve->wt);
483 BLI_remlink(&wm->jobs, steve);
487 else if (steve->flag & WM_JOB_PROGRESS) {
488 /* accumulate global progress for running jobs */
490 total_progress += steve->progress;
493 else if (steve->suspended) {
494 WM_jobs_start(wm, steve);
497 else if (steve->threads.first && !steve->ready) {
498 if (steve->flag & WM_JOB_PROGRESS) {
499 /* accumulate global progress for running jobs */
501 total_progress += steve->progress;
506 /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
508 /* if there are running jobs, set the global progress indicator */
509 if (jobs_progress > 0) {
510 float progress = total_progress / (float)jobs_progress;
511 WM_progress_set(wm->winactive, progress);
514 WM_progress_clear(wm->winactive);
519 int WM_jobs_has_running(wmWindowManager *wm)
523 for (steve = wm->jobs.first; steve; steve = steve->next)