|
Blender
V2.59
|
00001 /* 00002 * $Id: wm_jobs.c 36276 2011-04-21 15:53:30Z campbellbarton $ 00003 * 00004 * ***** BEGIN GPL LICENSE BLOCK ***** 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU General Public License 00008 * as published by the Free Software Foundation; either version 2 00009 * of the License, or (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software Foundation, 00018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 * 00020 * The Original Code is Copyright (C) 2009 Blender Foundation. 00021 * All rights reserved. 00022 * 00023 * 00024 * Contributor(s): Blender Foundation 00025 * 00026 * ***** END GPL LICENSE BLOCK ***** 00027 */ 00028 00034 #include <string.h> 00035 00036 #include "DNA_windowmanager_types.h" 00037 00038 #include "MEM_guardedalloc.h" 00039 00040 #include "BLI_blenlib.h" 00041 #include "BLI_threads.h" 00042 00043 #include "BKE_blender.h" 00044 #include "BKE_context.h" 00045 #include "BKE_idprop.h" 00046 #include "BKE_global.h" 00047 #include "BKE_library.h" 00048 #include "BKE_main.h" 00049 #include "BKE_report.h" 00050 00051 #include "WM_api.h" 00052 #include "WM_types.h" 00053 #include "wm_window.h" 00054 #include "wm_event_system.h" 00055 #include "wm_event_types.h" 00056 #include "wm.h" 00057 00058 00059 00060 /* ********************** Threaded Jobs Manager ****************************** */ 00061 00062 /* 00063 Add new job 00064 - register in WM 00065 - configure callbacks 00066 00067 Start or re-run job 00068 - if job running 00069 - signal job to end 00070 - add timer notifier to verify when it has ended, to start it 00071 - else 00072 - start job 00073 - add timer notifier to handle progress 00074 00075 Stop job 00076 - signal job to end 00077 on end, job will tag itself as sleeping 00078 00079 Remove job 00080 - signal job to end 00081 on end, job will remove itself 00082 00083 When job is done: 00084 - it puts timer to sleep (or removes?) 00085 00086 */ 00087 00088 struct wmJob { 00089 struct wmJob *next, *prev; 00090 00091 /* job originating from, keep track of this when deleting windows */ 00092 wmWindow *win; 00093 00094 /* should store entire own context, for start, update, free */ 00095 void *customdata; 00096 /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */ 00097 void (*initjob)(void *); 00098 /* this runs inside thread, and does full job */ 00099 void (*startjob)(void *, short *stop, short *do_update, float *progress); 00100 /* update gets called if thread defines so, and max once per timerstep */ 00101 /* it runs outside thread, blocking blender, no drawing! */ 00102 void (*update)(void *); 00103 /* free entire customdata, doesn't run in thread */ 00104 void (*free)(void *); 00105 /* gets called when job is stopped, not in thread */ 00106 void (*endjob)(void *); 00107 00108 /* running jobs each have own timer */ 00109 double timestep; 00110 wmTimer *wt; 00111 /* the notifier event timers should send */ 00112 unsigned int note, endnote; 00113 00114 00115 /* internal */ 00116 void *owner; 00117 int flag; 00118 short suspended, running, ready, do_update, stop; 00119 float progress; 00120 00121 /* for display in header, identification */ 00122 char name[128]; 00123 00124 /* once running, we store this separately */ 00125 void *run_customdata; 00126 void (*run_free)(void *); 00127 00128 /* we use BLI_threads api, but per job only 1 thread runs */ 00129 ListBase threads; 00130 00131 }; 00132 00133 /* finds: 00134 * 1st priority: job with same owner and name 00135 * 2nd priority: job with same owner 00136 */ 00137 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const char *name) 00138 { 00139 wmJob *steve, *found=NULL; 00140 00141 for(steve= wm->jobs.first; steve; steve= steve->next) 00142 if(steve->owner==owner) { 00143 found= steve; 00144 if (name && strcmp(steve->name, name)==0) 00145 return steve; 00146 } 00147 00148 return found; 00149 } 00150 00151 /* ******************* public API ***************** */ 00152 00153 /* returns current or adds new job, but doesnt run it */ 00154 /* every owner only gets a single job, adding a new one will stop running stop and 00155 when stopped it starts the new one */ 00156 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag) 00157 { 00158 wmJob *steve= wm_job_find(wm, owner, name); 00159 00160 if(steve==NULL) { 00161 steve= MEM_callocN(sizeof(wmJob), "new job"); 00162 00163 BLI_addtail(&wm->jobs, steve); 00164 steve->win= win; 00165 steve->owner= owner; 00166 steve->flag= flag; 00167 BLI_strncpy(steve->name, name, sizeof(steve->name)); 00168 } 00169 00170 return steve; 00171 } 00172 00173 /* returns true if job runs, for UI (progress) indicators */ 00174 int WM_jobs_test(wmWindowManager *wm, void *owner) 00175 { 00176 wmJob *steve; 00177 00178 for(steve= wm->jobs.first; steve; steve= steve->next) 00179 if(steve->owner==owner) 00180 if(steve->running) 00181 return 1; 00182 return 0; 00183 } 00184 00185 float WM_jobs_progress(wmWindowManager *wm, void *owner) 00186 { 00187 wmJob *steve= wm_job_find(wm, owner, NULL); 00188 00189 if (steve && steve->flag & WM_JOB_PROGRESS) 00190 return steve->progress; 00191 00192 return 0.0; 00193 } 00194 00195 char *WM_jobs_name(wmWindowManager *wm, void *owner) 00196 { 00197 wmJob *steve= wm_job_find(wm, owner, NULL); 00198 00199 if (steve) 00200 return steve->name; 00201 00202 return NULL; 00203 } 00204 00205 void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *)) 00206 { 00207 /* pending job? just free */ 00208 if(steve->customdata) 00209 steve->free(steve->customdata); 00210 00211 steve->customdata= customdata; 00212 steve->free= free; 00213 00214 if(steve->running) { 00215 /* signal job to end */ 00216 steve->stop= 1; 00217 } 00218 } 00219 00220 void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote) 00221 { 00222 steve->timestep = timestep; 00223 steve->note = note; 00224 steve->endnote = endnote; 00225 } 00226 00227 void WM_jobs_callbacks(wmJob *steve, 00228 void (*startjob)(void *, short *, short *, float *), 00229 void (*initjob)(void *), 00230 void (*update)(void *), 00231 void (*endjob)(void *)) 00232 { 00233 steve->startjob= startjob; 00234 steve->initjob= initjob; 00235 steve->update= update; 00236 steve->endjob= endjob; 00237 } 00238 00239 static void *do_job_thread(void *job_v) 00240 { 00241 wmJob *steve= job_v; 00242 00243 steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update, &steve->progress); 00244 steve->ready= 1; 00245 00246 return NULL; 00247 } 00248 00249 /* dont allow same startjob to be executed twice */ 00250 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test) 00251 { 00252 wmJob *steve; 00253 int suspend= 0; 00254 00255 /* job added with suspend flag, we wait 1 timer step before activating it */ 00256 if(test->flag & WM_JOB_SUSPEND) { 00257 suspend= 1; 00258 test->flag &= ~WM_JOB_SUSPEND; 00259 } 00260 else { 00261 /* check other jobs */ 00262 for(steve= wm->jobs.first; steve; steve= steve->next) { 00263 /* obvious case, no test needed */ 00264 if(steve==test || !steve->running) continue; 00265 00266 /* if new job is not render, then check for same startjob */ 00267 if(0==(test->flag & WM_JOB_EXCL_RENDER)) 00268 if(steve->startjob!=test->startjob) 00269 continue; 00270 00271 /* if new job is render, any render job should be stopped */ 00272 if(test->flag & WM_JOB_EXCL_RENDER) 00273 if(0==(steve->flag & WM_JOB_EXCL_RENDER)) 00274 continue; 00275 00276 suspend= 1; 00277 00278 /* if this job has higher priority, stop others */ 00279 if(test->flag & WM_JOB_PRIORITY) { 00280 steve->stop= 1; 00281 // printf("job stopped: %s\n", steve->name); 00282 } 00283 } 00284 } 00285 00286 /* possible suspend ourselfs, waiting for other jobs, or de-suspend */ 00287 test->suspended= suspend; 00288 // if(suspend) printf("job suspended: %s\n", test->name); 00289 } 00290 00291 /* if job running, the same owner gave it a new job */ 00292 /* if different owner starts existing startjob, it suspends itself */ 00293 void WM_jobs_start(wmWindowManager *wm, wmJob *steve) 00294 { 00295 if(steve->running) { 00296 /* signal job to end and restart */ 00297 steve->stop= 1; 00298 // printf("job started a running job, ending... %s\n", steve->name); 00299 } 00300 else { 00301 00302 if(steve->customdata && steve->startjob) { 00303 00304 wm_jobs_test_suspend_stop(wm, steve); 00305 00306 if(steve->suspended==0) { 00307 /* copy to ensure proper free in end */ 00308 steve->run_customdata= steve->customdata; 00309 steve->run_free= steve->free; 00310 steve->free= NULL; 00311 steve->customdata= NULL; 00312 steve->running= 1; 00313 00314 if(steve->initjob) 00315 steve->initjob(steve->run_customdata); 00316 00317 steve->stop= 0; 00318 steve->ready= 0; 00319 steve->progress= 0.0; 00320 00321 // printf("job started: %s\n", steve->name); 00322 00323 BLI_init_threads(&steve->threads, do_job_thread, 1); 00324 BLI_insert_thread(&steve->threads, steve); 00325 } 00326 00327 /* restarted job has timer already */ 00328 if(steve->wt==NULL) 00329 steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep); 00330 } 00331 else printf("job fails, not initialized\n"); 00332 } 00333 } 00334 00335 /* stop job, free data completely */ 00336 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve) 00337 { 00338 if(steve->running) { 00339 /* signal job to end */ 00340 steve->stop= 1; 00341 BLI_end_threads(&steve->threads); 00342 00343 if(steve->endjob) 00344 steve->endjob(steve->run_customdata); 00345 } 00346 00347 if(steve->wt) 00348 WM_event_remove_timer(wm, steve->win, steve->wt); 00349 if(steve->customdata) 00350 steve->free(steve->customdata); 00351 if(steve->run_customdata) 00352 steve->run_free(steve->run_customdata); 00353 00354 /* remove steve */ 00355 BLI_remlink(&wm->jobs, steve); 00356 MEM_freeN(steve); 00357 00358 } 00359 00360 void WM_jobs_stop_all(wmWindowManager *wm) 00361 { 00362 wmJob *steve; 00363 00364 while((steve= wm->jobs.first)) 00365 wm_jobs_kill_job(wm, steve); 00366 00367 } 00368 00369 /* signal job(s) from this owner or callback to stop, timer is required to get handled */ 00370 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob) 00371 { 00372 wmJob *steve; 00373 00374 for(steve= wm->jobs.first; steve; steve= steve->next) 00375 if(steve->owner==owner || steve->startjob==startjob) 00376 if(steve->running) 00377 steve->stop= 1; 00378 } 00379 00380 /* actually terminate thread and job timer */ 00381 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *)) 00382 { 00383 wmJob *steve; 00384 00385 steve= wm->jobs.first; 00386 while(steve) { 00387 if(steve->owner==owner || steve->startjob==startjob) { 00388 wmJob* bill = steve; 00389 steve= steve->next; 00390 wm_jobs_kill_job(wm, bill); 00391 } else { 00392 steve= steve->next; 00393 } 00394 } 00395 } 00396 00397 00398 /* kill job entirely, also removes timer itself */ 00399 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt) 00400 { 00401 wmJob *steve; 00402 00403 for(steve= wm->jobs.first; steve; steve= steve->next) { 00404 if(steve->wt==wt) { 00405 wm_jobs_kill_job(wm, steve); 00406 return; 00407 } 00408 } 00409 } 00410 00411 /* hardcoded to event TIMERJOBS */ 00412 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt) 00413 { 00414 wmJob *steve= wm->jobs.first, *stevenext; 00415 float total_progress= 0.f; 00416 float jobs_progress=0; 00417 00418 00419 for(; steve; steve= stevenext) { 00420 stevenext= steve->next; 00421 00422 if(steve->wt==wt) { 00423 00424 /* running threads */ 00425 if(steve->threads.first) { 00426 00427 /* always call note and update when ready */ 00428 if(steve->do_update || steve->ready) { 00429 if(steve->update) 00430 steve->update(steve->run_customdata); 00431 if(steve->note) 00432 WM_event_add_notifier(C, steve->note, NULL); 00433 00434 if (steve->flag & WM_JOB_PROGRESS) 00435 WM_event_add_notifier(C, NC_WM|ND_JOB, NULL); 00436 steve->do_update= 0; 00437 } 00438 00439 if(steve->ready) { 00440 if(steve->endjob) 00441 steve->endjob(steve->run_customdata); 00442 00443 /* free own data */ 00444 steve->run_free(steve->run_customdata); 00445 steve->run_customdata= NULL; 00446 steve->run_free= NULL; 00447 00448 // if(steve->stop) printf("job ready but stopped %s\n", steve->name); 00449 // else printf("job finished %s\n", steve->name); 00450 00451 steve->running= 0; 00452 BLI_end_threads(&steve->threads); 00453 00454 if(steve->endnote) 00455 WM_event_add_notifier(C, steve->endnote, NULL); 00456 00457 WM_event_add_notifier(C, NC_WM|ND_JOB, NULL); 00458 00459 /* new job added for steve? */ 00460 if(steve->customdata) { 00461 // printf("job restarted with new data %s\n", steve->name); 00462 WM_jobs_start(wm, steve); 00463 } 00464 else { 00465 WM_event_remove_timer(wm, steve->win, steve->wt); 00466 steve->wt= NULL; 00467 00468 /* remove steve */ 00469 BLI_remlink(&wm->jobs, steve); 00470 MEM_freeN(steve); 00471 } 00472 } else if (steve->flag & WM_JOB_PROGRESS) { 00473 /* accumulate global progress for running jobs */ 00474 jobs_progress++; 00475 total_progress += steve->progress; 00476 } 00477 } 00478 else if(steve->suspended) { 00479 WM_jobs_start(wm, steve); 00480 } 00481 } 00482 } 00483 00484 /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */ 00485 if(wm->winactive) { 00486 /* if there are running jobs, set the global progress indicator */ 00487 if (jobs_progress > 0) { 00488 float progress = total_progress / (float)jobs_progress; 00489 WM_progress_set(wm->winactive, progress); 00490 } else { 00491 WM_progress_clear(wm->winactive); 00492 } 00493 } 00494 } 00495