Radikant Event C

Device Screen

A lightweight preemptive multithreaded scheduler with tasks, message passing, message queues, pubsub and timers. Designed for tiny applications.

Task Messaging

If  not enabled all logging macros expand to empty loops and will be removed from the binary.

Queues

Filter debug messages at runtime to prevent verbose screen

Timers

Abstracts mutex locking to ensure log messages don't get scrambled when accessed by multiple threads.

PubSub

Define Log Levels, the macros will remove unused logging functions from runtime.

Thread Safety

Use context instances, allow different parts of your program to have different log levels.

TLS Sever

Log to one ore more files as astream simultaneously and independentily withouth any issues


This library makes it possible to run multiple tasks concurrently in a preemptive multithreaded model and is threadsafe. Tasks can intercommunicate via various mechanisms such as direct task messaging, queues, pub/sub and mempackets. The library is inspired by FreeRtos but with desktop platform in mind. You can create simple multithreaded server software withouth too much effort. Intertask communication can either be by “copy” or by “value” and there are various mechanisms in place that can track ownership of data that is passes arround.

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.

Task Messaging

1. Initialize


int main() {
    Scheduler_t s;
    scheduler_init(&s);
    
    Task_t *receiver = create_task(&s, receiver_task, NULL, TASK_PRIORITY_NORMAL, "ReceiverTask");
    Task_t *sender = create_task(&s, sender_task, (void *)receiver, TASK_PRIORITY_NORMAL, "SenderTask");

    start_task(&s, receiver);
    start_task(&s, sender);
    scheduler_run(&s, 1000); 
    return 0;
}
    

Create the sender and reciever tasks, create the scheduler with a tickrate of 1 ms . Pass the reciever task handle as user data to the sender task. The sender task is now “aware” of the recievers taskhandle and can send messages

2. Sender Task


void sender_task(Task_t *self, void *userdata) {
    Task_t *receiver = (Task_t *)userdata;
    int count = 0;
    static char message_buffer[64]; 

    printf("[Sender]   Task started. My Thread ID: %lu. Will send first in 2s.\n",(unsigned long)pthread_self());
    task_delay(self, 2000);	    // Wait for 2 seconds (2000 ticks * 1ms/tick)

    while (1) {
        sprintf(message_buffer, "Hello from sender! Count: %d", count++);
        task_send(self,receiver, 1, (void*)message_buffer, sizeof(message_buffer), TASK_PRIORITY_NORMAL);
        task_delay(self, 1000);
    }
}
    

Dereference the reciever task handle from user data. Create a static stack variable and send data to the reciever using its dereferenced taskhandle.

2. Receiver Task


void receiver_task(Task_t *self, void *userdata) {
    Msg_t msg;
    while (1) {
        if (task_receive(self, &msg, TASK_WAIT_FOREVER)) {
            
        printf("[Receiver][Thread %lu] ===> Received! Type: %d, Data: '%s'\n",
            (unsigned long)pthread_self(), msg.type, (char*)msg.data);   
        }
    }
}
    

Recieve into structure  Msg_t msg and print the received message.

Queues


#include 

// The message struct no longer needs a 'reply_to' field!
typedef struct
{
    int id;
    char text[64];
} SimpleMsg_t;

RQueueHandle_t g_my_queue;

void reader_task_fn(Task_t *self, void *userdata)
{
    SimpleMsg_t received_msg; // Buffer for the "letter"
    Task_t *sender_handle;    // Buffer for the "from" address
    char sender_name[TASK_NAME_MAX_LEN];
    static char reply_payload[] = "ACK: I got your message!";

    while (rqueue_receive(g_my_queue, &sender_handle, &received_msg, WAIT_FOREVER)){
        if (sender_handle)
        {
            if (scheduler_get_task_name(self->scheduler, sender_handle, sender_name, sizeof(sender_name)))
            {
                task_send(self, sender_handle,200,(void *)reply_payload,0,0);
            }
        }
    }
}

void writer_task_fn(Task_t *self, void *userdata)
{
    int count = 0;
    task_delay(self, 2000);

    while (1)
    {
        SimpleMsg_t msg_to_send;
        msg_to_send.id = count;
        sprintf(msg_to_send.text, "Hello! (%d)", count);

        rqueue_send(g_my_queue,self, &msg_to_send,0);
        count++;

        Msg_t reply_msg;         // --- 2. Wait for a reply on our *private* revent queue ---

        if (task_receive(self, &reply_msg, 5000))
        {
            if (reply_msg.type == 200)
            {
                printf("[%s] 📤 ===> REPLY RECEIVED: '%s'\n\n", self->name, (char *)reply_msg.data);
            }
        }
        task_delay(self, 1000);
    }
}

