/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2009 Blender Foundation. * All rights reserved. * * * Contributor(s): Blender Foundation * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/windowmanager/intern/wm_jobs.c * \ingroup wm */ #include #include "DNA_windowmanager_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_threads.h" #include "BKE_blender.h" #include "BKE_context.h" #include "BKE_idprop.h" #include "BKE_global.h" #include "BKE_library.h" #include "BKE_main.h" #include "BKE_report.h" #include "WM_api.h" #include "WM_types.h" #include "wm_window.h" #include "wm_event_system.h" #include "wm_event_types.h" #include "wm.h" /* ********************** Threaded Jobs Manager ****************************** */ /* Add new job - register in WM - configure callbacks Start or re-run job - if job running - signal job to end - add timer notifier to verify when it has ended, to start it - else - start job - add timer notifier to handle progress Stop job - signal job to end on end, job will tag itself as sleeping Remove job - signal job to end on end, job will remove itself When job is done: - it puts timer to sleep (or removes?) */ struct wmJob { struct wmJob *next, *prev; /* job originating from, keep track of this when deleting windows */ wmWindow *win; /* should store entire own context, for start, update, free */ void *customdata; /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */ void (*initjob)(void *); /* this runs inside thread, and does full job */ void (*startjob)(void *, short *stop, short *do_update, float *progress); /* update gets called if thread defines so, and max once per timerstep */ /* it runs outside thread, blocking blender, no drawing! */ void (*update)(void *); /* free entire customdata, doesn't run in thread */ void (*free)(void *); /* gets called when job is stopped, not in thread */ void (*endjob)(void *); /* running jobs each have own timer */ double timestep; wmTimer *wt; /* the notifier event timers should send */ unsigned int note, endnote; /* internal */ void *owner; int flag; short suspended, running, ready, do_update, stop; float progress; /* for display in header, identification */ char name[128]; /* once running, we store this separately */ void *run_customdata; void (*run_free)(void *); /* we use BLI_threads api, but per job only 1 thread runs */ ListBase threads; }; /* finds: * 1st priority: job with same owner and name * 2nd priority: job with same owner */ static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const char *name) { wmJob *steve, *found=NULL; for(steve= wm->jobs.first; steve; steve= steve->next) if(steve->owner==owner) { found= steve; if (name && strcmp(steve->name, name)==0) return steve; } return found; } /* ******************* public API ***************** */ /* returns current or adds new job, but doesnt run it */ /* every owner only gets a single job, adding a new one will stop running stop and when stopped it starts the new one */ wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag) { wmJob *steve= wm_job_find(wm, owner, name); if(steve==NULL) { steve= MEM_callocN(sizeof(wmJob), "new job"); BLI_addtail(&wm->jobs, steve); steve->win= win; steve->owner= owner; steve->flag= flag; BLI_strncpy(steve->name, name, sizeof(steve->name)); } return steve; } /* returns true if job runs, for UI (progress) indicators */ int WM_jobs_test(wmWindowManager *wm, void *owner) { wmJob *steve; for(steve= wm->jobs.first; steve; steve= steve->next) if(steve->owner==owner) if(steve->running) return 1; return 0; } float WM_jobs_progress(wmWindowManager *wm, void *owner) { wmJob *steve= wm_job_find(wm, owner, NULL); if (steve && steve->flag & WM_JOB_PROGRESS) return steve->progress; return 0.0; } char *WM_jobs_name(wmWindowManager *wm, void *owner) { wmJob *steve= wm_job_find(wm, owner, NULL); if (steve) return steve->name; return NULL; } int WM_jobs_is_running(wmJob *steve) { return steve->running; } void* WM_jobs_get_customdata(wmJob * steve) { if (!steve->customdata) { return steve->run_customdata; } else { return steve->customdata; } } void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *)) { /* pending job? just free */ if(steve->customdata) steve->free(steve->customdata); steve->customdata= customdata; steve->free= free; if(steve->running) { /* signal job to end */ steve->stop= 1; } } void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote) { steve->timestep = timestep; steve->note = note; steve->endnote = endnote; } void WM_jobs_callbacks(wmJob *steve, void (*startjob)(void *, short *, short *, float *), void (*initjob)(void *), void (*update)(void *), void (*endjob)(void *)) { steve->startjob= startjob; steve->initjob= initjob; steve->update= update; steve->endjob= endjob; } static void *do_job_thread(void *job_v) { wmJob *steve= job_v; steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update, &steve->progress); steve->ready= 1; return NULL; } /* dont allow same startjob to be executed twice */ static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test) { wmJob *steve; int suspend= 0; /* job added with suspend flag, we wait 1 timer step before activating it */ if(test->flag & WM_JOB_SUSPEND) { suspend= 1; test->flag &= ~WM_JOB_SUSPEND; } else { /* check other jobs */ for(steve= wm->jobs.first; steve; steve= steve->next) { /* obvious case, no test needed */ if(steve==test || !steve->running) continue; /* if new job is not render, then check for same startjob */ if(0==(test->flag & WM_JOB_EXCL_RENDER)) if(steve->startjob!=test->startjob) continue; /* if new job is render, any render job should be stopped */ if(test->flag & WM_JOB_EXCL_RENDER) if(0==(steve->flag & WM_JOB_EXCL_RENDER)) continue; suspend= 1; /* if this job has higher priority, stop others */ if(test->flag & WM_JOB_PRIORITY) { steve->stop= 1; // printf("job stopped: %s\n", steve->name); } } } /* possible suspend ourselfs, waiting for other jobs, or de-suspend */ test->suspended= suspend; // if(suspend) printf("job suspended: %s\n", test->name); } /* if job running, the same owner gave it a new job */ /* if different owner starts existing startjob, it suspends itself */ void WM_jobs_start(wmWindowManager *wm, wmJob *steve) { if(steve->running) { /* signal job to end and restart */ steve->stop= 1; // printf("job started a running job, ending... %s\n", steve->name); } else { if(steve->customdata && steve->startjob) { wm_jobs_test_suspend_stop(wm, steve); if(steve->suspended==0) { /* copy to ensure proper free in end */ steve->run_customdata= steve->customdata; steve->run_free= steve->free; steve->free= NULL; steve->customdata= NULL; steve->running= 1; if(steve->initjob) steve->initjob(steve->run_customdata); steve->stop= 0; steve->ready= 0; steve->progress= 0.0; // printf("job started: %s\n", steve->name); BLI_init_threads(&steve->threads, do_job_thread, 1); BLI_insert_thread(&steve->threads, steve); } /* restarted job has timer already */ if(steve->wt==NULL) steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep); } else printf("job fails, not initialized\n"); } } /* stop job, free data completely */ static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve) { if(steve->running) { /* signal job to end */ steve->stop= 1; BLI_end_threads(&steve->threads); if(steve->endjob) steve->endjob(steve->run_customdata); } if(steve->wt) WM_event_remove_timer(wm, steve->win, steve->wt); if(steve->customdata) steve->free(steve->customdata); if(steve->run_customdata) steve->run_free(steve->run_customdata); /* remove steve */ BLI_remlink(&wm->jobs, steve); MEM_freeN(steve); } void WM_jobs_stop_all(wmWindowManager *wm) { wmJob *steve; while((steve= wm->jobs.first)) wm_jobs_kill_job(wm, steve); } /* signal job(s) from this owner or callback to stop, timer is required to get handled */ void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob) { wmJob *steve; for(steve= wm->jobs.first; steve; steve= steve->next) if(steve->owner==owner || steve->startjob==startjob) if(steve->running) steve->stop= 1; } /* actually terminate thread and job timer */ void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *)) { wmJob *steve; steve= wm->jobs.first; while(steve) { if(steve->owner==owner || steve->startjob==startjob) { wmJob* bill = steve; steve= steve->next; wm_jobs_kill_job(wm, bill); } else { steve= steve->next; } } } /* kill job entirely, also removes timer itself */ void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt) { wmJob *steve; for(steve= wm->jobs.first; steve; steve= steve->next) { if(steve->wt==wt) { wm_jobs_kill_job(wm, steve); return; } } } /* hardcoded to event TIMERJOBS */ void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt) { wmJob *steve= wm->jobs.first, *stevenext; float total_progress= 0.f; float jobs_progress=0; for(; steve; steve= stevenext) { stevenext= steve->next; if(steve->wt==wt) { /* running threads */ if(steve->threads.first) { /* always call note and update when ready */ if(steve->do_update || steve->ready) { if(steve->update) steve->update(steve->run_customdata); if(steve->note) WM_event_add_notifier(C, steve->note, NULL); if (steve->flag & WM_JOB_PROGRESS) WM_event_add_notifier(C, NC_WM|ND_JOB, NULL); steve->do_update= 0; } if(steve->ready) { if(steve->endjob) steve->endjob(steve->run_customdata); /* free own data */ steve->run_free(steve->run_customdata); steve->run_customdata= NULL; steve->run_free= NULL; // if(steve->stop) printf("job ready but stopped %s\n", steve->name); // else printf("job finished %s\n", steve->name); steve->running= 0; BLI_end_threads(&steve->threads); if(steve->endnote) WM_event_add_notifier(C, steve->endnote, NULL); WM_event_add_notifier(C, NC_WM|ND_JOB, NULL); /* new job added for steve? */ if(steve->customdata) { // printf("job restarted with new data %s\n", steve->name); WM_jobs_start(wm, steve); } else { WM_event_remove_timer(wm, steve->win, steve->wt); steve->wt= NULL; /* remove steve */ BLI_remlink(&wm->jobs, steve); MEM_freeN(steve); } } else if (steve->flag & WM_JOB_PROGRESS) { /* accumulate global progress for running jobs */ jobs_progress++; total_progress += steve->progress; } } else if(steve->suspended) { WM_jobs_start(wm, steve); } } } /* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */ if(wm->winactive) { /* if there are running jobs, set the global progress indicator */ if (jobs_progress > 0) { float progress = total_progress / (float)jobs_progress; WM_progress_set(wm->winactive, progress); } else { WM_progress_clear(wm->winactive); } } }