New Improvements to EFL Animation Management

EFL is undergoing a huge API refactor; the goal of this change is to simplify API usage while simultaneously making it more powerful. One major component that required improvement was animation management.


In the past, Ecore_Animator was the only object in charge of providing information about when to animate something; this had a few limitations and problems. The first problem is handling the lifecycle of the object: it must be manually destroyed once the animation is completed or when the object that uses it is destroyed. There was no way to link the object lifecycle with another object in any way or form. The animator object was built with the idea that there is one, and only one, global frame rate for the entire application. This is becoming less true today as moving to Wayland provides the ability to get the animation tick from the compositor on a per-window basis. Finally, the system also doesn’t have a way to reduce the frame rate of an application in a cooperative way because the applications decide the frame rate, not the system.

Introducing the New API

The solution to solve all of these problems is to introduce a new event on all graphical objects:

animator,tick: Efl.Event.Animator_Tick;

Where Efl.Event.Animator_Tick is

struct Efl.Event.Animator_Tick { 
   [[EFL event animator tick data structure]] 
   update_area: Eina.Rectangle; [[Area of the canvas that will be pushed to screen.]] 

With this, it’s possible to register an event to get the animator for an object. This solves the lifecycle issue because once the graphical object is destroyed, the event will not tick anymore. It allows the animator to tick at different speeds for different areas, but it’s still possible to ignore this information since the update_area is simply an optimization. Ticks are only provided when a window needs to be updated, and only for widgets that are in the area that needs updating. Additionally, framerate can be dynamically adapted to meet the energy consumption of the system (e.g. it would be possible to have a rule that switches to 30 fps when the device’s battery drops below 20% capacity).


The problem with this idea is that if events are propagated to every object, the cost of the function call to trigger the event will be massive, resulting in a huge performance penalty. This shouldn’t be the case because there shouldn’t be very many objects animating at the same time, rather only the objects that need the tick event; so, events should be triggered only on these objects. The trick is to check on every object when new callbacks are registered; if the callback is an animator, a tick event is created and the animation is triggered.

Eo events have callback,add and callback,del events triggered when a callback is registered or unregistered. Two callbacks are registered on the Evas canvas, and when a callback is added/removed, it checks to see if the new callback wants an animator tick and begins ticking if necessary.

The following code shows how to register the event. This uses an array of callbacks because it is more efficient in memory usage than per-event callback registration:

                          { EFL_EVENT_CALLBACK_ADD, _check_animator_event_catcher_add }, 
                          { EFL_EVENT_CALLBACK_DEL, _check_animator_event_catcher_del }); 
EAPI void 
_ecore_evas_register_animators(Ecore_Evas *ee) 
   efl_event_callback_array_add(ee->evas, animator_watch(), ee); 
EAPI void 
_ecore_evas_register(Ecore_Evas *ee) 
   ee->registered = 1; 
   ecore_evases = (Ecore_Evas *)eina_inlist_prepend 
     (EINA_INLIST_GET(ecore_evases), EINA_INLIST_GET(ee)); 
   if (_ecore_evas_render_sync) ecore_evas_first = EINA_TRUE; 

Here is the code that walks through each newly-added callback and checks to see if the event is an animator.

static void
_check_animator_event_catcher_add(void *data, const Efl_Event *event)
   const Efl_Callback_Array_Item *array = event->info;
   Ecore_Evas *ee = data;
   int i;

   for (i = 0; array[i].desc != NULL; i++)
          if (array[i].desc == EFL_EVENT_ANIMATOR_TICK)
             if (ee->anim_count++ > 0) return;
             INF("Setting up animator for %p from '%s' with title '%s'.", ee->evas, ee->driver, ee->prop.title);

             if (ee->engine.func->fn_animator_register && ee->engine.func->fn_animator_unregister)
                  // Backend support per window vsync
                  if (!_general_tick) _general_tick = ee;
                  // Backend doesn't support per window vsync, fallback to generic support
                  ee->anim = ecore_animator_add(_ecore_evas_animator_fallback, ee);

             // No need to walk more than once per array as you can not del
             // a partial array

You might notice that this API only sees an array of events, this is done on purpose to simplify the code of anything that’s watching callback registration. Eo automatically shows new registrations in an array, so that’s the only thing that matters in this structure. The same logic is applied to every Evas_Object to get the animator,tick event propagated to them. This piece of code is beginning to be used quite often in EFL now that we are moving to Eo events to reduce the overhead associated with triggering events that nothing is listening to.

The rest of the function handles reference counting and asks the backend to start ticking an animator if it has the ability, or fallback to use the old general animator.

With this, we have a framework for animation that will scale nicely and provide all the benefit we were looking for. This is now accessible in the latest version of EFL when the application turns on the following configurations.


Have fun experimenting with it!

Author: Cedric Bail

Cedric has been contributing for a long time to EFL. He is known as the borker due to his work on optimizing the core libraries and triggering side effect bugs which tend to take years to be discovered.