/* * Copyright (C) 2012 Daniel Manjarres * Copyright (C) 2013-2015 Kim Woelders * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of the Software, its documentation and marketing & publicity * materials, and acknowledgment shall be given in the documentation, materials * and software packages that this Software was used. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include "E.h" #include "animation.h" #include "eobj.h" #include "timers.h" #include "util.h" #define ENABLE_DEBUG 1 #if ENABLE_DEBUG #define Dprintf(fmt...) if(EDebug(EDBUG_TYPE_ANIM))Eprintf(fmt) #define D2printf(fmt...) if(EDebug(EDBUG_TYPE_ANIM)>1)Eprintf(fmt) #define D3printf(fmt...) #define EOW(eo) (eo ? EobjGetXwin(eo) : NoXID) #else #define Dprintf(fmt...) #define D2printf(fmt...) #define D3printf(fmt...) #endif /* ENABLE_DEBUG */ #define FPS Mode.screen.fps static int timing_engine(void); /* * State */ static struct { Timer *timer; Idler *idler; unsigned int time_ms; /* Just use Mode.events.time_ms? */ unsigned int seqn; } Mode_anim = { NULL, NULL, 0, 0 }; static int _AnimatorsTimer(void *timer_call) { int frame_skip; int dt; /* Remember current event run sequence number */ Mode_anim.seqn = Mode.events.seqn; /* Remember current event time */ Mode_anim.time_ms = Mode.events.time_ms; frame_skip = timing_engine(); /* time = partial_frames * usecs_per_partial_frame */ if (frame_skip < 1000000) dt = (1000 * (frame_skip + 1)) / FPS; else dt = 1000000000; /* Some arbitrary high value */ if (timer_call) { TimerSetInterval(Mode_anim.timer, dt); } else { TIMER_DEL(Mode_anim.timer); TIMER_ADD(Mode_anim.timer, dt, _AnimatorsTimer, (void *)1L); } D2printf("%s (%s) frame_skip=%d dt=%d\n", __func__, timer_call ? "TIMER" : "IDLER", frame_skip, dt); return 1; } static void _AnimatorsIdler(void *data) { /* Don't run idler if we have just run timer */ if (Mode_anim.seqn == Mode.events.seqn) return; _AnimatorsTimer(data); } static void _AnimatorsInit(void) { TIMER_ADD(Mode_anim.timer, 100, _AnimatorsTimer, (void *)1L); Mode_anim.idler = IdlerAdd(_AnimatorsIdler, (void *)0L); } /* * The animation engine */ #define LATER(frame, than) ((int)(frame - than) > 0) #define LATER_EQ(frame, than) ((int)(frame - than) >= 0) struct _animator { struct _animator *next; AnimCbFunc *func; AnimDoneFunc *done; animation_category category; int duration; esound_e start_sound; esound_e end_sound; char initialized; char serialize; char cancelled; unsigned int start_frame; unsigned int end_frame; unsigned int next_frame; unsigned int last_tms; EObj *eo; }; static Animator *global_animators; /* This is the frame we THINk we are currently displaying. * The next frame to render is this + 1. */ static unsigned int current_frame_num = 1; /* This is the number of the next frame we need to render for a pending * animation */ static unsigned int skip_to_frame_num = 0; static char anim_recheck = 0; Animator * AnimatorAdd(EObj *eo, animation_category category, AnimCbFunc *func, int duration, int serialize, size_t data_size, void *data) { Animator *an, **insert; an = (Animator *) calloc(1, sizeof(Animator) + data_size); if (!an) return NULL; Dprintf("%s: %u/%u: %#x %p C%d\n", __func__, current_frame_num, skip_to_frame_num, EOW(eo), an, category); if (!Mode_anim.timer) _AnimatorsInit(); insert = eo ? &eo->animations : &global_animators; while (*insert) insert = &((*insert)->next); *insert = an; an->func = func; if (duration >= 0) { an->duration = (duration * FPS) / 1000; /* ms -> frames */ if (an->duration == 0) an->duration = 1; /* At least one frame */ } else an->duration = -1; /* Forever */ an->category = category; an->serialize = serialize; an->eo = eo; an->start_sound = an->end_sound = SOUND_NONE; if (data_size) memcpy(an + 1, data, data_size); anim_recheck = 1; return an; } void AnimatorSetSound(Animator *an, esound_e start_sound, esound_e end_sound) { if (!an) return; an->start_sound = start_sound; an->end_sound = end_sound; } void AnimatorSetDoneFunc(Animator *an, AnimDoneFunc *done) { an->done = done; } static void _AnimatorDel(Animator *an) { Dprintf("%s: %u/%u: %#x %p C%d\n", __func__, current_frame_num, skip_to_frame_num, EOW(an->eo), an, an->category); Efree(an); } void AnimatorsFree(EObj *eo) { Animator *an, *next; for (an = eo->animations; an; an = next) { next = an->next; _AnimatorDel(an); } } void * AnimatorGetData(Animator *an) { return (an) ? an + 1 : NULL; } /* Quarter period sinusoidal used in time limited animations */ #define M_PI_F ((float)(M_PI)) #define REMAINING(elapsed, duration) \ (int)(1024 * (1.f - cosf(((M_PI_F / 2 * (elapsed)) / (duration))))) static unsigned int _AnimatorsRun(Animator **head, unsigned int frame_num, unsigned int next_frame) { Animator *an, *next, **pprev; int res; int first; int remaining; int delta_t; for (first = 1, pprev = head, an = *head; an; an = next) { D3printf("%s: %#x %p\n", __func__, EOW(an->eo), an); next = an->next; if (an->cancelled) { res = ANIM_RET_CANCEL_ANIM; goto check_res; } if (!an->initialized) { /* Just added - calculate first/last frame */ /* NB! New animations start one frame into the future */ if (an->serialize) { /* Start when other non-forever animations have run */ if (!first) goto do_next; Dprintf("%s: %#x %p C%d: De-serialize\n", __func__, EOW(an->eo), an, an->category); } an->initialized = 1; an->start_frame = frame_num + 1; an->end_frame = an->start_frame + an->duration - 1; an->next_frame = an->start_frame; an->last_tms = Mode_anim.time_ms; } /* Don't serialize animations that follow an inf loop with the inf loop */ if (an->duration > 0) first = 0; if (an->category >= 0 && LATER(an->next_frame, frame_num)) goto check_next_frame; /*{ start of old _AnimatorRun() */ if (an->start_sound) { SoundPlay(an->start_sound); an->start_sound = SOUND_NONE; } delta_t = Mode_anim.time_ms - an->last_tms; an->last_tms = Mode_anim.time_ms; if (an->duration > 0) { remaining = 0; if (frame_num < an->end_frame) remaining = REMAINING(an->end_frame - frame_num, an->duration); } else { remaining = delta_t; } D2printf("%s: eo=%p an=%p cat=%d rem=%4d dur=%4d dt=%4d\n", __func__, an->eo, an, an->category, remaining, an->duration, delta_t); res = an->func(an->eo, remaining, an + 1); Dprintf("%s: res=%4d num=%u end=%u\n", __func__, res, frame_num, an->end_frame); if (res >= 0) { if (an->duration > 0 && remaining <= 0) { Dprintf("%s: %#x %p C%d: autocancelling\n", __func__, EOW(an->eo), an, an->category); res = ANIM_RET_CANCEL_ANIM; } } else { Dprintf("%s: %#x %p C%d: self cancelling\n", __func__, EOW(an->eo), an, an->category); } /*} end of old _AnimatorRun() */ check_res: if (res >= 0) { /* animator will run again */ an->next_frame = frame_num + 1 + res; } else { if (an->done) an->done(an->eo, an + 1); if (an->end_sound) SoundPlay(an->end_sound); _AnimatorDel(an); *pprev = next; continue; /* Skip pprev update */ } check_next_frame: if (an->category >= 0 && LATER(next_frame, an->next_frame)) next_frame = an->next_frame; do_next: pprev = &an->next; } return next_frame; } static unsigned int _AnimatorsRunAll(unsigned int frame_num) { EObj **lst; EObj *const *lst2; unsigned int next_frame; int num, i; lst2 = EobjListStackGet(&num); lst = EMALLOC(EObj *, num); memcpy(lst, lst2, num * sizeof(EObj *)); next_frame = frame_num + 0x7fffffff; D3printf("%s: %u/%u\n", __func__, current_frame_num, skip_to_frame_num); for (i = 0; i < num; i++) next_frame = _AnimatorsRun(&lst[i]->animations, frame_num, next_frame); Efree(lst); next_frame = _AnimatorsRun(&global_animators, frame_num, next_frame); return next_frame; } int AnimatorDel(EObj *eo, Animator *anx) { Animator *an; for (an = (eo) ? eo->animations : global_animators; an; an = an->next) { if (an != anx) continue; Dprintf("%s: %u/%u: %#x %p C%d\n", __func__, current_frame_num, skip_to_frame_num, EOW(an->eo), an, an->category); an->cancelled = 1; return 1; } return 0; } static unsigned int _FrameNum(void) { static char init = 0; static unsigned int tp = 0; static unsigned int fp = 0; unsigned int t, frame, dx; t = GetTimeMs(); if (!init) { init = 1; tp = t; } dx = t - tp; frame = fp + (dx * FPS) / 1000; if (dx > 1000000) { dx /= 1000; tp += dx * 1000; fp += dx * FPS; } return frame; } static unsigned int get_check_frame_count(unsigned int last_frame __UNUSED__, unsigned int skip_to_frame, unsigned int *good_framesp, unsigned int *last_skipped_framep, const char *msg) { unsigned int frame_num; frame_num = _FrameNum(); if (frame_num > skip_to_frame) { if (EDebug(1)) Eprintf("@%u %s missed %u frames after %u [%u] good frames\n", frame_num, msg, frame_num - skip_to_frame, *good_framesp, skip_to_frame - 1 - *last_skipped_framep); *good_framesp = 0; *last_skipped_framep = frame_num - 1; } return frame_num; } static int timing_engine(void) { static unsigned int last_frame_num; static unsigned int good_frames; static unsigned int last_skipped_frame; int frameskip; current_frame_num = get_check_frame_count(last_frame_num, skip_to_frame_num, &good_frames, &last_skipped_frame, "before render"); D2printf("%s: cur/last=%u/%u next=%u good=%u last-skipped=%u\n", __func__, current_frame_num, last_frame_num, skip_to_frame_num, good_frames, last_skipped_frame); if (current_frame_num == last_frame_num && !anim_recheck) goto done; last_frame_num = current_frame_num; anim_recheck = 0; skip_to_frame_num = _AnimatorsRunAll(current_frame_num); done: frameskip = skip_to_frame_num - current_frame_num - 1; return frameskip; }