Main Loop Programming Guide

The EFL is event-driven. This means that execution usually takes place within an internal EFL Main Loop. The application is notified through function callbacks of virtually any event happening on the computer. This is typically more efficient than polling for events, whereby the application has to repeatedly ask if a certain event has occurred. When nothing is happening (no events are pending) the main loop enters the idle state during which the CPU consumes very little power.

EFL manages timers, file descriptors, user interface events amongst other things and even provides a simple mechanism for applications to perform conventional data polling if required.

Prerequisites

The Application Main Loop

For convenience, when your application starts, EFL creates one Main Loop for you, called the Application Main Loop. You can use it as the parent for any object you create that requires a main loop (Like Promises and Futures, for example).

In the Hello World tutorial you learned that all EFL applications start with the efl_main() method. You can retrieve the Application Main Loop from the Efl_Event * parameter like this:

EAPI_MAIN void
efl_main(void *data, const Efl_Event *ev)
{
   Efl_Loop *loop = ev->object;
}

Timers

Timers allow events to be triggered periodically after the given time has elapsed. After an event callback has been registered with the timer, it will be called at regular intervals.

You can find usage examples in the EFL repository: reference/c/core/src/core_idler.c and reference/c/core/src/core_poll.c.

Creating and Destroying Timers

Timers are Eo objects. You can create and destroy them with efl_add() as for all other Eo objects (see Introduction to Eo). Their class is EFL_LOOP_TIMER_CLASS:

timer_object = efl_add(EFL_LOOP_TIMER_CLASS, ...);

Timers do not need to have a parent. However it is convenient to create them under the application Main Loop, so the parent can manage destroying the timer.

If you want to manually destroy a timer use the regular efl_unref() or efl_del().

The Timer Callback

Register the callback using efl_event_callback_add() and the EFL_LOOP_TIMER_EVENT_TIMER_TICK event as explained in the Events Programming Guide.

efl_event_callback_add(timer_object, EFL_LOOP_TIMER_EVENT_TIMER_TICK, _timer_cb, NULL);

The callback has the usual event handler signature:

static void
_timer_cb(void *data, const Efl_Event *event)
{
   Efl_Loop_Timer *timer = event->object;
 
   [...]
}

Notice how the timer object is recovered from the event structure.

Configuring a Timer

The interval property controls the amount of time between callback triggers in seconds:

efl_loop_timer_interval_set(timer_object, seconds);
efl_loop_timer_interval_get(timer_object);

The time left before the next trigger of the timer can be retrieved through the pending read-only property:

efl_loop_timer_pending_get(timer_object);

The current interval can be reset with:

efl_loop_timer_reset(timer_object);

The current interval can also be extended, effectively delaying all future triggers of the timer by a given number of seconds:

efl_loop_timer_delay(timer_object, seconds);

Pausing a Timer

Pause a timer with:

efl_event_freeze(timer_object);

Resume from the previous time index:

efl_event_thaw(timer_object);

File Descriptor Monitors

EFL can monitor the system's file descriptor activity through Efl.Loop.Fd objects and trigger relevant events.

You can find usage examples in the EFL examples repository: reference/c/core/src/core_loop.c

Creating and Destroying FD Monitors

FD monitors are Eo objects that wrap system file descriptors, and are instantiated and destroyed with efl_add() as for all other Eo objects (see Introduction to Eo). Their class is EFL_LOOP_FD_CLASS,:

fd_object = efl_add(EFL_LOOP_FD_CLASS, ...);

To destroy an FD monitor use the regular efl_unref() or efl_del().

FD Monitor callbacks

Register to receive events on the FD monitor object with efl_event_callback_add():

efl_event_callback_add(fd_object, EFL_LOOP_FD_EVENT_READ, _read_fd_cb, NULL);

The available events include:

  • EFL_LOOP_FD_EVENT_READ: The file descriptor is ready to be read.

  • EFL_LOOP_FD_EVENT_WRITE: The file descriptor is ready to be written to.

  • EFL_LOOP_FD_EVENT_ERROR: An error has occurred on the file descriptor. You can retrieve more information from the callback using the event->info field:

    void
    _fd_error(void *data, const Efl_Event *event)
    {
     const Eina_Error *perr = event->info;
     
     fprintf(stderr, "  %s error: #%d '%s'\n",
             efl_name_get(event->object), *perr, eina_error_msg_get(*perr));

Accessing the File Descriptor

Set and get the internal file descriptor wrapped by an FD monitor object with the fd property:

efl_loop_fd_get(fd_object);
efl_loop_fd_set(fd_object, fd);

When the file descriptor corresponds to a file instead of a network socket, for instance, use fd_file instead. Otherwise you may face errors when using Windows:

efl_loop_fd_file_get(fd_object);
efl_loop_fd_file_set(fd_object, fd);

Idlers

When there are no events to process the EFL main loop enters the Idle state, consuming very little CPU power. You can receive a notification when the Idle state starts or stops.

EFL defines three different events you can use to be notified of the above conditions:

  • EFL_LOOP_EVENT_IDLE_ENTER: Main loop enters the idle state.

  • EFL_LOOP_EVENT_IDLE_EXIT: Main loop exits the idle state.

  • EFL_LOOP_EVENT_IDLE: Main loop is in the idle state, meaning that it has nothing else to do. Be warned that this callback will be invoked frequently consuming a significant amount of CPU resources. This also defeats the point of an event-driven application: heavy calculations should be performed on a separate thread or they will block the main loop. This can lead to an unresponsive user interface and other issues.

Register a callback to be notified of these events using efl_event_callback_add(), as outlined in the Events Programming Guide.

You can also view the example in the EFL examples repository: reference/c/core/src/core_idler.c.

Polling

In the rare case where EFL does not provide the event you want, you can resort to conventional application-driven polling. EFL can still manage the scheduling of the periodic polling, calling you back when it is time to perform the poll.

You can choose from among the predefined polling priorities depending on which event you use to register your callback:

  • EFL_LOOP_EVENT_POLL_HIGH: For events that may happen multiple times per second.

  • EFL_LOOP_EVENT_POLL_MEDIUM: For events that may happen multiple times per minute.

  • EFL_LOOP_EVENT_POLL_LOW: For events that may happen multiple times every 15 minutes.

The actual polling period is controlled by EFL and can be changed system-wide.

You can find usage examples in the EFL examples repository: reference/c/core/src/core_poll.c

Further Reading

Efl.Loop API Reference
Detailed documentation for the Loop object