/** * $Id: * * ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * The Original Code is Copyright (C) 2009 Blender Foundation. * All rights reserved. * * * Contributor(s): Blender Foundation * * ***** END GPL LICENSE BLOCK ***** */ #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" #include "ED_screen.h" #include "RNA_types.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); /* 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 *); /* running jobs each have own timer */ double timestep; wmTimer *wt; /* the notifier event timers should send */ unsigned int note, endnote; /* internal */ void *owner; short suspended, running, ready, do_update, stop; /* 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; }; /* ******************* 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) { wmJob *steve; for(steve= wm->jobs.first; steve; steve= steve->next) if(steve->owner==owner) break; if(steve==NULL) { steve= MEM_callocN(sizeof(wmJob), "new job"); BLI_addtail(&wm->jobs, steve); steve->win= win; steve->owner= owner; } 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; } 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 *), void (*initjob)(void *), void (*update)(void *)) { steve->startjob= startjob; steve->initjob= initjob; steve->update= update; } static void *do_job_thread(void *job_v) { wmJob *steve= job_v; steve->stop= steve->ready= 0; steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update); steve->ready= 1; return NULL; } /* dont allow same startjob to be executed twice */ static void wm_jobs_test_suspend(wmWindowManager *wm, wmJob *test) { wmJob *steve; for(steve= wm->jobs.first; steve; steve= steve->next) if(steve!=test) if(steve->running) if(steve->startjob==test->startjob) break; if(steve) test->suspended= 1; else test->suspended= 0; } /* 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; } else { if(steve->customdata && steve->startjob) { wm_jobs_test_suspend(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); BLI_init_threads(&steve->threads, do_job_thread, 1); BLI_insert_thread(&steve->threads, steve); // printf("job started\n"); } /* restarted job has timer already */ if(steve->wt==NULL) steve->wt= WM_event_add_window_timer(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->wt) WM_event_remove_window_timer(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 to stop, timer is required to get handled */ void WM_jobs_stop(wmWindowManager *wm, void *owner) { wmJob *steve; for(steve= wm->jobs.first; steve; steve= steve->next) if(steve->owner==owner) if(steve->running) steve->stop= 1; } /* 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 */ static int wm_jobs_timer(bContext *C, wmOperator *op, wmEvent *evt) { wmWindowManager *wm= CTX_wm_manager(C); wmJob *steve= wm->jobs.first; for(; steve; steve= steve->next) { if(evt->customdata==steve->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); steve->do_update= 0; } if(steve->ready) { /* free own data */ steve->run_free(steve->run_customdata); steve->run_customdata= NULL; steve->run_free= NULL; // if(steve->stop) printf("job stopped\n"); // else printf("job finished\n"); steve->running= 0; BLI_end_threads(&steve->threads); if(steve->endnote) WM_event_add_notifier(C, steve->endnote, NULL); /* new job added for steve? */ if(steve->customdata) { WM_jobs_start(wm, steve); } else { WM_event_remove_window_timer(steve->win, steve->wt); steve->wt= NULL; /* remove steve */ BLI_remlink(&wm->jobs, steve); MEM_freeN(steve); } } } else if(steve->suspended) { WM_jobs_start(wm, steve); } return OPERATOR_FINISHED; } } return OPERATOR_PASS_THROUGH; } void WM_OT_jobs_timer(wmOperatorType *ot) { /* identifiers */ ot->name= "Jobs timer"; ot->idname= "WM_OT_jobs_timer"; ot->description="Jobs timer operator."; /* api callbacks */ ot->invoke= wm_jobs_timer; ot->poll= ED_operator_screenactive; }