Usage Examples

Relevant source files

This document provides practical examples demonstrating how to use the HandlerTable API in real-world scenarios. It covers common usage patterns, integration strategies, and best practices for implementing lock-free event handling in no_std environments.

For detailed API documentation, see API Reference. For implementation details about the underlying atomic operations, see Implementation Details.

Basic Event Handler Registration

The fundamental usage pattern involves creating a static HandlerTable, registering event handlers, and dispatching events as they occur.

Static Table Creation and Handler Registration


The basic pattern demonstrated in README.md(L11 - L31)  shows:

OperationMethodPurpose
CreationHandlerTable::new()Initialize empty handler table
Registrationregister_handler(idx, fn)Associate handler function with event ID
Dispatchhandle(idx)Execute registered handler for event
Cleanupunregister_handler(idx)Remove and retrieve handler function

Sources: README.md(L11 - L31) 

Event Registration Patterns

Single Event Handler Registration

The most common pattern involves registering individual handlers for specific event types. Each handler is a closure or function pointer that gets executed when the corresponding event occurs.

sequenceDiagram
    participant EventSource as "Event Source"
    participant HandlerTableN as "HandlerTable<N>"
    participant AtomicUsizeidx as "AtomicUsize[idx]"
    participant EventHandlerFn as "Event Handler Fn"

    EventSource ->> HandlerTableN: "register_handler(0, closure)"
    HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, fn_ptr)"
    alt "Registration Success"
        AtomicUsizeidx -->> HandlerTableN: "Ok(0)"
        HandlerTableN -->> EventSource: "true"
    else "Slot Occupied"
        AtomicUsizeidx -->> HandlerTableN: "Err(existing_ptr)"
        HandlerTableN -->> EventSource: "false"
    end
    Note over EventSource,EventHandlerFn: "Later: Event Dispatch"
    EventSource ->> HandlerTableN: "handle(0)"
    HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "fn_ptr"
    HandlerTableN ->> EventHandlerFn: "transmute + call()"
    EventHandlerFn -->> HandlerTableN: "execution complete"
    HandlerTableN -->> EventSource: "true"

Sources: README.md(L16 - L21) 

Multiple Handler Registration

For systems handling multiple event types, handlers are typically registered during initialization:

flowchart TD
subgraph subGraph2["Handler Functions"]
    TimerFn["timer_interrupt_handler"]
    IOFn["io_completion_handler"]
    UserFn["user_signal_handler"]
end
subgraph subGraph1["HandlerTable Operations"]
    Reg0["register_handler(0, timer_fn)"]
    Reg1["register_handler(1, io_fn)"]
    Reg2["register_handler(2, user_fn)"]
end
subgraph subGraph0["System Initialization Phase"]
    Init["System Init"]
    Timer["Timer Handler Setup"]
    IO["I/O Handler Setup"]
    User["User Event Setup"]
end

IO --> Reg1
Init --> IO
Init --> Timer
Init --> User
Reg0 --> TimerFn
Reg1 --> IOFn
Reg2 --> UserFn
Timer --> Reg0
User --> Reg2

Sources: README.md(L16 - L21) 

Event Handling Scenarios

Successful Event Dispatch

When an event occurs and a handler is registered, the handle method executes the associated function and returns true:

flowchart TD
subgraph subGraph0["Example from README"]
    Ex1["handle(0) -> true"]
    Ex2["handle(2) -> false"]
end
Event["Event Occurs"]
Handle["handle(event_id)"]
Check["Check AtomicUsize[event_id]"]
Found["Handler Found?"]
Execute["Execute Handler Function"]
ReturnFalse["Return false"]
ReturnTrue["Return true"]

Check --> Found
Event --> Handle
Execute --> ReturnTrue
Found --> Execute
Found --> ReturnFalse
Handle --> Check

The example in README.md(L23 - L24)  demonstrates both successful dispatch (handle(0) returns true) and missing handler scenarios (handle(2) returns false).

Sources: README.md(L23 - L24) 

Handler Retrieval and Cleanup

The unregister_handler method provides atomic removal and retrieval of handler functions:

flowchart TD
subgraph subGraph2["Example Calls"]
    Unreg1["unregister_handler(2) -> None"]
    Unreg2["unregister_handler(1) -> Some(fn)"]
