Blender  V2.59
wm_jobs.c
Go to the documentation of this file.
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