525 lines
12 KiB
C
525 lines
12 KiB
C
/*
|
|
* ***** 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 <string.h>
|
|
|
|
#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);
|
|
}
|
|
}
|
|
else if(steve->threads.first && !steve->ready) {
|
|
if(steve->flag & WM_JOB_PROGRESS) {
|
|
/* accumulate global progress for running jobs */
|
|
jobs_progress++;
|
|
total_progress += steve->progress;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
int WM_jobs_has_running(wmWindowManager *wm)
|
|
{
|
|
wmJob *steve;
|
|
|
|
for(steve= wm->jobs.first; steve; steve= steve->next)
|
|
if(steve->running)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|