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