#ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "Ecore.h" #include "ecore_private.h" #define MY_CLASS EFL_LOOP_TIMER_CLASS #define MY_CLASS_NAME "Efl_Loop_Timer" #define ECORE_TIMER_CHECK(obj) if (!efl_isa((obj), MY_CLASS)) return #define EFL_LOOP_TIMER_DATA_GET(o, td) \ Efl_Loop_Timer_Data *td = efl_data_scope_safe_get(o, EFL_LOOP_TIMER_CLASS) #define EFL_LOOP_TIMER_DATA_GET_OR_RETURN(o, ptr, ...) \ EFL_LOOP_TIMER_DATA_GET(o, ptr); \ if (EINA_UNLIKELY(!ptr)) \ { \ ERR("No data for timer %p", o); \ return __VA_ARGS__; \ } struct _Ecore_Timer_Legacy { Ecore_Task_Cb func; const void *data; Eina_Bool inside_call : 1; Eina_Bool delete_me : 1; }; typedef struct _Ecore_Timer_Legacy Ecore_Timer_Legacy; struct _Efl_Loop_Timer_Data { EINA_INLIST; Eo *object; Eo *loop; Efl_Loop_Data *loop_data; Ecore_Timer_Legacy *legacy; double in; double at; double pending; int listening; Eina_Bool just_added : 1; Eina_Bool frozen : 1; Eina_Bool initialized : 1; Eina_Bool noparent : 1; Eina_Bool constructed : 1; Eina_Bool finalized : 1; }; static void _efl_loop_timer_util_delay(Efl_Loop_Timer_Data *timer, double add); static void _efl_loop_timer_util_instanciate(Efl_Loop_Data *loop, Efl_Loop_Timer_Data *timer); static void _efl_loop_timer_set(Efl_Loop_Timer_Data *timer, double at, double in); static double precision = 10.0 / 1000000.0; EAPI double ecore_timer_precision_get(void) { EINA_MAIN_LOOP_CHECK_RETURN_VAL(0.0); return precision; } EAPI void ecore_timer_precision_set(double value) { EINA_MAIN_LOOP_CHECK_RETURN; if (value < 0.0) value = 0.0; precision = value; } static void _check_timer_event_catcher_add(void *data, const Efl_Event *event) { Efl_Loop_Timer_Data *timer = data; const Efl_Callback_Array_Item_Full *array = event->info; int i; for (i = 0; array[i].desc != NULL; i++) { if (array[i].desc == EFL_LOOP_TIMER_EVENT_TIMER_TICK) { if (timer->listening++ > 0) return; if (timer->finalized) _efl_loop_timer_util_instanciate(timer->loop_data, timer); // No need to walk more than once per array as you can not del // a partial array return; } } } static void _check_timer_event_catcher_del(void *data, const Efl_Event *event) { Efl_Loop_Timer_Data *timer = data; const Efl_Callback_Array_Item_Full *array = event->info; int i; for (i = 0; array[i].desc != NULL; i++) { if (array[i].desc == EFL_LOOP_TIMER_EVENT_TIMER_TICK) { if ((--timer->listening) > 0) return; _efl_loop_timer_util_instanciate(timer->loop_data, timer); return; } } } EFL_CALLBACKS_ARRAY_DEFINE(timer_watch, { EFL_EVENT_CALLBACK_ADD, _check_timer_event_catcher_add }, { EFL_EVENT_CALLBACK_DEL, _check_timer_event_catcher_del }); EOLIAN static Eo * _efl_loop_timer_efl_object_constructor(Eo *obj, Efl_Loop_Timer_Data *timer) { efl_constructor(efl_super(obj, MY_CLASS)); efl_event_callback_array_add(obj, timer_watch(), timer); efl_wref_add(obj, &timer->object); timer->in = -1.0; timer->constructed = EINA_TRUE; return obj; } EOLIAN static Eo * _efl_loop_timer_efl_object_finalize(Eo *obj, Efl_Loop_Timer_Data *pd) { pd->loop = efl_provider_find(obj, EFL_LOOP_CLASS); pd->loop_data = efl_data_scope_get(pd->loop, EFL_LOOP_CLASS); if (pd->at < efl_loop_time_get(pd->loop)) pd->at = ecore_time_get() + pd->in; else pd->at += pd->in; if (pd->in < 0.0) { ERR("You need to specify the interval of a timer to create a valid timer."); return NULL; } pd->initialized = EINA_TRUE; pd->finalized = EINA_TRUE; _efl_loop_timer_set(pd, pd->at, pd->in); return efl_finalize(efl_super(obj, MY_CLASS)); } static void _ecore_timer_legacy_del(void *data, const Efl_Event *event EINA_UNUSED) { free(data); } static void _ecore_timer_legacy_tick(void *data, const Efl_Event *event) { Ecore_Timer_Legacy *legacy = data; Eina_Bool inside_call = legacy->inside_call; legacy->inside_call = 1; if (!_ecore_call_task_cb(legacy->func, (void *)legacy->data) || legacy->delete_me) { legacy->delete_me = EINA_TRUE; /* cannot destroy timer if recursing */ if (!inside_call) efl_del(event->object); } /* only unset flag if not currently recursing */ else if (!inside_call) legacy->inside_call = 0; } EFL_CALLBACKS_ARRAY_DEFINE(legacy_timer, { EFL_LOOP_TIMER_EVENT_TIMER_TICK, _ecore_timer_legacy_tick }, { EFL_EVENT_DEL, _ecore_timer_legacy_del }); EAPI Ecore_Timer * ecore_timer_add(double in, Ecore_Task_Cb func, const void *data) { Ecore_Timer_Legacy *legacy; Eo *timer; EINA_MAIN_LOOP_CHECK_RETURN_VAL(NULL); if (!func) { ERR("Callback function must be set up for the class."); return NULL; } legacy = calloc(1, sizeof (Ecore_Timer_Legacy)); if (!legacy) return NULL; legacy->func = func; legacy->data = data; timer = efl_add(MY_CLASS, efl_main_loop_get(), efl_event_callback_array_add(efl_added, legacy_timer(), legacy), efl_loop_timer_interval_set(efl_added, in)); EFL_LOOP_TIMER_DATA_GET_OR_RETURN(timer, td, NULL); td->legacy = legacy; return timer; } EAPI Ecore_Timer * ecore_timer_loop_add(double in, Ecore_Task_Cb func, const void *data) { Ecore_Timer_Legacy *legacy; Eo *timer; EINA_MAIN_LOOP_CHECK_RETURN_VAL(NULL); if (!func) { ERR("Callback function must be set up for the class."); return NULL; } legacy = calloc(1, sizeof (Ecore_Timer_Legacy)); if (!legacy) return NULL; legacy->func = func; legacy->data = data; timer = efl_add(MY_CLASS, efl_main_loop_get(), efl_event_callback_array_add(efl_added, legacy_timer(), legacy), efl_loop_timer_loop_reset(efl_added), efl_loop_timer_interval_set(efl_added, in)); EFL_LOOP_TIMER_DATA_GET_OR_RETURN(timer, td, NULL); td->legacy = legacy; return timer; } EAPI void * ecore_timer_del(Ecore_Timer *timer) { void *data; if (!timer) return NULL; EINA_MAIN_LOOP_CHECK_RETURN_VAL(NULL); EFL_LOOP_TIMER_DATA_GET(timer, td); // If legacy == NULL, this means double free or something if ((!td) || (!td->legacy)) { // Just in case it is an Eo timer, but not a legacy one. ERR("You are trying to destroy a timer which seems dead already."); efl_unref(timer); return NULL; } data = (void *)td->legacy->data; if (td->legacy->inside_call) td->legacy->delete_me = EINA_TRUE; else efl_del(timer); return data; } EOLIAN static void _efl_loop_timer_timer_interval_set(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer, double in) { if (in < 0.0) in = 0.0; timer->in = in; } EOLIAN static double _efl_loop_timer_timer_interval_get(const Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { return timer->in; } EOLIAN static void _efl_loop_timer_timer_delay(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *pd, double add) { _efl_loop_timer_util_delay(pd, add); } EOLIAN static void _efl_loop_timer_timer_reset(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now, add; if (!timer->loop_data) return; // Do not reset the current timer while inside the callback if (timer->loop_data->timer_current == timer) return; now = ecore_time_get(); if (!timer->initialized) { timer->at = now; return; } if (timer->frozen) add = timer->pending; else add = timer->at - now; _efl_loop_timer_util_delay(timer, timer->in - add); } EOLIAN static void _efl_loop_timer_timer_loop_reset(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now, add; if (!timer->loop_data) return; // Do not reset the current timer while inside the callback if (timer->loop_data->timer_current == timer) return; now = efl_loop_time_get(timer->loop); if (!timer->initialized) { timer->at = now; return; } if (timer->frozen) add = timer->pending; else add = timer->at - now; _efl_loop_timer_util_delay(timer, timer->in - add); } EOLIAN static double _efl_loop_timer_time_pending_get(const Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now, ret = 0.0; now = ecore_time_get(); if (timer->frozen) ret = timer->pending; else ret = timer->at - now; return ret; } EAPI void ecore_timer_freeze(Ecore_Timer *timer) { ECORE_TIMER_CHECK(timer); efl_event_freeze(timer); } EOLIAN static void _efl_loop_timer_efl_object_event_freeze(Eo *obj, Efl_Loop_Timer_Data *timer) { double now = 0.0; efl_event_freeze(efl_super(obj, MY_CLASS)); // Timer already frozen if (timer->frozen) return; /* not set if timer is not finalized */ if (timer->loop) now = efl_loop_time_get(timer->loop); /* only if timer interval has been set */ if (timer->initialized) timer->pending = timer->at - now; else timer->pending = 0.0; timer->at = 0.0; timer->frozen = 1; _efl_loop_timer_util_instanciate(timer->loop_data, timer); } EAPI Eina_Bool ecore_timer_freeze_get(Ecore_Timer *timer) { EINA_MAIN_LOOP_CHECK_RETURN_VAL(EINA_FALSE); return !!efl_event_freeze_count_get(timer); } EOLIAN static int _efl_loop_timer_efl_object_event_freeze_count_get(const Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { return timer->frozen; } EAPI void ecore_timer_thaw(Ecore_Timer *timer) { ECORE_TIMER_CHECK(timer); efl_event_thaw(timer); } EOLIAN static void _efl_loop_timer_efl_object_event_thaw(Eo *obj, Efl_Loop_Timer_Data *timer) { double now; efl_event_thaw(efl_super(obj, MY_CLASS)); if (!timer->frozen) return; // Timer not frozen timer->frozen = 0; if (timer->loop_data) { timer->loop_data->suspended = eina_inlist_remove (timer->loop_data->suspended, EINA_INLIST_GET(timer)); } now = ecore_time_get(); _efl_loop_timer_set(timer, timer->pending + now, timer->in); } EAPI char * ecore_timer_dump(void) { return NULL; } static void _efl_loop_timer_util_loop_clear(Efl_Loop_Timer_Data *pd) { Eina_Inlist *first; if (!pd->loop_data) return; // Check if we are the current timer, if so move along if (pd->loop_data->timer_current == pd) pd->loop_data->timer_current = (Efl_Loop_Timer_Data *) EINA_INLIST_GET(pd)->next; // Remove the timer from all possible pending list first = eina_inlist_first(EINA_INLIST_GET(pd)); if (first == pd->loop_data->timers) pd->loop_data->timers = eina_inlist_remove (pd->loop_data->timers, EINA_INLIST_GET(pd)); else if (first == pd->loop_data->suspended) pd->loop_data->suspended = eina_inlist_remove (pd->loop_data->suspended, EINA_INLIST_GET(pd)); } static void _efl_loop_timer_util_instanciate(Efl_Loop_Data *loop, Efl_Loop_Timer_Data *timer) { Efl_Loop_Timer_Data *t2; if (!loop) return; _efl_loop_timer_util_loop_clear(timer); // And start putting it back where it belong if ((!timer->listening) || (timer->frozen) || (timer->at <= 0.0) || (timer->in < 0.0)) { loop->suspended = eina_inlist_prepend(loop->suspended, EINA_INLIST_GET(timer)); return; } if (!timer->initialized) { ERR("Trying to instantiate an uninitialized timer is impossible."); return; } EINA_INLIST_REVERSE_FOREACH(loop->timers, t2) { if (timer->at > t2->at) { loop->timers = eina_inlist_append_relative(loop->timers, EINA_INLIST_GET(timer), EINA_INLIST_GET(t2)); return; } } loop->timers = eina_inlist_prepend(loop->timers, EINA_INLIST_GET(timer)); } static void _efl_loop_timer_util_delay(Efl_Loop_Timer_Data *timer, double add) { if (!timer->initialized) { ERR("Impossible to delay an uninitialized timer."); return; } if (timer->frozen) { timer->pending += add; return; } _efl_loop_timer_set(timer, timer->at + add, timer->in); } EOLIAN static void _efl_loop_timer_efl_object_parent_set(Eo *obj, Efl_Loop_Timer_Data *pd, Efl_Object *parent) { Eina_Inlist *first; efl_parent_set(efl_super(obj, EFL_LOOP_TIMER_CLASS), parent); if ((!pd->constructed) || (!pd->finalized)) return; // Remove the timer from all possible pending list first = eina_inlist_first(EINA_INLIST_GET(pd)); if (first == pd->loop_data->timers) { /* if this timer is currently being processed, update the pointer here so it is not lost */ if (pd == pd->loop_data->timer_current) pd->loop_data->timer_current = (Efl_Loop_Timer_Data*)EINA_INLIST_GET(pd)->next; pd->loop_data->timers = eina_inlist_remove(pd->loop_data->timers, EINA_INLIST_GET(pd)); } else if (first == pd->loop_data->suspended) pd->loop_data->suspended = eina_inlist_remove (pd->loop_data->suspended, EINA_INLIST_GET(pd)); if (efl_invalidated_get(obj)) return; pd->loop = efl_provider_find(obj, EFL_LOOP_CLASS); if (pd->loop) pd->loop_data = efl_data_scope_get(pd->loop, EFL_LOOP_CLASS); else pd->loop_data = NULL; if (efl_parent_get(obj) != parent) return; _efl_loop_timer_util_instanciate(pd->loop_data, pd); if (parent != NULL) pd->noparent = EINA_FALSE; else pd->noparent = EINA_TRUE; } EOLIAN static void _efl_loop_timer_efl_object_destructor(Eo *obj, Efl_Loop_Timer_Data *pd) { _efl_loop_timer_util_loop_clear(pd); efl_destructor(efl_super(obj, MY_CLASS)); } void _efl_loop_timer_enable_new(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd) { Efl_Loop_Timer_Data *timer; if (!pd->timers_added) return; pd->timers_added = 0; EINA_INLIST_FOREACH(pd->timers, timer) timer->just_added = 0; } int _efl_loop_timers_exists(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd) { return !!pd->timers; } static inline Ecore_Timer * _efl_loop_timer_first_get(Eo *ob EINA_UNUSED, Efl_Loop_Data *pd) { Efl_Loop_Timer_Data *timer; EINA_INLIST_FOREACH(pd->timers, timer) { if (!timer->just_added) return timer->object; } return NULL; } static inline Efl_Loop_Timer_Data * _efl_loop_timer_after_get(Efl_Loop_Timer_Data *base) { Efl_Loop_Timer_Data *timer; Efl_Loop_Timer_Data *valid_timer = base; double maxtime = base->at + precision; EINA_INLIST_FOREACH(EINA_INLIST_GET(base)->next, timer) { if (EINA_UNLIKELY(!timer->initialized)) continue; // This shouldn't happen if (timer->at >= maxtime) break; if (!timer->just_added) valid_timer = timer; } return valid_timer; } double _efl_loop_timer_next_get(Eo *obj, Efl_Loop_Data *pd) { Ecore_Timer *object; Efl_Loop_Timer_Data *first; double now; double in; object = _efl_loop_timer_first_get(obj, pd); if (!object) return -1; first = _efl_loop_timer_after_get(efl_data_scope_get(object, MY_CLASS)); now = efl_loop_time_get(obj); in = first->at - now; if (in < 0) in = 0; return in; } static inline void _efl_loop_timer_reschedule(Efl_Loop_Timer_Data *timer, double when) { if (timer->frozen || efl_invalidated_get(timer->object) || (timer->legacy && timer->legacy->delete_me)) return; if (timer->loop_data && (EINA_INLIST_GET(timer)->next || EINA_INLIST_GET(timer)->prev)) { if (timer->loop_data->timers && (!timer->noparent)) timer->loop_data->timers = eina_inlist_remove (timer->loop_data->timers, EINA_INLIST_GET(timer)); } /* if the timer would have gone off more than 15 seconds ago, * assume that the system hung and set the timer to go off * timer->in from now. this handles system hangs, suspends * and more, so ecore will only "replay" the timers while * the system is suspended if it is suspended for less than * 15 seconds (basically). this also handles if the process * is stopped in a debugger or IO and other handling gets * really slow within the main loop. */ if ((timer->at + timer->in) < (when - 15.0)) _efl_loop_timer_set(timer, when + timer->in, timer->in); else _efl_loop_timer_set(timer, timer->at + timer->in, timer->in); } void _efl_loop_timer_expired_timers_call(Eo *obj, Efl_Loop_Data *pd, double when) { // call the first expired timer until no expired timers exist while (_efl_loop_timer_expired_call(obj, pd, when)); } int _efl_loop_timer_expired_call(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd, double when) { if (!pd->timers) return 0; if (pd->last_check > when) { Efl_Loop_Timer_Data *timer; // User set time backwards EINA_INLIST_FOREACH(pd->timers, timer) timer->at -= (pd->last_check - when); } pd->last_check = when; if (!pd->timer_current) // regular main loop, start from head pd->timer_current = (Efl_Loop_Timer_Data *)pd->timers; else { // recursive main loop, continue from where we were Efl_Loop_Timer_Data *timer_old = pd->timer_current; pd->timer_current = (Efl_Loop_Timer_Data *) EINA_INLIST_GET(pd->timer_current)->next; _efl_loop_timer_reschedule(timer_old, when); } while (pd->timer_current) { Efl_Loop_Timer_Data *timer = pd->timer_current; if (timer->at > when) { pd->timer_current = NULL; // ended walk, next should restart. return 0; } if (timer->just_added) { pd->timer_current = (Efl_Loop_Timer_Data *) EINA_INLIST_GET(pd->timer_current)->next; continue; } efl_ref(timer->object); eina_evlog("+timer", timer, 0.0, NULL); /* this can remove timer from its inlist in the legacy codepath */ efl_event_callback_call(timer->object, EFL_LOOP_TIMER_EVENT_TIMER_TICK, NULL); eina_evlog("-timer", timer, 0.0, NULL); // may have changed in recursive main loops // this current timer can not die yet as we hold a reference on it /* this is tricky: the current timer cannot be deleted, but it CAN be removed from its inlist, * thus breaking timer processing */ if (pd->timer_current) { if (pd->timer_current == timer) pd->timer_current = (Efl_Loop_Timer_Data *)EINA_INLIST_GET(pd->timer_current)->next; /* assume this has otherwise been modified either due to recursive mainloop processing or * the timer being removed from its inlist and carefully updating pd->timer_current in the * process as only the most elite of engineers would think to do */ } _efl_loop_timer_reschedule(timer, when); efl_unref(timer->object); } return 0; } static void _efl_loop_timer_set(Efl_Loop_Timer_Data *timer, double at, double in) { if (!timer->loop_data) return; timer->loop_data->timers_added = 1; timer->in = in; timer->just_added = 1; timer->initialized = 1; if (!timer->frozen) { timer->at = at; timer->pending = 0.0; } _efl_loop_timer_util_instanciate(timer->loop_data, timer); } #include "efl_loop_timer.eo.c" #include "efl_loop_timer_eo.legacy.c"