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