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