Showing posts with label delayed event trigger. Show all posts
Showing posts with label delayed event trigger. Show all posts

Sunday, June 29, 2025

Building on Event Handlers: Implementing Delayed Triggers in C++

Introduction

Earlier, I explained four foundational event handler patterns in C++—from basic callbacks to observer systems (https://a5w1h.blogspot.com/2025/06/event-handlers-in-c-journey-from.html). Today, we level up: how do you execute actions not just when an event occurs, but precisely when you need them—even seconds, minutes, or hours later?

Imagine:

·        A security system that delays alarm triggers by 30 seconds to allow authorized deactivation.

·        An e-commerce platform that schedules abandoned-cart reminders 2 hours after a user leaves.

·        A robotics controller that sequences movements with millisecond-precision pauses.

These aren’t theoretical—they’re real-world problems solved by decoupling event detection from action timing. The CTimedEvent class embodies this philosophy.


The CTimedEvent class provides a thread-safe event system where subscribers can attach callbacks to be executed either immediately or with a specified delay when an event is triggered. This design is common in applications like game engines, IoT systems, or UI frameworks where actions need to respond to events synchronously or asynchronously.

    void subscribe(Callback callback) {
        std::lock_guard<std::mutex> lock(mutex_);
        immediate_callbacks_.push_back(std::move(callback));
    }
 
    void subscribe_with_delay(Callback callback, unsigned int delay_ms) {
        std::lock_guard<std::mutex> lock(mutex_);
        delayed_callbacks_.push_back({std::move(callback), delay_ms});
    }
 
    void trigger(Args... args) {
 
        // Process immediate callbacks
        {
            std::lock_guard<std::mutex> lock(mutex_);
            for (const auto& cb : immediate_callbacks_) {
                if (cb) cb(args...);
            }
        }
 
        // Process delayed callbacks
        {
            std::lock_guard<std::mutex> lock(mutex_);
            for (const auto& tcb : delayed_callbacks_) {
                if (tcb.func) {
                    launch_delayed(tcb.func, tcb.delay_ms, args...);
                }
            }
        }
    }
 

You may find the source code in github: https://github.com/happytong/EventTemplates/blob/main/CTimedEvent.h.


Key Components & Design Principles



1.     Observer Pattern:

o   Purpose: Decouples event sources from subscribers.

o   Implementation:

§  subscribe(): Registers immediate callbacks.

§  subscribe_with_delay(): Registers delayed callbacks.

§  trigger(): Notifies all subscribers when an event occurs.

o   Benefit: Flexible, dynamic subscription without modifying the event source.

2.     RAII (Resource Acquisition Is Initialization):

o   std::lock_guard in subscribe()subscribe_with_delay(), and trigger() automatically locks/unlocks mutex_ to ensure thread safety.

3.     Concurrency & Thread Safety:

o   Mutex (mutex_): Protects shared data (immediate_callbacks_delayed_callbacks_) from race conditions.

o   Detached Threads: Delayed callbacks run in separate threads to avoid blocking the main thread.

4.     Template Flexibility:

o   template <typename... Args> allows the class to handle events with any argument signature (e.g., CTimedEvent<int, string>).


Workflow Explained

Here’s how the system processes a trigger() call:


1.     Immediate Callbacks:

o   Executed synchronously in the triggering thread.

o   Example: Logging, real-time state updates.

2.     Delayed Callbacks:

o   Each callback runs in a separate detached thread.

o   The thread:

1.     Binds arguments to the callback.

2.     Sleeps via delay(delay_ms).

3.     Executes the callback.

o   Example: Scheduled tasks, debouncing user input.


Critical Functions

1.     delay(int nMs):

o   Uses nanosleep() to pause execution for nMs milliseconds. You may replace delay() with std::this_thread::sleep_for().

o   Handles EINTR (interrupted system calls) by retrying.

2.     launch_delayed():

o   Binds arguments to the callback using std::bind.

o   Spawns a detached thread to manage the delay and execution.

3.     trigger(Args... args):

o   Processes immediate callbacks under lock.

o   Launches threads for delayed callbacks (also under lock).


Real-World Use Cases

1.     Game Development:

o   Immediate: Update player health on collision.

o   Delayed: Respawn enemies after 5 seconds.

2.     UI Applications:

o   Immediate: Refresh UI on data change.

o   Delayed: Auto-save form data after 2 seconds of inactivity.

3.     IoT Systems:

o   Immediate: Alert on critical sensor readings.

o   Delayed: Turn off lights after 10 minutes of no motion.


Potential Pitfalls & Improvements

1.     Resource Exhaustion:

o   Risk: Many delayed callbacks → excessive threads.

o   Fix: Use a thread pool + priority queue (e.g., a scheduler thread).

2.     Argument Lifetime:

o   Risk: Arguments referenced in delayed callbacks may be destroyed.

o   Fix: Use shared_ptr or copy arguments by value.

3.     Exception Handling:

o   Risk: Exceptions in callbacks terminate detached threads.

o   Fix: Wrap callbacks in try/catch.

4.     Scalability:

o   Risk: Lock contention in high-frequency events.

o   Fix: Copy callbacks under lock, then execute without lock:

std::vector<Callback> tmp;
{
    std::lock_guard<std::mutex> lock(mutex_);
    tmp = immediate_callbacks_; // Copy
}
for (auto& cb : tmp) { /*...*/ }

Example Usage

CTimedEvent<std::string, int> event;
 
// Immediate callback
event.subscribe([](std::string msg, int code) {
    std::cout << "[IMMEDIATE] " << msg << " (" << code << ")\n";
});
 
// Delayed callback (2 seconds)
event.subscribe_with_delay([](std::string msg, int code) {
    std::cout << "[DELAYED] " << msg << " (" << code << ")\n";
}, 2000);
 
// Trigger event
event.trigger("Error: DB timeout", 500);
 
// Output:
// [IMMEDIATE] Error: DB timeout (500)
// [DELAYED] Error: DB timeout (500) [after 2 seconds]

Conclusion

The CTimedEvent class elegantly combines:

·        Observer Pattern for decoupled subscriptions.

·        RAII for thread-safe resource management.

·        Asynchronous Threading for delayed tasks.

Use it for event-driven scenarios requiring flexible timing, but enhance it with thread pooling and lifetime management for production systems.


Inspiring Innovation: Design Systems That Fit Your Vision

As you explore the CTimedEvent class, remember: great software design isn’t about rigidly following patterns—it’s about creatively adapting them to your unique challenges. This implementation is a starting point, not a final destination. Ask yourself:

"What if I could..."

·        Scale effortlessly? Replace detached threads with a dedicated thread pool to handle thousands of delayed events without resource exhaustion.

·        Add precision? Integrate a priority queue for callbacks sorted by execution time, turning CTimedEvent into a high-precision scheduler.

·        Enhance resilience? Wrap callbacks in retry logic or circuit breakers to handle failures in distributed systems.

Your Call to Action

1.     Experiment Fearlessly:

o   Modify launch_delayed() to support cancellable timers (e.g., abort a delayed email if the user edits it within 5 seconds).

o   Add recurring events by rescheduling callbacks after execution.

2.     Observe Real Needs:

o   In a game? Extend this to predictive event batching (e.g., "trigger all physics callbacks in the next 16ms frame").

o   Building IoT? Embed energy-aware delays (e.g., "execute during low-power cycles").

3.     Break Boundaries:

o   "What if my events span machines?" Replace std::function with gRPC stubs for distributed events.

o   "What if timing is critical?" Swap nanosleep() for hardware timers or RTOS primitives.

🔥 Remember: The most elegant systems emerge when you understand the rules—then innovate beyond them. Your use case is unique; your solution should be too. Start simple, iterate boldly, and engineer something that doesn’t just work—it inspires.

Now go build the event system only YOU can envision. ðŸš€

This article was enhanced with DeepSeek.

Building on Event Handlers: Implementing Delayed Triggers in C++

Introduction Earlier, I explained  four foundational event handler patterns  in C++—from basic callbacks to observer systems ( https://a5w...