2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / clock.cpp
blobaa5c2ccbb0209e3a5f9929dfd2fe2c7970cc502c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * clock.cpp: Clock management
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
14 #include <config.h>
16 #include <glib.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #ifdef HAVE_SYS_TIME_H
21 #include <sys/time.h>
22 #endif
23 #include <time.h>
25 #include "clock.h"
26 #include "timeline.h"
27 #include "timemanager.h"
29 #include "runtime.h"
30 #include "deployment.h"
32 #define CLOCK_DEBUG 0
34 RepeatBehavior RepeatBehavior::Forever (RepeatBehavior::FOREVER);
35 Duration Duration::Automatic (Duration::AUTOMATIC);
36 Duration Duration::Forever (Duration::FOREVER);
39 struct ClockNode {
40 ClockNode *next;
41 Clock *clock;
44 typedef void (*ClockFunc)(Clock*);
46 void
47 clock_list_foreach (GList *clock_list, ClockFunc func)
49 GList *list = NULL, *tail = NULL;
50 for (GList *l = clock_list; l; l = l->next) {
51 list = g_list_prepend (list, l->data);
52 if (!tail) tail = list;
53 ((Clock*)l->data)->ref();
55 for (GList *node = tail;node;node = node->prev) {
56 func ((Clock*)node->data);
57 ((Clock*)node->data)->unref ();
59 g_list_free (list);
62 static void
63 CallRaiseAccumulatedEvents (Clock *clock)
65 clock->RaiseAccumulatedEvents ();
68 static void
69 CallRaiseAccumulatedCompleted (Clock *clock)
71 clock->RaiseAccumulatedCompleted ();
75 Clock::Clock (Timeline *tl)
76 : natural_duration (Duration::Automatic)
78 SetObjectType (Type::CLOCK);
80 calculated_natural_duration = false;
81 state = Clock::Stopped;
82 progress = 0.0;
83 current_time = 0;
84 seek_time = 0;
85 time_manager = NULL;
86 parent_clock = NULL;
87 is_paused = false;
88 is_seeking = false;
89 begin_pause_time = 0;
90 accumulated_pause_time = 0;
91 has_started = false;
92 timeline = tl;
93 timeline->ref();
94 queued_events = 0;
95 root_parent_time = 0;
97 was_stopped = false;
98 begin_time = -1;
99 begin_on_tick = false;
100 emit_completed = false;
101 has_completed = false;
104 Clock::~Clock ()
106 timeline->unref();
109 void
110 Clock::Dispose ()
112 if (!IsDisposed ()) {
113 DependencyObject::Dispose ();
114 GetTimeline()->TeardownClock ();
118 Duration
119 Clock::GetNaturalDuration ()
121 if (!calculated_natural_duration) {
122 calculated_natural_duration = true;
124 Duration *duration = timeline->GetDuration ();
125 if (duration->HasTimeSpan ()) {
126 natural_duration = *duration;
128 else {
129 natural_duration = timeline->GetNaturalDuration (this);
133 return natural_duration;
136 bool
137 Clock::UpdateFromParentTime (TimeSpan parentTime)
139 #define CLAMP_NORMALIZED_TIME do { \
140 if (normalizedTime < 0.0) normalizedTime = 0.0; \
141 if (normalizedTime > 1.0) normalizedTime = 1.0; \
142 } while (0)
145 // The idea behind this method is that it is possible (and
146 // easier, and clearer) to apply a simple function to our
147 // parent clock's time to calculate our own time.
149 // We also calculate our progress (MS uses the term
150 // "normalized time"), a value in the range [0-1] at the same
151 // time.
153 // This clock's localTime runs from the range
154 // [0-natural_duration] for natural_durations with timespans,
155 // and [0-$forever] for Forever durations. Automatic
156 // durations are translated into timespans.
158 if (!GetHasStarted() && !GetWasStopped() && (GetBeginOnTick() || timeline->GetBeginTime () <= parentTime)) {
159 if (GetBeginOnTick())
160 BeginOnTick (false);
161 Begin (parentTime);
164 // root_parent_time is the time we were added to our parent clock.
165 // timeline->GetBeginTime() is expressed in the time-space of the parent clock.
167 // subtracting those two translates our start time to 0
169 // we then have to account for our accumulated pause time, and
170 // scale the whole thing by our speed ratio.
172 // the result is a timespan unaffected by repeatbehavior or
173 // autoreverse. it is simple the timespan our clock has been
174 // running.
175 TimeSpan localTime = (parentTime - root_parent_time - timeline->GetBeginTime() - accumulated_pause_time) * timeline->GetSpeedRatio();
177 bool seek_completed = false;
179 if (is_seeking) {
180 // if we're seeking, we need to arrange for the above
181 // localTime formula to keep time correctly. we clear
182 // accumulated_pause_time, and adjust root_parent_time
183 // such that we can re-evaluate localTime and have
184 // localTime = seek_time.
186 begin_pause_time = 0;
187 accumulated_pause_time = 0;
189 /* seek_time = localTime
191 seek_time = (parentTime - root_parent_time - timeline->BeginTime() - 0) * timeline->GetSpeedRatio ()
193 seek_time
194 ------------------------- = parentTime - root_parent_time - timeline->BeginTime();
195 timeline->GetSpeedRatio()
196 seek_time
197 root_parent_time = parentTime - timeline->BeginTime() - -------------------------
198 timeline->GetSpeedRatio()
200 root_parent_time = parentTime - (timeline->GetBeginTime () - seek_time) / timeline->GetSpeedRatio ();
201 localTime = (seek_time - timeline->GetBeginTime()) * timeline->GetSpeedRatio();
202 is_seeking = false;
203 seek_completed = true;
205 if (!GetHasStarted())
206 CalculateFillTime ();
208 else if (is_paused) {
209 // if we're paused and not seeking, we don't update
210 // anything.
211 return true;
214 // the clock doesn't update and we don't progress if the
215 // translated local time is before our begin time. Keep in
216 // mind that this can happen *after* a clock has started,
217 // since parentTime isn't strictly increasing. It can
218 // decrease and represent a time before our start time.
219 if (localTime < 0)
220 return true;
222 if (GetClockState () == Clock::Stopped) {
223 if (!seek_completed)
224 return false;
226 // even for stopped clocks we update their position if they're seeked.
229 double normalizedTime = 0.0;
232 // we only do the bulk of the work if the duration has a
233 // timespan. if we're automatic/forever, our normalizedTime
234 // stays pegged at 0.0, and our localTime progresses
235 // undisturbed. i.e. a RepeatBehavior="2x" means nothing if
236 // the Duration of the animation is forever.
237 if (GetNaturalDuration().HasTimeSpan()) {
238 TimeSpan natural_duration_timespan = GetNaturalDuration().GetTimeSpan();
240 if (natural_duration_timespan <= 0) {
241 // for clocks with instantaneous begin times/durations, expressable like so:
242 // <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" BeginTime="00:00:00" Duration="00:00:00" />
243 // we keep our localtime pegged at 0 (FIXME:
244 // without filling?) and our normalizedTime at
245 // 1. The latter makes sure that value is applied in full.
246 localTime = 0;
247 normalizedTime = 1.0;
248 if (GetClockState () == Clock::Active) {
249 FillOnNextTick ();
250 Completed ();
253 else if (natural_duration_timespan > 0) {
254 RepeatBehavior *repeat = timeline->GetRepeatBehavior ();
256 if (!repeat->IsForever() && localTime >= fillTime) {
257 // fillTime represents the local time
258 // at which the number of repeats
259 // (expressed either as a timespan or
260 // e.g. "2x") and autoreverses have
261 // completed. i.e. it's the
262 // $natural_duration * $repeat_count
263 // for repeat behaviors with counts,
264 // and $repeat_duration for repeat
265 // behaviors with timespans.
267 // if the timeline is auto-reversible,
268 // we always end at localTime = 0.
269 // Otherwise we know it's fillTime.
270 localTime = timeline->GetAutoReverse () ? 0 : fillTime;
271 normalizedTime = (double)localTime / natural_duration_timespan;
272 CLAMP_NORMALIZED_TIME;
273 if (GetClockState () == Clock::Active) {
274 FillOnNextTick ();
275 Completed ();
278 else {
279 if (GetClockState () != Clock::Active)
280 SetClockState (Clock::Active);
282 if (localTime > 0) {
283 double t = (double)localTime / natural_duration_timespan;
284 int ti = (int)t;
285 double fract = t - ti;
287 // This block of code is the first time where localTime is translated
288 // into per-repeat/per-autoreverse segments. We do it here because it
289 // allows us to use a cute hack for determining if we're ascending or
290 // descending.
292 // for instance:
294 // <storyboard duration="00:00:12">
295 // <doubleanimation begintime="00:00:00" repeatbehavior="2x" autoreverse="<below>" duration="00:00:03" />
296 // </storyboard>
298 // autoreverse = true autoreverse = false
299 // 0 / 3 = 0 = 0 0 / 3 = 0 = 0
300 // 1 / 3 = .333 > 0.333 1 / 3 = .333 > 0.333
301 // 2 / 3 = .666 > 0.666 2 / 3 = .666 > 0.666
302 // 3 / 3 = 1 = 1 3 / 3 = 1 = 1
303 // 4 / 3 = 1.33 < 0.666 4 / 3 = 1.33 > 0.333
304 // 5 / 3 = 1.66 < 0.333 5 / 3 = 1.66 > 0.666
305 // 6 / 3 = 2 = 0 6 / 3 = 2 = 1
306 // 7 / 3 = 2.33 > 0.333
307 // 8 / 3 = 2.66 > 0.666
308 // 9 / 3 = 3 = 1
309 // 10 / 3 = 3.33 < 0.666
310 // 11 / 3 = 3.66 < 0.333
311 // 12 / 3 = 4 = 0
314 // a little explanation: the $localtime / $natural_duration = $foo is done
315 // to factor out the repeat count. we know that the time within a given repeated
316 // run is just the fractional part of that (if the result has a fraction), or 0 or 1.
318 // the >,<,= column above represents whether we're increasing, decreasing, or at an
319 // end-point, respectively.
321 if (timeline->GetAutoReverse()) {
322 // left column above
323 if (ti & 1) {
324 // e.g:
325 // 3 / 3 = 1 = 1
326 // 4 / 3 = 1.33 < 0.666
327 // 5 / 3 = 1.66 < 0.333
329 // we know we're either at normalized time 1 (at our duration), or we're descending,
330 // based on if there's a fractional component.
331 if (ti == t) {
332 normalizedTime = 1.0;
333 localTime = natural_duration_timespan;
335 else {
336 /* we're descending */
337 normalizedTime = 1.0 - fract;
338 CLAMP_NORMALIZED_TIME;
339 localTime = normalizedTime * natural_duration_timespan;
342 else {
343 // e.g:
344 // 6 / 3 = 2 = 0
345 // 7 / 3 = 2.33 > 0.333
346 // 8 / 3 = 2.66 > 0.666
348 // we know we're either at normalizd time 0 (at our start time), or we're ascending,
349 // based on if there's a fractional component.
350 if (ti == t) {
351 normalizedTime = 0.0;
352 localTime = 0;
354 else {
355 /* we're ascending */
356 normalizedTime = fract;
357 CLAMP_NORMALIZED_TIME;
358 localTime = normalizedTime * natural_duration_timespan;
362 else {
363 // e.g.:
364 // 0 / 3 = 0 = 0
365 // 1 / 3 = .333 > 0.333
366 // 2 / 3 = .666 > 0.666
367 // 3 / 3 = 1 = 1
368 // 4 / 3 = 1.33 > 0.333
369 // 5 / 3 = 1.66 > 0.666
370 // 6 / 3 = 2 = 1
372 // we're always ascending here (since autoreverse is off), and we know we're > 0,
373 // so we don't need to concern ourselves with that case. At the integer points we're
374 // at our duration, and otherwise we're at the fractional value.
375 if (ti == t) {
376 normalizedTime = 1.0;
377 localTime = natural_duration_timespan;
379 else {
380 /* we're ascending */
381 normalizedTime = fract;
382 CLAMP_NORMALIZED_TIME;
383 localTime = normalizedTime * natural_duration_timespan;
391 SetCurrentTime (localTime);
392 progress = normalizedTime;
393 return true;
396 void
397 Clock::BeginOnTick (bool begin)
399 begin_on_tick = begin;
401 // tell the time manager that we need another tick
402 time_manager->NeedClockTick ();
405 void
406 Clock::SetClockState (ClockState state)
408 #if CLOCK_DEBUG
409 const char *states[] = { "Active", "Filling", "Stopped" };
410 printf ("Setting clock %p state to %s\n", this, states[state]);
411 #endif
412 this->state = state;
413 QueueEvent (CURRENT_STATE_INVALIDATED);
416 void
417 Clock::SetCurrentTime (TimeSpan ts)
419 current_time = ts;
420 QueueEvent (CURRENT_TIME_INVALIDATED);
423 void
424 Clock::QueueEvent (int event)
426 queued_events |= event;
429 void
430 Clock::RaiseAccumulatedEvents ()
432 if ((queued_events & CURRENT_TIME_INVALIDATED) != 0) {
433 Emit (CurrentTimeInvalidatedEvent);
436 if ((queued_events & CURRENT_STATE_INVALIDATED) != 0) {
437 if (state != Clock::Stopped)
438 has_started = true;
439 Emit (CurrentStateInvalidatedEvent);
442 queued_events = 0;
445 void
446 Clock::RaiseAccumulatedCompleted ()
448 if (emit_completed) {
449 emit_completed = false;
450 // printf ("clock %p (%s) completed\n", this, GetName ());
451 Emit (CompletedEvent);
452 has_completed = true;
456 void
457 Clock::SetRootParentTime (TimeSpan parentTime)
459 root_parent_time = parentTime;
462 void
463 Clock::CalculateFillTime ()
465 if (GetNaturalDuration().HasTimeSpan()) {
466 RepeatBehavior *repeat = timeline->GetRepeatBehavior ();
467 if (repeat->HasDuration ()) {
468 fillTime = (repeat->GetDuration() * timeline->GetSpeedRatio ());
470 else if (repeat->HasCount ()) {
471 fillTime = repeat->GetCount() * GetNaturalDuration().GetTimeSpan() * (timeline->GetAutoReverse() ? 2 : 1);
473 else {
474 fillTime = GetNaturalDuration().GetTimeSpan() * (timeline->GetAutoReverse() ? 2 : 1);
479 void
480 Clock::Begin (TimeSpan parentTime)
482 //printf ("clock %p (%s) begin\n", this, GetName ());
483 emit_completed = false;
484 has_completed = false;
485 was_stopped = false;
487 /* we're starting. initialize our current_time field */
488 SetCurrentTime ((parentTime - root_parent_time - timeline->GetBeginTime ()) * timeline->GetSpeedRatio());
490 Duration d = GetNaturalDuration ();
491 if (d.HasTimeSpan ()) {
492 if (d.GetTimeSpan() == 0) {
493 progress = 1.0;
495 else {
496 progress = (double)current_time / d.GetTimeSpan();
497 if (progress > 1.0)
498 progress = 1.0;
501 else
502 progress = 0.0;
504 CalculateFillTime ();
506 SetClockState (Clock::Active);
508 // force the time manager to tick the clock hierarchy to wake it up
509 time_manager->NeedClockTick ();
512 void
513 Clock::Pause ()
515 //printf ("clock %p (%s) paused\n", this, GetName ());
517 if (is_paused)
518 return;
520 is_paused = true;
521 begin_pause_time = GetCurrentTime();
524 void
525 Clock::Resume ()
527 if (!is_paused)
528 return;
530 is_paused = false;
532 accumulated_pause_time += GetCurrentTime() - begin_pause_time;
535 void
536 Clock::Seek (TimeSpan timespan)
538 //printf ("clock %p (%s) seek to timespan %" G_GINT64_FORMAT "\n", this, GetName (), timespan);
540 seek_time = timespan;
542 is_seeking = true;
545 void
546 Clock::SeekAlignedToLastTick (TimeSpan timespan)
548 Seek (timespan);
550 if (parent_clock)
551 UpdateFromParentTime (parent_clock->GetCurrentTime());
554 void
555 Clock::FillOnNextTick ()
557 switch (timeline->GetFillBehavior()) {
558 case FillBehaviorHoldEnd:
559 SetClockState (Clock::Filling);
560 break;
562 case FillBehaviorStop:
563 Stop ();
564 break;
568 void
569 Clock::SkipToFill ()
571 // FIXME: this only works on clocks that have a duration
572 #if CLOCK_DEBUG
573 printf ("filling clock %p after this tick\n", this);
574 #endif
576 Seek (fillTime);
578 FillOnNextTick ();
581 void
582 Clock::Stop ()
584 SetClockState (Clock::Stopped);
585 was_stopped = true;
588 void
589 Clock::Reset ()
591 // printf ("clock %p (%s) reset\n", this, GetName());
592 calculated_natural_duration = false;
593 state = Clock::Stopped;
594 progress = 0.0;
595 current_time = 0;
596 seek_time = 0;
597 begin_time = -1;
598 begin_on_tick = false;
599 is_paused = false;
600 is_seeking = false;
601 begin_pause_time =
602 accumulated_pause_time = 0;
603 has_started = false;
604 was_stopped = false;
605 emit_completed = false;
606 has_completed = false;
609 void
610 Clock::Completed ()
612 if (!has_completed)
613 emit_completed = true;
616 ClockGroup::ClockGroup (TimelineGroup *timeline, bool timemanager_clockgroup)
617 : Clock (timeline)
619 SetObjectType (Type::CLOCKGROUP);
621 this->timemanager_clockgroup = timemanager_clockgroup;
623 child_clocks = NULL;
626 void
627 ClockGroup::AddChild (Clock *clock)
629 clock->SetRootParentTime (GetCurrentTime ());
630 clock->SetParentClock (this);
632 child_clocks = g_list_append (child_clocks, clock);
633 clock->ref ();
634 clock->SetTimeManager (GetTimeManager());
637 void
638 ClockGroup::SetTimeManager (TimeManager *manager)
640 Clock::SetTimeManager (manager);
641 for (GList *l = child_clocks; l; l = l->next) {
642 Clock *c = (Clock*)l->data;
643 c->SetTimeManager (manager);
647 void
648 ClockGroup::RemoveChild (Clock *clock)
650 child_clocks = g_list_remove (child_clocks, clock);
651 clock->SetTimeManager (NULL);
652 clock->SetParentClock (NULL);
653 clock->unref ();
656 void
657 ClockGroup::Begin (TimeSpan parentTime)
659 Clock::Begin (parentTime);
661 for (GList *l = child_clocks; l; l = l->next) {
662 Clock *c = (Clock*)l->data;
663 c->ClearHasStarted ();
665 /* start any clocks that need starting immediately */
666 if (c->GetTimeline()->GetBeginTime() <= current_time) {
667 c->Begin (current_time);
672 void
673 ClockGroup::SkipToFill ()
675 if (child_clocks == NULL)
676 Stop ();
677 else
678 Clock::SkipToFill ();
681 void
682 ClockGroup::Stop ()
684 for (GList *l = child_clocks; l; l = l->next) {
685 Clock *clock = (Clock*)l->data;
687 if (timemanager_clockgroup || !clock->Is(Type::CLOCKGROUP)) {
688 // we don't stop sub-clock groups, since if we
689 // nest storyboards under one another they
690 // seem to behave independent of each other
691 // from this perspective.
692 ((Clock*)l->data)->Stop ();
696 Clock::Stop ();
699 bool
700 ClockGroup::UpdateFromParentTime (TimeSpan parentTime)
702 // we need to cache this here because
703 // Clock::UpdateFromParentTime will be updating it for the
704 // next tick.
705 ClockState current_state = GetClockState();
707 /* likewise, we need to cache this here since
708 Clock::UpdateFromParentTime will clear it */
709 bool seeking = GetIsSeeking();
711 bool rv = Clock::UpdateFromParentTime (parentTime);
713 // ClockGroups (which correspond to storyboards generally)
714 // only cause their children to update (and therefore for
715 // animations to hold/progress their value) if they are
716 // active, or if they've had Seek called on them.
718 // but it also happens when the clockgroup is in the Filling
719 // state. This means that you can attach a handler to
720 // Storyboard.Completed and inside the handler modify a
721 // property that an animation under that storyboard was
722 // targetting. and the new setting isnt clobbered by the
723 // animation like it would be if the storyboard was active.
725 bool update_child_clocks = (current_state == Clock::Active || seeking);
727 for (GList *l = child_clocks; l; l = l->next) {
728 Clock *clock = (Clock*)l->data;
729 if (update_child_clocks || clock->Is(Type::CLOCKGROUP))
730 rv = clock->UpdateFromParentTime (current_time) || rv;
733 return rv;
736 void
737 ClockGroup::RaiseAccumulatedEvents ()
739 /* raise our events */
740 Clock::RaiseAccumulatedEvents ();
742 /* now cause our children to raise theirs */
743 clock_list_foreach (child_clocks, CallRaiseAccumulatedEvents);
746 void
747 ClockGroup::RaiseAccumulatedCompleted ()
749 Clock::RaiseAccumulatedCompleted ();
750 clock_list_foreach (child_clocks, CallRaiseAccumulatedCompleted);
753 void
754 ClockGroup::Reset ()
756 Clock::Reset ();
758 for (GList *l = child_clocks; l; l = l->next)
759 ((Clock*)l->data)->Reset();
762 ClockGroup::~ClockGroup ()
766 void
767 ClockGroup::Dispose ()
769 GList *node = child_clocks;
770 while (node) {
771 Clock *clock = (Clock*)node->data;
772 node = node->next;
773 clock->Dispose ();
775 Clock::Dispose ();