f8258d18c1a73876493bdc1936298e7d26eaf754
[blender.git] / source / blender / windowmanager / intern / wm_jobs.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2009 Blender Foundation.
19  * All rights reserved.
20  *
21  * 
22  * Contributor(s): Blender Foundation
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/windowmanager/intern/wm_jobs.c
28  *  \ingroup wm
29  *
30  * Threaded job manager (high level job access).
31  */
32
33 #include <string.h>
34
35 #include "DNA_windowmanager_types.h"
36
37 #include "MEM_guardedalloc.h"
38
39 #include "BLI_blenlib.h"
40 #include "BLI_threads.h"
41 #include "BLI_utildefines.h"
42
43 #include "BKE_context.h"
44 #include "BKE_global.h"
45
46 #include "WM_api.h"
47 #include "WM_types.h"
48 #include "wm_event_types.h"
49 #include "wm.h"
50
51 #include "PIL_time.h"
52
53 /*
54  * Add new job
55  * - register in WM
56  * - configure callbacks
57  *
58  * Start or re-run job
59  * - if job running
60  *   - signal job to end
61  *   - add timer notifier to verify when it has ended, to start it
62  * - else
63  *   - start job
64  *   - add timer notifier to handle progress
65  *
66  * Stop job
67  *   - signal job to end
68  *  on end, job will tag itself as sleeping
69  *
70  * Remove job
71  * - signal job to end
72  *  on end, job will remove itself
73  *
74  * When job is done:
75  * - it puts timer to sleep (or removes?)
76  *
77  */
78
79 struct wmJob {
80         struct wmJob *next, *prev;
81         
82         /* job originating from, keep track of this when deleting windows */
83         wmWindow *win;
84         
85         /* should store entire own context, for start, update, free */
86         void *customdata;
87         /* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
88         void (*initjob)(void *);
89         /* this runs inside thread, and does full job */
90         void (*startjob)(void *, short *stop, short *do_update, float *progress);
91         /* update gets called if thread defines so, and max once per timerstep */
92         /* it runs outside thread, blocking blender, no drawing! */
93         void (*update)(void *);
94         /* free entire customdata, doesn't run in thread */
95         void (*free)(void *);
96         /* gets called when job is stopped, not in thread */
97         void (*endjob)(void *);
98         
99         /* running jobs each have own timer */
100         double timestep;
101         wmTimer *wt;
102         /* the notifier event timers should send */
103         unsigned int note, endnote;
104         
105         
106 /* internal */
107         void *owner;
108         int flag;
109         short suspended, running, ready, do_update, stop, job_type;
110         float progress;
111
112         /* for display in header, identification */
113         char name[128];
114         
115         /* once running, we store this separately */
116         void *run_customdata;
117         void (*run_free)(void *);
118         
119         /* we use BLI_threads api, but per job only 1 thread runs */
120         ListBase threads;
121
122         double start_time;
123
124         /* ticket mutex for main thread locking while some job accesses
125          * data that the main thread might modify at the same time */
126         TicketMutex *main_thread_mutex;
127         bool main_thread_mutex_ending;
128 };
129
130 /* Main thread locking */
131
132 void WM_job_main_thread_lock_acquire(wmJob *wm_job)
133 {
134         BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
135
136         /* if BLI_end_threads is being called to stop the job before it's finished,
137          * we no longer need to lock to get access to the main thread as it's
138          * waiting and can't respond */
139         if (wm_job->main_thread_mutex_ending)
140                 BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
141 }
142
143 void WM_job_main_thread_lock_release(wmJob *wm_job)
144 {
145         if (!wm_job->main_thread_mutex_ending)
146                 BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
147 }
148
149 static void wm_job_main_thread_yield(wmJob *wm_job, bool ending)
150 {
151         if (ending)
152                 wm_job->main_thread_mutex_ending = true;
153
154         /* unlock and lock the ticket mutex. because it's a fair mutex any job that
155          * is waiting to acquire the lock will get it first, before we can lock */
156         BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
157         BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
158 }
159
160 /**
161  * Finds if type or owner, compare for it, otherwise any matching job.
162  */
163 static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const int job_type)
164 {
165         wmJob *wm_job;
166         
167         if (owner && job_type) {
168                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
169                         if (wm_job->owner == owner && wm_job->job_type == job_type)
170                                 return wm_job;
171         }
172         else if (owner) {
173                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
174                         if (wm_job->owner == owner)
175                                 return wm_job;
176         }
177         else if (job_type) {
178                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next)
179                         if (wm_job->job_type == job_type)
180                                 return wm_job;
181         }
182         
183         return NULL;
184 }
185
186 /* ******************* public API ***************** */
187
188 /**
189  * \return current job or adds new job, but doesnt run it.
190  *
191  * \note every owner only gets a single job,
192  * adding a new one will stop running job and when stopped it starts the new one.
193  */
194 wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag, int job_type)
195 {
196         wmJob *wm_job = wm_job_find(wm, owner, job_type);
197         
198         if (wm_job == NULL) {
199                 wm_job = MEM_callocN(sizeof(wmJob), "new job");
200
201                 BLI_addtail(&wm->jobs, wm_job);
202                 wm_job->win = win;
203                 wm_job->owner = owner;
204                 wm_job->flag = flag;
205                 wm_job->job_type = job_type;
206                 BLI_strncpy(wm_job->name, name, sizeof(wm_job->name));
207
208                 wm_job->main_thread_mutex = BLI_ticket_mutex_alloc();
209                 BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
210         }
211         /* else: a running job, be careful */
212         
213         /* prevent creating a job with an invalid type */
214         BLI_assert(wm_job->job_type != WM_JOB_TYPE_ANY);
215
216         return wm_job;
217 }
218
219 /* returns true if job runs, for UI (progress) indicators */
220 bool WM_jobs_test(wmWindowManager *wm, void *owner, int job_type)
221 {
222         wmJob *wm_job;
223         
224         /* job can be running or about to run (suspended) */
225         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
226                 if (wm_job->owner == owner) {
227                         if (job_type == WM_JOB_TYPE_ANY || (wm_job->job_type == job_type)) {
228                                 if (wm_job->running || wm_job->suspended) {
229                                         return true;
230                                 }
231                         }
232                 }
233         }
234
235         return false;
236 }
237
238 float WM_jobs_progress(wmWindowManager *wm, void *owner)
239 {
240         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
241         
242         if (wm_job && wm_job->flag & WM_JOB_PROGRESS)
243                 return wm_job->progress;
244         
245         return 0.0;
246 }
247
248 char *WM_jobs_name(wmWindowManager *wm, void *owner)
249 {
250         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
251         
252         if (wm_job)
253                 return wm_job->name;
254         
255         return NULL;
256 }
257
258 void *WM_jobs_customdata(wmWindowManager *wm, void *owner)
259 {
260         wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
261         
262         if (wm_job)
263                 return WM_jobs_customdata_get(wm_job);
264         
265         return NULL;
266 }
267
268 void *WM_jobs_customdata_from_type(wmWindowManager *wm, int job_type)
269 {
270         wmJob *wm_job = wm_job_find(wm, NULL, job_type);
271         
272         if (wm_job)
273                 return WM_jobs_customdata_get(wm_job);
274         
275         return NULL;
276 }
277
278 bool WM_jobs_is_running(wmJob *wm_job)
279 {
280         return wm_job->running;
281 }
282
283 void *WM_jobs_customdata_get(wmJob *wm_job)
284 {
285         if (!wm_job->customdata) {
286                 return wm_job->run_customdata;
287         }
288         else {
289                 return wm_job->customdata;
290         }
291 }
292
293 void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void (*free)(void *))
294 {
295         /* pending job? just free */
296         if (wm_job->customdata)
297                 wm_job->free(wm_job->customdata);
298         
299         wm_job->customdata = customdata;
300         wm_job->free = free;
301
302         if (wm_job->running) {
303                 /* signal job to end */
304                 wm_job->stop = true;
305         }
306 }
307
308 void WM_jobs_timer(wmJob *wm_job, double timestep, unsigned int note, unsigned int endnote)
309 {
310         wm_job->timestep = timestep;
311         wm_job->note = note;
312         wm_job->endnote = endnote;
313 }
314
315 void WM_jobs_callbacks(wmJob *wm_job,
316                        void (*startjob)(void *, short *, short *, float *),
317                        void (*initjob)(void *),
318                        void (*update)(void *),
319                        void (*endjob)(void *))
320 {
321         wm_job->startjob = startjob;
322         wm_job->initjob = initjob;
323         wm_job->update = update;
324         wm_job->endjob = endjob;
325 }
326
327 static void *do_job_thread(void *job_v)
328 {
329         wmJob *wm_job = job_v;
330         
331         wm_job->startjob(wm_job->run_customdata, &wm_job->stop, &wm_job->do_update, &wm_job->progress);
332         wm_job->ready = true;
333         
334         return NULL;
335 }
336
337 /* don't allow same startjob to be executed twice */
338 static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
339 {
340         wmJob *wm_job;
341         bool suspend = false;
342         
343         /* job added with suspend flag, we wait 1 timer step before activating it */
344         if (test->flag & WM_JOB_SUSPEND) {
345                 suspend = true;
346                 test->flag &= ~WM_JOB_SUSPEND;
347         }
348         else {
349                 /* check other jobs */
350                 for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
351                         /* obvious case, no test needed */
352                         if (wm_job == test || !wm_job->running) {
353                                 continue;
354                         }
355                         
356                         /* if new job is not render, then check for same startjob */
357                         if (0 == (test->flag & WM_JOB_EXCL_RENDER))
358                                 if (wm_job->startjob != test->startjob)
359                                         continue;
360                         
361                         /* if new job is render, any render job should be stopped */
362                         if (test->flag & WM_JOB_EXCL_RENDER)
363                                 if (0 == (wm_job->flag & WM_JOB_EXCL_RENDER))
364                                         continue;
365
366                         suspend = true;
367
368                         /* if this job has higher priority, stop others */
369                         if (test->flag & WM_JOB_PRIORITY) {
370                                 wm_job->stop = true;
371                                 // printf("job stopped: %s\n", wm_job->name);
372                         }
373                 }
374         }
375         
376         /* possible suspend ourselfs, waiting for other jobs, or de-suspend */
377         test->suspended = suspend;
378         // if (suspend) printf("job suspended: %s\n", test->name);
379 }
380
381 /**
382  * if job running, the same owner gave it a new job.
383  * if different owner starts existing startjob, it suspends itself
384  */
385 void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
386 {
387         if (wm_job->running) {
388                 /* signal job to end and restart */
389                 wm_job->stop = true;
390                 // printf("job started a running job, ending... %s\n", wm_job->name);
391         }
392         else {
393                 
394                 if (wm_job->customdata && wm_job->startjob) {
395                         
396                         wm_jobs_test_suspend_stop(wm, wm_job);
397                         
398                         if (wm_job->suspended == false) {
399                                 /* copy to ensure proper free in end */
400                                 wm_job->run_customdata = wm_job->customdata;
401                                 wm_job->run_free = wm_job->free;
402                                 wm_job->free = NULL;
403                                 wm_job->customdata = NULL;
404                                 wm_job->running = true;
405                                 
406                                 if (wm_job->initjob)
407                                         wm_job->initjob(wm_job->run_customdata);
408                                 
409                                 wm_job->stop = false;
410                                 wm_job->ready = false;
411                                 wm_job->progress = 0.0;
412
413                                 // printf("job started: %s\n", wm_job->name);
414                                 
415                                 BLI_init_threads(&wm_job->threads, do_job_thread, 1);
416                                 BLI_insert_thread(&wm_job->threads, wm_job);
417                         }
418                         
419                         /* restarted job has timer already */
420                         if (wm_job->wt == NULL)
421                                 wm_job->wt = WM_event_add_timer(wm, wm_job->win, TIMERJOBS, wm_job->timestep);
422
423                         if (G.debug & G_DEBUG_JOBS)
424                                 wm_job->start_time = PIL_check_seconds_timer();
425                 }
426                 else {
427                         printf("job fails, not initialized\n");
428                 }
429         }
430 }
431
432 static void wm_job_free(wmWindowManager *wm, wmJob *wm_job)
433 {
434         BLI_remlink(&wm->jobs, wm_job);
435         BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
436         BLI_ticket_mutex_free(wm_job->main_thread_mutex);
437         MEM_freeN(wm_job);
438 }
439
440 /* stop job, end thread, free data completely */
441 static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job)
442 {
443         if (wm_job->running) {
444                 /* signal job to end */
445                 wm_job->stop = true;
446                 wm_job_main_thread_yield(wm_job, true);
447                 BLI_end_threads(&wm_job->threads);
448
449                 if (wm_job->endjob)
450                         wm_job->endjob(wm_job->run_customdata);
451         }
452         
453         if (wm_job->wt)
454                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
455         if (wm_job->customdata)
456                 wm_job->free(wm_job->customdata);
457         if (wm_job->run_customdata)
458                 wm_job->run_free(wm_job->run_customdata);
459         
460         /* remove wm_job */
461         wm_job_free(wm, wm_job);
462 }
463
464 /* wait until every job ended */
465 void WM_jobs_kill_all(wmWindowManager *wm)
466 {
467         wmJob *wm_job;
468         
469         while ((wm_job = wm->jobs.first))
470                 wm_jobs_kill_job(wm, wm_job);
471         
472 }
473
474 /* wait until every job ended, except for one owner (used in undo to keep screen job alive) */
475 void WM_jobs_kill_all_except(wmWindowManager *wm, void *owner)
476 {
477         wmJob *wm_job, *next_job;
478         
479         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
480                 next_job = wm_job->next;
481
482                 if (wm_job->owner != owner)
483                         wm_jobs_kill_job(wm, wm_job);
484         }
485 }
486
487
488 void WM_jobs_kill_type(struct wmWindowManager *wm, void *owner, int job_type)
489 {
490         wmJob *wm_job, *next_job;
491         
492         for (wm_job = wm->jobs.first; wm_job; wm_job = next_job) {
493                 next_job = wm_job->next;
494
495                 if (!owner || wm_job->owner == owner)
496                         if (wm_job->job_type == job_type)
497                                 wm_jobs_kill_job(wm, wm_job);
498         }
499 }
500
501 /* signal job(s) from this owner or callback to stop, timer is required to get handled */
502 void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
503 {
504         wmJob *wm_job;
505         
506         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
507                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
508                         if (wm_job->running) {
509                                 wm_job->stop = true;
510                         }
511                 }
512         }
513 }
514
515 /* actually terminate thread and job timer */
516 void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
517 {
518         wmJob *wm_job;
519         
520         wm_job = wm->jobs.first;
521         while (wm_job) {
522                 if (wm_job->owner == owner || wm_job->startjob == startjob) {
523                         wmJob *wm_job_kill = wm_job;
524                         wm_job = wm_job->next;
525                         wm_jobs_kill_job(wm, wm_job_kill);
526                 }
527                 else {
528                         wm_job = wm_job->next;
529                 }
530         }
531 }
532
533
534 /* kill job entirely, also removes timer itself */
535 void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
536 {
537         wmJob *wm_job;
538         
539         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
540                 if (wm_job->wt == wt) {
541                         wm_jobs_kill_job(wm, wm_job);
542                         return;
543                 }
544         }
545 }
546
547 /* hardcoded to event TIMERJOBS */
548 void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
549 {
550         wmJob *wm_job, *wm_jobnext;
551         float total_progress = 0.f;
552         float jobs_progress = 0;
553         
554         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_jobnext) {
555                 wm_jobnext = wm_job->next;
556                 
557                 if (wm_job->wt == wt) {
558                         
559                         /* running threads */
560                         if (wm_job->threads.first) {
561
562                                 /* let threads get temporary lock over main thread if needed */
563                                 wm_job_main_thread_yield(wm_job, false);
564                                 
565                                 /* always call note and update when ready */
566                                 if (wm_job->do_update || wm_job->ready) {
567                                         if (wm_job->update)
568                                                 wm_job->update(wm_job->run_customdata);
569                                         if (wm_job->note)
570                                                 WM_event_add_notifier(C, wm_job->note, NULL);
571
572                                         if (wm_job->flag & WM_JOB_PROGRESS)
573                                                 WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
574                                         wm_job->do_update = false;
575                                 }
576                                 
577                                 if (wm_job->ready) {
578                                         if (wm_job->endjob)
579                                                 wm_job->endjob(wm_job->run_customdata);
580
581                                         /* free own data */
582                                         wm_job->run_free(wm_job->run_customdata);
583                                         wm_job->run_customdata = NULL;
584                                         wm_job->run_free = NULL;
585                                         
586                                         // if (wm_job->stop) printf("job ready but stopped %s\n", wm_job->name);
587                                         // else printf("job finished %s\n", wm_job->name);
588
589                                         if (G.debug & G_DEBUG_JOBS) {
590                                                 printf("Job '%s' finished in %f seconds\n", wm_job->name,
591                                                        PIL_check_seconds_timer() - wm_job->start_time);
592                                         }
593
594                                         wm_job->running = false;
595                                         wm_job_main_thread_yield(wm_job, true);
596                                         BLI_end_threads(&wm_job->threads);
597                                         wm_job->main_thread_mutex_ending = false;
598                                         
599                                         if (wm_job->endnote)
600                                                 WM_event_add_notifier(C, wm_job->endnote, NULL);
601                                         
602                                         WM_event_add_notifier(C, NC_WM | ND_JOB, NULL);
603
604                                         /* new job added for wm_job? */
605                                         if (wm_job->customdata) {
606                                                 // printf("job restarted with new data %s\n", wm_job->name);
607                                                 WM_jobs_start(wm, wm_job);
608                                         }
609                                         else {
610                                                 WM_event_remove_timer(wm, wm_job->win, wm_job->wt);
611                                                 wm_job->wt = NULL;
612                                                 
613                                                 /* remove wm_job */
614                                                 wm_job_free(wm, wm_job);
615                                         }
616                                 }
617                                 else if (wm_job->flag & WM_JOB_PROGRESS) {
618                                         /* accumulate global progress for running jobs */
619                                         jobs_progress++;
620                                         total_progress += wm_job->progress;
621                                 }
622                         }
623                         else if (wm_job->suspended) {
624                                 WM_jobs_start(wm, wm_job);
625                         }
626                 }
627                 else if (wm_job->threads.first && !wm_job->ready) {
628                         if (wm_job->flag & WM_JOB_PROGRESS) {
629                                 /* accumulate global progress for running jobs */
630                                 jobs_progress++;
631                                 total_progress += wm_job->progress;
632                         }
633                 }
634         }
635         
636         
637         /* if there are running jobs, set the global progress indicator */
638         if (jobs_progress > 0) {
639                 wmWindow *win;
640                 float progress = total_progress / (float)jobs_progress;
641
642                 for (win = wm->windows.first; win; win = win->next)
643                         WM_progress_set(win, progress);
644         }
645         else {
646                 wmWindow *win;
647
648                 for (win = wm->windows.first; win; win = win->next)
649                         WM_progress_clear(win);
650         }
651         
652 }
653
654 bool WM_jobs_has_running(wmWindowManager *wm)
655 {
656         wmJob *wm_job;
657
658         for (wm_job = wm->jobs.first; wm_job; wm_job = wm_job->next) {
659                 if (wm_job->running) {
660                         return true;
661                 }
662         }
663
664         return false;
665 }