#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 struct _Efl_Loop_Timer_Data { EINA_INLIST; Eo *object; double in; double at; double pending; int listening; unsigned char just_added : 1; unsigned char frozen : 1; unsigned char initialized : 1; unsigned char noparent : 1; }; typedef struct _Efl_Loop_Timer_Data Efl_Loop_Timer_Data; static void _efl_loop_timer_util_delay(Efl_Loop_Timer_Data *timer, double add); static void _efl_loop_timer_util_instanciate(Efl_Loop_Timer_Data *timer); static void _efl_loop_timer_set(Efl_Loop_Timer_Data *timer, double at, double in); static Eina_Inlist *timers = NULL; static Eina_Inlist *suspended = NULL; static Efl_Loop_Timer_Data *timer_current = NULL; static int timers_added = 0; static double last_check = 0.0; 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) { ERR("Precision %f less than zero, ignored", value); return ; } precision = value; } static void _check_timer_event_catcher_add(void *data, const Efl_Event *event) { const Efl_Callback_Array_Item *array = event->info; Efl_Loop_Timer_Data *timer = data; int i; for (i = 0; array[i].desc != NULL; i++) { if (array[i].desc == EFL_LOOP_TIMER_EVENT_TICK) { if (timer->listening++ > 0) return; _efl_loop_timer_util_instanciate(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) { const Efl_Callback_Array_Item *array = event->info; Efl_Loop_Timer_Data *timer = data; int i; for (i = 0; array[i].desc != NULL; i++) { if (array[i].desc == EFL_LOOP_TIMER_EVENT_TICK) { if ((--timer->listening) > 0) return; _efl_loop_timer_util_instanciate(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->initialized = EINA_FALSE; timer->in = -1.0; return obj; } EOLIAN static Eo * _efl_loop_timer_efl_object_finalize(Eo *obj, Efl_Loop_Timer_Data *pd) { if (pd->at < ecore_loop_time_get()) { pd->at = ecore_time_get() + pd->in; } else { pd->at += pd->in; } if (pd->in < 0) { ERR("You need to specify the interval of a timer to create a valid timer."); return NULL; } pd->initialized = EINA_TRUE; _efl_loop_timer_set(pd, pd->at, pd->in); return efl_finalize(efl_super(obj, MY_CLASS)); } typedef struct _Ecore_Timer_Legacy Ecore_Timer_Legacy; struct _Ecore_Timer_Legacy { Ecore_Task_Cb func; const void *data; Eina_Bool inside_call : 1; Eina_Bool delete_me : 1; }; static void _ecore_timer_legacy_del(void *data, const Efl_Event *event EINA_UNUSED) { Ecore_Timer_Legacy *legacy = data; free(legacy); } static void _ecore_timer_legacy_tick(void *data, const Efl_Event *event) { Ecore_Timer_Legacy *legacy = data; legacy->inside_call = 1; if (!_ecore_call_task_cb(legacy->func, (void*)legacy->data) || legacy->delete_me) efl_del(event->object); else legacy->inside_call = 0; } EFL_CALLBACKS_ARRAY_DEFINE(legacy_timer, { EFL_LOOP_TIMER_EVENT_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); legacy = calloc(1, sizeof (Ecore_Timer_Legacy)); if (!legacy) return NULL; legacy->func = func; legacy->data = data; timer = efl_add(MY_CLASS, ecore_main_loop_get(), efl_event_callback_array_add(efl_added, legacy_timer(), legacy), efl_key_data_set(efl_added, "_legacy", legacy), efl_loop_timer_interval_set(efl_added, in)); 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); legacy = calloc(1, sizeof (Ecore_Timer_Legacy)); if (!legacy) return NULL; legacy->func = func; legacy->data = data; timer = efl_add(MY_CLASS, ecore_main_loop_get(), efl_event_callback_array_add(efl_added, legacy_timer(), legacy), efl_key_data_set(efl_added, "_legacy", legacy), efl_loop_timer_loop_reset(efl_added), efl_loop_timer_interval_set(efl_added, in)); return timer; } EAPI void * ecore_timer_del(Ecore_Timer *timer) { Ecore_Timer_Legacy *legacy; void *data; if (!timer) return NULL; EINA_MAIN_LOOP_CHECK_RETURN_VAL(NULL); legacy = efl_key_data_get(timer, "_legacy"); // If legacy == NULL, this means double free or something if (legacy == NULL) { // 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*) legacy->data; if (legacy->inside_call) legacy->delete_me = EINA_TRUE; else efl_del(timer); return data; } EOLIAN static void _efl_loop_timer_interval_set(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer, double in) { EINA_MAIN_LOOP_CHECK_RETURN; if (in < 0.0) in = 0.0; timer->in = in; } EOLIAN static double _efl_loop_timer_interval_get(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double ret = -1.0; EINA_MAIN_LOOP_CHECK_RETURN_VAL(ret); ret = timer->in; return ret; } EOLIAN static void _efl_loop_timer_delay(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *pd, double add) { EINA_MAIN_LOOP_CHECK_RETURN; _efl_loop_timer_util_delay(pd, add); } EOLIAN static void _efl_loop_timer_reset(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now, add; EINA_MAIN_LOOP_CHECK_RETURN; // Do not reset the current timer while inside the callback if (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_loop_reset(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now, add; EINA_MAIN_LOOP_CHECK_RETURN; // Do not reset the current timer while inside the callback if (timer_current == timer) return ; now = ecore_loop_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 double _efl_loop_timer_pending_get(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now; double ret = 0.0; EINA_MAIN_LOOP_CHECK_RETURN_VAL(ret); 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) { EINA_MAIN_LOOP_CHECK_RETURN; ECORE_TIMER_CHECK(timer); efl_event_freeze(timer); } EOLIAN static void _efl_loop_timer_efl_object_event_freeze(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { double now; EINA_MAIN_LOOP_CHECK_RETURN; efl_event_freeze(efl_super(obj, MY_CLASS)); /* Timer already frozen */ if (timer->frozen) return; if (EINA_UNLIKELY(!timer->initialized)) { ERR("Attempt freezing an non initialized timer."); return; } now = ecore_loop_time_get(); timer->pending = timer->at - now; timer->at = 0.0; timer->frozen = 1; _efl_loop_timer_util_instanciate(timer); } EAPI Eina_Bool ecore_timer_freeze_get(Ecore_Timer *timer) { int r = 0; EINA_MAIN_LOOP_CHECK_RETURN_VAL(EINA_FALSE); r = efl_event_freeze_count_get(timer); return !!r; } EOLIAN static int _efl_loop_timer_efl_object_event_freeze_count_get(Eo *obj EINA_UNUSED, Efl_Loop_Timer_Data *timer) { EINA_MAIN_LOOP_CHECK_RETURN_VAL(0); return timer->frozen; } EAPI void ecore_timer_thaw(Ecore_Timer *timer) { EINA_MAIN_LOOP_CHECK_RETURN; 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; EINA_MAIN_LOOP_CHECK_RETURN; efl_event_thaw(efl_super(obj, MY_CLASS)); /* Timer not frozen */ if (!timer->frozen) return ; suspended = eina_inlist_remove(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_instanciate(Efl_Loop_Timer_Data *timer) { Efl_Loop_Timer_Data *t2; Eina_Inlist *first; // Remove the timer from all possible pending list first = eina_inlist_first(EINA_INLIST_GET(timer)); if (first == timers) timers = eina_inlist_remove(timers, EINA_INLIST_GET(timer)); else if (first == suspended) suspended = eina_inlist_remove(suspended, EINA_INLIST_GET(timer)); // And start putting it back where it belong if (!timer->listening || timer->frozen || timer->at <= 0.0 || timer->in < 0.0) { suspended = eina_inlist_prepend(suspended, EINA_INLIST_GET(timer)); return ; } if (!timer->initialized) { ERR("Trying to instantiate an uninitialized timer is impossible."); return ; } EINA_INLIST_REVERSE_FOREACH(timers, t2) { if (timer->at > t2->at) { timers = eina_inlist_append_relative(timers, EINA_INLIST_GET(timer), EINA_INLIST_GET(t2)); return; } } timers = eina_inlist_prepend(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 EINA_UNUSED, Efl_Loop_Timer_Data *pd, Efl_Object *parent) { Eina_Inlist *first; first = eina_inlist_first(EINA_INLIST_GET(pd)); if (first == timers) timers = eina_inlist_remove(timers, EINA_INLIST_GET(pd)); else if (first == suspended) suspended = eina_inlist_remove(suspended, EINA_INLIST_GET(pd)); efl_parent_set(efl_super(obj, EFL_LOOP_USER_CLASS), parent); if (efl_parent_get(obj) != parent) return ; if (parent != NULL) { _efl_loop_timer_util_instanciate(pd); 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) { Eina_Inlist *first; // Check if we are the current timer, if so move along if (timer_current == pd) 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 == timers) timers = eina_inlist_remove(timers, EINA_INLIST_GET(pd)); else if (first == suspended) suspended = eina_inlist_remove(suspended, EINA_INLIST_GET(pd)); efl_destructor(efl_super(obj, MY_CLASS)); } void _efl_loop_timer_shutdown(void) { timer_current = NULL; } void _efl_loop_timer_enable_new(void) { Efl_Loop_Timer_Data *timer; if (!timers_added) return; timers_added = 0; EINA_INLIST_FOREACH(timers, timer) timer->just_added = 0; } int _efl_loop_timers_exists(void) { return !!timers; } static inline Ecore_Timer * _efl_loop_timer_first_get(void) { Efl_Loop_Timer_Data *timer; EINA_INLIST_FOREACH(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(void) { Ecore_Timer *object; Efl_Loop_Timer_Data *first; double now; double in; object = _efl_loop_timer_first_get(); if (!object) return -1; first = _efl_loop_timer_after_get(efl_data_scope_get(object, MY_CLASS)); now = ecore_loop_time_get(); 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) return; if (timers && !timer->noparent) timers = eina_inlist_remove(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(double when) { /* call the first expired timer until no expired timers exist */ while (_efl_loop_timer_expired_call(when)) ; } int _efl_loop_timer_expired_call(double when) { if (!timers) return 0; if (last_check > when) { Efl_Loop_Timer_Data *timer; /* User set time backwards */ EINA_INLIST_FOREACH(timers, timer) timer->at -= (last_check - when); } last_check = when; if (!timer_current) { /* regular main loop, start from head */ timer_current = (Efl_Loop_Timer_Data *)timers; } else { /* recursive main loop, continue from where we were */ Efl_Loop_Timer_Data *timer_old = timer_current; timer_current = (Efl_Loop_Timer_Data *)EINA_INLIST_GET(timer_current)->next; _efl_loop_timer_reschedule(timer_old, when); } while (timer_current) { Efl_Loop_Timer_Data *timer = timer_current; if (timer->at > when) { timer_current = NULL; /* ended walk, next should restart. */ return 0; } if (timer->just_added) { timer_current = (Efl_Loop_Timer_Data *)EINA_INLIST_GET(timer_current)->next; continue; } efl_ref(timer->object); eina_evlog("+timer", timer, 0.0, NULL); efl_event_callback_call(timer->object, EFL_LOOP_TIMER_EVENT_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 */ if (timer_current) timer_current = (Efl_Loop_Timer_Data *)EINA_INLIST_GET(timer_current)->next; _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) { timers_added = 1; timer->at = at; timer->in = in; timer->just_added = 1; timer->initialized = 1; timer->frozen = 0; timer->pending = 0.0; _efl_loop_timer_util_instanciate(timer); } #include "efl_loop_timer.eo.c"