Question

this is a little theoretical question. Imagine a device full of sensors. Now, in case a sensor x detects something, something should happen. Meanwhile, in case something else is detected, like two sensors detects two different things, then, this device must behave differently.

From webdesign (so javascript) I learned about Events, for example (using jquery) $(".x").on("click", function(){}) or from angularjs $scope.watch("name_of_var", function()).

Is there any possibility to replicate this behaviour in C, without using complex libraries?

Thanks.

Was it helpful?

Solution 2

I would assume you're owning an embedded system with access to interrupts or a major event loop in a separate thread, otherwise this isn't possible..

A basic model for event handling is here:

#define NOEVENT 0

typedef void *(*EventHandler)(void *);

void *doNothing(void *p){/*do nothing absolutely*/ return NULL; }
typedef struct _event{
  EventHandler handler;
}Event, *PEvent;

Event AllEvents[1000];
unsigned short counter = 0;
void InitEvents()
{
    LOCK(AllEvents);
    for(int i = 0; i < 1000; i++){ 
        AllEvents[i].handler = doNothing;
    }
    UNLOCK(AllEvents);
}
void AddEvent(int EventType, EventHandler ev_handler)
{
    LOCK(AllEvents);
    AllEvents[EventType].handler = ev_handler;
    UNLOCK(AllEvents);
}

void RemoveEvent(int EventType, EventHandler ev_handler)
{
   LOCK(AllEvents);
   AllEvents[EventType] = doNothing;
   UNLOCK(AllEvents); /*to safeguard the event loop*/
}

/*to be run in separate thread*/
void EventLoop()
{
   int event = NOEVENT;
   EventHandler handler;
   while(1){
       while(event == NOEVENT)event=GetEvents();
       handler = AllEvents[event].handler;
       handler();/*perform on an event*/
  }
}

Sorry if this is slightly naive.. but this is the best I could think of at the moment.

OTHER TIPS

A system I can think about is a subscriber-notifier model. You may have something that handles your sensors (for example, a thread that polls on it to see if something happened). When it detects something, the task should raise a mechanism in order to let the outer world be aware : this is the notification process.
On the other side, only the people who are interested in your sensor should be notified, thus, a subscription method should be here to take care of this.

Now, the hard part comes in. When the sensor handler notifies the world, it must NOT spend too much time doing so otherwise it might miss other events. Thus, it is compulsory to have a task (or thread) dedicated to the notifying process. On the other hand, the subscribers wish to have some of their data updated when such a notified event is received. This is obviously an asynchronous process and thus the subscribers will have to supply the notifier thread with a callback.
Finally, you should mark your events with timestamps, this way the receivers will know if the event they get is outdated and wether or not they should discard it.
The final thing may look like the piece of code below :


Data structures

/*
 * Some data structures to begin with
 */
struct event;
struct notifier;
struct subscription;
struct notify_sched;


typedef int (*notify_cbck)(struct event *evt, void *private);
/*
 *@type : a value to show the type of event
 *@t : the timestamp of the event
 *@value : a pointer towards the event data
 */
struct event {
    int type;
    struct timeval t; // the timestamp
    void *value;
};

/*
 * @type : the type in which the subscriber is interested
 * @cb : the callback that should be run when an event occur
 * @cb_data : the data to provide to the callback
 * @next,prev : doubly-linked list
 */
struct subscription {
    int type;
    notify_cbck cb;
    void *cb_data;
    struct subscription *next, *prev;
};

/*
 * This structure gathers the subscriptions of a given type.
 * @type : the event type
 * @subs : the subscription list
 * @mutex : a mutex to protect the list while inserting/removing subscriptions
 * @next,prev : link to other typed subscriptions
 */

struct typed_subscription {
    int type;
    struct subscription *subs;
    mutex_t mutex;
    struct typed_subscription *next, *prev;
};

/*
 * @magic : the ID of the event producer
 * @t_subs : the typed_subscription list
 * @mutex : a mutex to protect data when (un)registering new types to the producer
 * @next, prev : doubly-linked list ...
 */
struct notifier {
    int magic;
    struct typed_subscription *t_subs;
    mutex_t mutex;
    struct notifier *next, *prev;
};

/*
 * @ntf : the notifiers list
 * @mutex : a mutex to protect the ntf list
 * @th : something to identify the task that hosts the scheduler
 */
struct notify_sched {
    struct notifier *ntf;
    mutex_t mutex;
    pthread_t th; // I assume it's a classic pthread in this example.
};

I do not have the time to complete my answer right now, I will edit it later to give you the full example. But starting from the data structures, you should get some ideas. Hope this is a bit helpful anyhow.

For events you need event loop, which detects that the actual event (say, data from network) happens, and then generates the the software event structure and calls appropriate event handler, or in more complex systems, chain of event handlers, until a handler marks the event accepted. If there's no handler, or no registered handler accepts the event, event is ignored.

Often event loop is in a library, which has API for application to have event handlers, and send application specific events, in addition to any events the library itself may produce. One problem with event based applications is, it is often complicated to use two libraries which both want to have their own event loop, unless library developer took extra care to allow using other event loop than library's own.

Unless it is a very low level real time system, it's critical that event loop does not do busy wait. In Linux/Unix/Posix code, event loop typically works around select() or poll() system function. When there are no events, event loop calls this function with timeout matching time of next timer event (if there are timer events). In addition to timeout, select()/poll() will also return if any of specified file handles (often network or IPC sockets) are ready for reading/writing/error, as well as if there is an interrupt which is not otherwise handled. Then event loop code checks why the function returned, generates and dispatches necessary events, and when all is done, calls the select()/poll() function again.

