Introduction

LibEventCpp is a lightweight and portable C++14 library designed for handling events efficiently. It is implemented in a single header file, making it easy to integrate into projects. The library supports an unlimited number of arguments for event handlers, providing flexibility in event management. LibEventCpp is designed to be simple to use while offering powerful features for event-driven programming.

📜 License: MIT License

Key Features

📨 Message Event Handler

Asynchronous event processing with delayed and repeated messages support

🔗 Signals and Slots

Qt-style signal-slot connections for decoupled communication

⏰ Time Events

Thread-safe timers with one-shot and periodic modes

🎯 Once Events

Control function execution based on various conditions

🔄 Toggle Events

State-based event triggering with thread synchronization

📁 File Descriptor Events

Monitor multiple file descriptors with poll-based I/O

📡 Signal Events

Clean wrapper around POSIX signal handling

📂 File System Events

Efficient file system monitoring using inotify (Linux)

Supported Compilers

LibEventCpp requires C++14 support. Compatible compilers include:

  • GCC (GNU Compiler Collection): Version 5.0 and later
  • Clang: Version 3.4 and later

Compiler flags:

  • GCC/Clang: -std=c++14

Support Environment

Linux-based for Automotive

LibEventCpp primarily supports Linux environments, making it highly suitable for most automotive projects where Linux-based embedded systems are commonly used. The library leverages POSIX APIs for features such as timers, signals, file descriptors, and file system monitoring, ensuring optimal performance and integration in Linux-based automotive ECUs and embedded platforms.

🚗 Perfect for:
  • Automotive ECU development
  • Linux-based embedded systems
  • Real-time automotive applications
  • POSIX-compliant platforms

Usage Guide

📨 Message Event Handler

Create event handlers by deriving from event_handler::event_handler. The base class uses an event looper to continuously and asynchronously poll and execute messages from an event queue.

Basic Example

#include "libevent.h"

class TestHandler : public event_handler::event_handler
{
public:
    TestHandler() : event_handler::event_handler() {}

    void print_message(std::string s)
    {
        std::cout << s << std::endl;
    }

    void do_heavy_work(int ith)
    {
        for (int i = 0; i < 1000; i++)
        {
            // do something
            int val = i & 1;
        }
        std::cout << "Done work #" << ith << std::endl;
    }

    void tick()
    {
        std::cout << "Tick every 1 second..." << std::endl;
        this->post_delayed_event(1000U, &TestHandler::tick);
    }
};

// Usage
std::shared_ptr<TestHandler> handler = std::make_shared<TestHandler>();

// Post delayed message (2 seconds)
handler->post_delayed_event(2000U, &TestHandler::print_message, 
                             std::string("Delayed message"));

// Post regular message
handler->post_event(&TestHandler::print_message, 
                    std::string("Hello event handler"));

// Post repeated message (5 times, every 1 second)
handler->post_repeated_event(5, 1000U, &TestHandler::repated_work);

🔗 Signals and Slots

Qt-style signal-slot mechanism for decoupled communication between objects.

Basic Example

#include "libevent.h"

// Signal sender
class Sender
{
public:
    sigslot::signal<std::string> message_notification;

    void boardcast_message(std::string mess)
    {
        message_notification(mess);
    }
};

// Signal receiver
class Listener : public sigslot::base_slot
{
public:
    void on_boardcast_received(std::string mess)
    {
        std::cout << "Received: " << mess << std::endl;
    }
};

// Usage
Sender sender;
Listener listener;

// Connect signal to slot
sender.message_notification.connect(&listener, &Listener::on_boardcast_received);

// Emit signal
sender.boardcast_message("Hello from Sender");

// Disconnect
sender.message_notification.disconnect(&listener);

Lambda Connections

sigslot::signal<> test_sig_lambda;
test_sig_lambda.connect([]() {
    std::cout << "Lambda activated" << std::endl;
});
test_sig_lambda();
test_sig_lambda.disconnect_all_callable();

⏰ Time Events

Thread-safe timer mechanism using POSIX APIs. Supports one-shot and periodic timers.

Example

#include "libevent.h"

time_event::timer my_timer;

// Set duration
my_timer.set_duration(1000); // 1 second

// Add callback
my_timer.add_callback([]() {
    std::cout << "Timer expired!" << std::endl;
});

// Start timer
try {
    my_timer.start(5);  // repeat 5 times
    // my_timer.start(0);  // one-shot
    // my_timer.start();   // repeat forever
}
catch (const std::runtime_error& e) {
    std::cerr << "Timer error: " << e.what() << std::endl;
}