end
subgraph subGraph1["Return Values"]
    Some["Some(handler_fn)"]
    None["None"]
end
subgraph subGraph0["Unregistration Process"]
    Call["unregister_handler(idx)"]
    Swap["atomic_swap(0)"]
    Check["Check Previous Value"]
    Result["Option"]
end

Call --> Swap
Check --> Result
None --> Unreg1
Result --> None
Result --> Some
Some --> Unreg2
Swap --> Check

The pattern shown in README.md(L26 - L28)  demonstrates retrieving a previously registered handler function, which can then be called directly.

Sources: README.md(L26 - L30) 

Advanced Usage Patterns

Concurrent Handler Management

In multi-threaded environments, multiple threads can safely register, handle, and unregister events without synchronization primitives:

flowchart TD
subgraph subGraph2["Memory Ordering"]
    Acquire["Acquire Ordering"]
    Release["Release Ordering"]
    Consistency["Memory Consistency"]
end
subgraph subGraph1["Atomic Operations Layer"]
    CAS["compare_exchange"]
    Load["load(Acquire)"]
    Swap["swap(Acquire)"]
end
subgraph subGraph0["Thread Safety Guarantees"]
    T1["Thread 1: register_handler"]
    T2["Thread 2: handle"]
    T3["Thread 3: unregister_handler"]
end

Acquire --> Consistency
CAS --> Release
Load --> Acquire
Release --> Consistency
Swap --> Acquire
T1 --> CAS
T2 --> Load
T3 --> Swap

Sources: README.md(L14) 

Static vs Dynamic Handler Tables

The compile-time sized HandlerTable<N> enables static allocation suitable for no_std environments:

ApproachDeclarationUse Case
Static Globalstatic TABLE: HandlerTable<8>System-wide event handling
Local Instancelet table = HandlerTable::<16>::new()Component-specific events
Const GenericHandlerTable<MAX_EVENTS>Configurable event capacity

The static approach shown in README.md(L14)  is preferred for kernel-level integration where heap allocation is unavailable.

Sources: README.md(L14) 

Integration Examples

ArceOS Kernel Integration

In the ArceOS operating system context, HandlerTable serves as the central event dispatch mechanism:

flowchart TD
subgraph subGraph2["Event Dispatch Flow"]
    Dispatch["event_dispatch(id)"]
    Handle["TABLE.handle(id)"]
    Execute["handler_function()"]
end
subgraph subGraph1["HandlerTable Integration"]
    TABLE["static KERNEL_EVENTS: HandlerTable<32>"]
    IRQ["IRQ_HANDLERS[0..15]"]
    TIMER["TIMER_EVENTS[16..23]"]
    SCHED["SCHED_EVENTS[24..31]"]
end
subgraph subGraph0["ArceOS Kernel Components"]
    Interrupt["Interrupt Controller"]
    Timer["Timer Subsystem"]
    Scheduler["Task Scheduler"]
    Syscall["System Call Handler"]
end

Dispatch --> Handle
Handle --> Execute
IRQ --> TABLE
Interrupt --> IRQ
SCHED --> TABLE
Scheduler --> SCHED
Syscall --> TABLE
TABLE --> Dispatch
TIMER --> TABLE
Timer --> TIMER

Sources: README.md(L7) 

Device Driver Event Handling

Device drivers can use dedicated handler tables for managing I/O completion events:

flowchart TD
subgraph subGraph2["Event Types"]
    Complete["DMA_COMPLETE"]
    Error["DMA_ERROR"]
    Timeout["DMA_TIMEOUT"]
end
subgraph subGraph1["Event Management"]
    DriverTable["HandlerTable"]
    CompletionFn["io_completion_handler"]
    ErrorFn["io_error_handler"]
end
subgraph subGraph0["Driver Architecture"]
    Driver["Device Driver"]
    DMA["DMA Controller"]
    Buffer["I/O Buffers"]
end

Complete --> CompletionFn
CompletionFn --> Buffer
DMA --> Complete
DMA --> Error
DMA --> Timeout
Driver --> DriverTable
Error --> ErrorFn
ErrorFn --> Buffer
Timeout --> ErrorFn

This pattern allows drivers to maintain separate event handling contexts while leveraging the same lock-free infrastructure.

Sources: README.md(L7)