// --- Main Setup (no changes needed) ---
int main()
{
    printf("[Main]     Starting request/reply example.\n");

    Scheduler_t s;
    scheduler_init(&s);

    // Create the queue. The size is just the payload size.
    // The queue logic handles the extra space for the pointer.
    g_my_queue = rqueue_create(10, sizeof(SimpleMsg_t));
    if (!g_my_queue)
    {
        fprintf(stderr, "[Main]     FATAL: Could not create queue.\n");
        return 1;
    }

    // Pass the names during creation
    Task_t *reader = create_task(&s, reader_task_fn, NULL, TASK_PRIORITY_NORMAL, "ReaderTask");
    Task_t *writer = create_task(&s, writer_task_fn, NULL, TASK_PRIORITY_NORMAL, "WriterTask");

    start_task(&s, reader);
    start_task(&s, writer);

    printf("[Main]     Scheduler running. Tasks are live.\n\n");
    scheduler_run(&s, 1000);

    return 0;
}
    

Queues follows a classic RTOS methodology, messages are set on a queue “by copy” any tasks can read of that queue. Typically only 1 queue is created between two tasks with 1 sender and 1 reciever and is tyipically used undirectional.

PubSub


#define TOPIC_SYSTEM_STATS 1
#define TOPIC_USER_EVENTS 2

typedef struct {
    int value;
    char source[32];
} StatMessage_t;

void subscriber_task(Task_t *self, void *userdata) {
    Scheduler_t *s = (Scheduler_t *)userdata;
    const char *task_name = task_get_name(self);
    pubsub_subscribe(s, TOPIC_SYSTEM_STATS, self);
    Msg_t msg;
    while (1) {
        if (task_receive(self, &msg, TASK_WAIT_FOREVER)) {
            if (msg.type == TOPIC_SYSTEM_STATS){
                StatMessage_t *stat = (StatMessage_t *)msg.data;
                printf("[%s] 📬 Received stat from '%s': %d\n", task_name, stat->source, stat->value);
                free(msg.data);}
    }
}}

void publisher_task(Task_t *self, void *userdata) {
    Scheduler_t *s = (Scheduler_t *)userdata;
    int count = 0;
    task_delay(self, 2000);
    
    while (1) {
        StatMessage_t msg_data;
        msg_data.value = count++;
        strncpy(msg_data.source, "Publisher", sizeof(msg_data.source));
        printf("[Publisher] 📢 Publishing new stat: %d\n", msg_data.value);

        int sent_count = pubsub_publish(s,TOPIC_SYSTEM_STATS, &msg_data,sizeof(msg_data),0);    
        task_delay(self, 1000);
    }
}

int main() {
    Scheduler_t s;   
    scheduler_init(&s);
    
    Task_t *publisher   = create_task(&s, publisher_task, &s, TASK_PRIORITY_NORMAL, "Publisher");
    Task_t *subscriber1 = create_task(&s, subscriber_task, &s, TASK_PRIORITY_NORMAL, "Subscriber_1");
    Task_t *subscriber2 = create_task(&s, subscriber_task, &s, TASK_PRIORITY_NORMAL, "Subscriber_2");
    start_task(&s, publisher);
    start_task(&s, subscriber1);
    start_task(&s, subscriber2);

    scheduler_run(&s, 1000); 
    scheduler_destroy(&s);
    return 0; 
}
    

Pub Sub follows the publisher/subscriber model, where a publisher task van publish data but is not interrested who receives it or what is dont with theta the so called one-to-many protocol. 

Timers


#include "event.h"
#include 

void timer_callback(void *userdata) { ... }

int main(void) {
    Scheduler_t sched;
    scheduler_init(&sched);

    Timer_t *timerA = create_timer(&sched, 1000, timer_callback, "Timer A", 0);
    start_timer(timerA);
    scheduler_run(&sched, 1000); // 1 tick = 1ms
}
    

Timers are a mechanicm to register and call a function  asynchonisly after a certain amount of time ellapsed. These are great tools to periodically send information. Timers can be one-shot, or repeating.  

TLS Server


#include 

int main() {
    service_account_t service_account;
    service_account_init(&service_account);
    service_request_acces_token(&service_account);

    service_get_data(service_account.acces_token.acces_token,"test");
    service_firestore_get_data(service_account.acces_token.acces_token, "Ranges");
    service_firestore_get_admins(service_account.acces_token.acces_token);
    return 0;
}
    

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.