// Stop timer
my_timer.stop();

🎯 Once Events

Control function execution based on various conditions like call count, value changes, or time intervals.

once_per_life

once_event::once_per_life init_flag;

// Called only once
init_flag.call_once([]() {
    std::cout << "Initialize resources" << std::endl;
});

once_per_n_times

once_event::once_per_n_times every_third(3);

for (int i = 1; i <= 10; ++i) {
    // Executed on calls 3, 6, 9
    every_third.call_if_due([]() {
        std::cout << "Executed!" << std::endl;
    });
}

once_per_value

once_event::once_per_value<int> value_monitor;

value_monitor.on_value_change(5, [](int val) {
    std::cout << "New value: " << val << std::endl;
}); // Prints: New value: 5

value_monitor.on_value_change(5, [](int val) {
    std::cout << "New value: " << val << std::endl;
}); // Does nothing (unchanged)

once_per_interval

once_event::once_per_interval rate_limiter(1000); // 1 second

bool executed = rate_limiter.call([]() {
    std::cout << "Rate-limited action" << std::endl;
});

🔄 Toggle Events

Synchronization mechanism that triggers a callback only once when a condition becomes true.

Basic Usage

toggle_event::toggle_event toggle;

// Trigger once
toggle.trigger_if_not_set([]() {
    std::cout << "Event triggered!" << std::endl;
}); // Prints

// Subsequent calls ignored
toggle.trigger_if_not_set([]() {
    std::cout << "Won't print" << std::endl;
}); // Does nothing

// Reset and trigger again
toggle.reset();
toggle.trigger_if_not_set([]() {
    std::cout << "Triggered again!" << std::endl;
});

Blocking Wait

toggle_event::toggle_event event;

// Wait for event (blocking)
std::thread waiter([&event]() {
    event.wait(); // Blocks until triggered
    std::cout << "Event received!" << std::endl;
});

// Trigger from another thread
std::this_thread::sleep_for(std::chrono::seconds(2));
event.trigger_if_not_set([]() {
    std::cout << "Triggering event" << std::endl;
});

Wait with Timeout

toggle_event::toggle_event event;

bool result = event.wait_for(2000); // 2 second timeout
if (result) {
    std::cout << "Event triggered" << std::endl;
} else {
    std::cout << "Timeout" << std::endl;
}

📁 File Descriptor Events

Monitor multiple file descriptors using poll-based I/O with event-driven callbacks.

Basic Example

#include "libevent.h"

fd_event::fd_event_manager manager;

// Add file descriptor with callback
manager.add_fd(
    socket_fd,
    static_cast<short>(fd_event::event_type::READ),
    [](int fd, short revents, void* user_data) {
        if (revents & POLLIN) {
            char buffer[1024];
            ssize_t n = read(fd, buffer, sizeof(buffer));
            // Process data...
        }
    },
    nullptr,
    "my_socket"
);

// Main event loop
while (running) {
    int ret = manager.wait_and_process(1000); // 1 second timeout
    if (ret < 0) {
        std::cerr << "Error: " << manager.get_last_error() << std::endl;
    }
}

With User Data

struct Context {
    int counter;
    std::string name;
};

Context my_context = {0, "MyContext"};

manager.add_fd(
    fd,
    POLLIN,
    [](int fd, short revents, void* user_data) {
        Context* ctx = static_cast<Context*>(user_data);
        ctx->counter++;
        std::cout << ctx->name << " event #" << ctx->counter << std::endl;
    },
    &my_context,
    "context_fd"
);

📡 Signal Events

Clean C++ wrapper around POSIX signal handling APIs.

Basic Signal Handler

#include "libevent.h"

void handle_interrupt(int signum) {
    std::cout << "Caught signal " << signum << std::endl;
    std::cout << "Gracefully shutting down..." << std::endl;
}

// Set handler
signal_event::set_signal_handler(SIGINT, handle_interrupt);

// Reset to default
signal_event::reset_signal_handler(SIGINT);

// Ignore signal
signal_event::ignore_signal(SIGUSR1);

Extended Signal Handler

void extended_handler(int signum, siginfo_t* info, void* context) {
    std::cout << "Signal: " << signal_event::get_signal_name(signum) << std::endl;
    std::cout << "Sender PID: " << info->si_pid << std::endl;
    std::cout << "Sender UID: " << info->si_uid << std::endl;
}

signal_event::set_signal_handler_ex(SIGUSR1, extended_handler);

Blocking and Waiting

// Block signals
signal_event::block_signals({SIGUSR1, SIGUSR2});

