Blender V2.61 - r43446

wm_jobs.c

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