C/C++ Arena

Step 6 of 6

Challenge: an event bus

A plain function pointer has a limitation: the function it points at can only use its parameters and global variables. What if the callback needs to update your data, like a counter in main or a particular window in a GUI?

C's answer is the context pointer. Alongside the function pointer, you register a void *ctx, which can point at anything. Whoever calls the callback passes that ctx back unchanged. The callback casts it to the type it expects and uses it.

#include <stdio.h>

typedef void (*Visitor)(int value, void *ctx);

void for_each(const int *a, int n, Visitor v, void *ctx) {
    for (int i = 0; i < n; i++) {
        v(a[i], ctx);
    }
}

typedef struct {
    int sum;
    int count;
} Stats;

static void add_to_stats(int value, void *ctx) {
    Stats *s = ctx;
    s->sum += value;
    s->count++;
}

static void track_max(int value, void *ctx) {
    int *best = ctx;
    if (value > *best) *best = value;
}

int main(void) {
    int data[] = {4, 9, 2};
    Stats st = {0, 0};
    int best = 0;
    for_each(data, 3, add_to_stats, &st);
    for_each(data, 3, track_max, &best);
    printf("sum %d count %d max %d\n", st.sum, st.count, best);
    return 0;
}
sum 15 count 3 max 9

The same for_each drives two completely different callbacks, each with its own kind of context. This "function pointer plus void * user data" pattern appears throughout real C APIs: pthread_create, qsort_r, event loops, GUI toolkits and audio libraries.

An event bus

An event bus stores several handler and context pairs. Emitting an event calls every registered handler, in order, each with its own context. Store the pairs in parallel arrays (or an array of structs), guard against overflowing the fixed capacity, and loop over them on emit.

Your task

Build a tiny event bus that stores up to 8 handler/context pairs.

Challenge 2

The bus from this step is written for you. Add int bus_off(Bus *b, Handler h, void *ctx) that removes the first registration with exactly this handler and context, keeping the others in order. Return 0, or -1 if there was no such registration.

Challenge 3

Make the bus filter by event type. Each registration now has a type: bus_on(b, type, h, ctx) registers for one type, and type -1 means "every type". bus_emit(b, type, value) calls, in registration order, only the handlers registered for that type or for -1, passing them value. Return -1 from bus_on when all 8 slots are used.

Previous: Generic code with void *