Also important in event based systems is, event handler must not block, because it is function called by event loop, so event loop does not continue somewhere in the background, handler function call is part of event loop. So handler function must process only available data, preferably quickly, then store necessary state to continue later, and return to wait for next event. For operations that must block, another thread must be launched. Also for long computations, the computation must either be chopped to small pieces to allow event loop to run too, or computation must happen in another thread. The annoying "Not responding" in GUI application title bar typically means just this: application programmer was lazy/incompetent and blocked event loop, so it can't react to OS events.

So, yes, it is quite easy to have event based system with C. Just have a loop with select()/poll() in it, define event types, create data structs for events, and have a list of function pointers to be called with a new event struct as parameter, for each event type.

Because I found this message and I didn't exactly found my solution, I post my code here based from https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/ for others.

event.h

typedef struct s_Arguments {
    char *name;
    int value;
} Arguments; // Treat this as you please
 
typedef struct s_Event {
    char *name;
    Arguments args;
} Event;
 
typedef struct {
    char *name;
    void (*handler)(void*);
} Handler;

struct s_EventContainer {
    int *exitFlag;
    Event *poll;
    Handler *listeners;
    void (*registerEvent)(char *name, void*);
    void (*emit)(char *name, Arguments args);
    int listenersc;
    int pollc;
};

struct s_EventContainer eventContainer;

void _registerEvent(char *name, void*);
void _emitEvent(char *name, Arguments args);

void popPoll();

void find_and_exec_handler_in_listeners(char *name, Arguments *args);

void * fn_eventsDigest(void * p_Data);

void *testFired(Arguments *args);

event.c

#include "event.h"


int BLOCK_POP, BLOCK_EMIT;


void _registerEvent(char *name, void *cb) {
    LOG("_registerEvent '%s'", name);
    if (!eventContainer.listeners) eventContainer.listeners = malloc(sizeof(Handler));    
    Handler listener = {name, cb};  
    eventContainer.listeners[eventContainer.listenersc] = listener;  
    eventContainer.listenersc++;  
    eventContainer.listeners = realloc(eventContainer.listeners, sizeof(Handler) * (eventContainer.listenersc+1));  
}

void _emitEvent(char *name, Arguments args) {
    LOG("Emit event '%s' with args value %d", name, args.value);
    while(BLOCK_EMIT) asm("nop");
    BLOCK_POP = 1;
    if (!eventContainer.poll) eventContainer.poll = malloc(sizeof(Event));    
    Event poll = {name, args};  
    eventContainer.poll[eventContainer.pollc] = poll;  
    eventContainer.pollc++;  
    eventContainer.poll = realloc(eventContainer.poll, sizeof(Event) * (eventContainer.pollc+1));  
    BLOCK_POP = 0;
}

void popPoll(){
    int* temp = malloc((eventContainer.pollc - 1) * sizeof(Event));
    memcpy(temp, eventContainer.poll+1, (eventContainer.pollc - 1) * sizeof(Event));
    eventContainer.pollc -= 1;
    eventContainer.poll = realloc(eventContainer.poll, eventContainer.pollc * sizeof(Event) + sizeof(Event));
    memcpy(eventContainer.poll, temp, eventContainer.pollc * sizeof(Event));
    temp = NULL;
}



void find_and_exec_handler_in_listeners(char *name, Arguments *args){
    int i;
    for (i=0; i < eventContainer.listenersc; i++) {
        if (strcmp(eventContainer.listeners[i].name, name ) == 0 ) {
            (*eventContainer.listeners[i].handler)(args);
        }
    }
}


void * fn_eventsDigest(void * p_Data) {
    struct s_EventContainer *eventContainer = (struct s_EventContainer *)p_Data;

    BLOCK_POP = 0;
    BLOCK_EMIT = 0;
    
    while(*(eventContainer->exitFlag) == 0) {
        if ( eventContainer->pollc > 0 && BLOCK_POP == 0) {
            BLOCK_EMIT = 1;
            Event ev = eventContainer->poll[0];
            find_and_exec_handler_in_listeners(ev.name, &ev.args);
            popPoll(); 
            BLOCK_EMIT = 0;
        }
    usleep(1*1000);
    }
    printf("Event Loop::exiting...\r\n"); fflush(stdout);
}

void *testFired(Arguments *args) {
    LOG("test event fired with value %d \r\n", args->value);
}

main.c

#include "event.h"
#include <pthread.h>

struct s_EventContainer eventContainer = {
    &Data.exitFlag, //exit loop
    NULL, //poll
    NULL, //listeners
    _registerEvent,
    _emitEvent,
    0,
    0
};

pthread_t events_thread;

int main(int argc, char** argv) {

    eventContainer.registerEvent("test", &testFired);
    
    int ret = pthread_create (&events_thread, NULL, fn_eventsDigest, &eventContainer);
    if (ret) {
        fprintf (stderr, "%s", strerror (ret));
    }
    pthread_setname_np(events_thread,"events_thread");
   
    //....
    sleep(2);
    Arguments args;
    args.name = "test";
    args.value = 10;
    eventContainer.emit("test", args);

    pthread_join (events_thread, NULL);
    return (EXIT_SUCCESS);
}

I'm open to suggestions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top