// Wait for signal (infinite)
int received = signal_event::wait_for_signal({SIGUSR1, SIGUSR2});

// Wait with timeout
received = signal_event::wait_for_signal({SIGUSR1, SIGUSR2}, 5000);

// Check if pending
if (signal_event::is_signal_pending(SIGUSR1)) {
    std::cout << "SIGUSR1 is pending" << std::endl;
}

📂 File System Events

Monitor file system changes using Linux inotify API. Similar to Python's pyinotify.

Basic Example

#include "libevent.h"

// Custom event handler
class my_event_handler : public fs_event::process_event
{
public:
    void process_IN_CREATE(const fs_event::fs_event_info& event) override
    {
        std::cout << "Created: " << event.get_full_path() << std::endl;
    }

    void process_IN_MODIFY(const fs_event::fs_event_info& event) override
    {
        std::cout << "Modified: " << event.get_full_path() << std::endl;
    }

    void process_IN_DELETE(const fs_event::fs_event_info& event) override
    {
        std::cout << "Deleted: " << event.get_full_path() << std::endl;
    }
};

int main()
{
    // Create watch manager
    auto wm = std::make_shared<fs_event::watch_manager>();

    // Create event handler
    auto handler = std::make_shared<my_event_handler>();

    // Define events to monitor
    auto mask = fs_event::fs_event_type::CREATE |
                fs_event::fs_event_type::MODIFY |
                fs_event::fs_event_type::DELETE;

    // Add watch (recursive)
    wm->add_watch("/path/to/watch", mask, true);

    // Create notifier and start event loop
    auto notifier = std::make_shared<fs_event::notifier>(wm, handler);
    notifier->loop();  // Blocking

    return 0;
}

Non-Blocking Mode

auto notifier = std::make_shared<fs_event::notifier>(wm, handler);

bool running = true;
while (running) {
    // Process events with timeout
    int count = notifier->process_events(1000);
    
    if (count > 0) {
        std::cout << "Processed " << count << " events" << std::endl;
    }
    
    // Do other work...
}

Pros and Cons

Each event handling method has its own advantages and disadvantages. Choose the appropriate technique based on your application's specific requirements.

Message Event Handler

✅ Pros

  • Asynchronous message processing without blocking
  • Centralized event handling
  • Flexible event types (instant, delayed, repeated)
  • Handler functions can be reused

❌ Cons

  • Small latency due to queuing
  • Debugging asynchronous code can be challenging
  • One message per handler function

Signals and Slots

✅ Pros

  • Direct and immediate communication
  • Simple and intuitive API
  • One signal can connect to multiple slots
  • Flexible connection management

❌ Cons

  • Synchronous execution can block threads
  • No support for delayed/repeated events
  • Complex connection graphs hard to debug

Time Events

✅ Pros

  • Flexible timer options (one-time, periodic, infinite)
  • Thread-safe implementation
  • Support multiple callbacks
  • Dynamic duration adjustment

❌ Cons

  • Unix/Linux only (POSIX APIs)
  • No pause/resume mechanism yet

Once Events

✅ Pros

  • Precise control over execution patterns
  • Thread-safe
  • Memory efficient
  • Easy to use API

❌ Cons

  • No built-in persistence
  • Limited to single condition
  • once_at_least has background thread overhead

Toggle Events

✅ Pros

  • Simple state management
  • Thread-safe with condition variables
  • Flexible waiting (blocking, timeout, non-blocking)
  • One-time trigger guarantee

❌ Cons

  • Binary state only
  • Manual reset required
  • Potential for deadlocks if used improperly

File Descriptor Events

✅ Pros

  • Efficient monitoring using poll()
  • Thread-safe with mutex protection
  • Dynamic FD management
  • Cross-platform on Unix-like systems

❌ Cons

  • Not scalable for thousands of FDs
  • Unix-only, no Windows support
  • Level-triggered only
  • Requires manual event loop

Signal Events

✅ Pros

  • Clean API for POSIX signals
  • Extended information with siginfo_t
  • Easy signal blocking/unblocking
  • Inter-process communication

❌ Cons

  • Unix/Linux only
  • Cannot catch SIGKILL/SIGSTOP
  • Handler restrictions (async-signal-safe only)
  • Complex interaction with threads

File System Events

✅ Pros

  • Efficient kernel-level monitoring
  • Python-like API (pyinotify style)
  • Flexible event handling
  • Recursive watching support
  • No polling overhead

❌ Cons

  • Linux only (inotify specific)
  • System limits on watch count
  • Event overflow possible
  • Manual recursive watch management

References