Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Last active February 27, 2024 17:20
Show Gist options
  • Save ITotalJustice/579dfe196bae9a590484866bfa220ca1 to your computer and use it in GitHub Desktop.
Save ITotalJustice/579dfe196bae9a590484866bfa220ca1 to your computer and use it in GitHub Desktop.
c99 scheduler, very fast, converted from my c++23 impl
#include "scheduler.h"
#include <stdbool.h>
#include <assert.h>
#ifndef SCHEDULER_UNUSED
#define SCHEDULER_UNUSED(x) (void)(x)
#endif // SCHEDULER_UNUSED
// this is the value that events are set to when disabled
enum { SCHEDULER_DISABLE_VALUE = INT_MAX };
static bool event_is_enabled(const struct SchedulerEvent* e) {
return e->time != SCHEDULER_DISABLE_VALUE;
}
static void event_disable(struct SchedulerEvent* e) {
e->time = SCHEDULER_DISABLE_VALUE;
}
static void find_next_event(struct Scheduler* s) {
int new_id = 0;
for (int i = 1; i < SchedulerID_MAX; i++) {
// don't need to explicitly check if an event is enabled
// as the time will be set to 0x7FFFFFFF
if (s->events[i].time < s->events[new_id].time) {
new_id = i;
}
}
s->next_event_id = new_id;
}
// default reset event
static void default_reset_event(void* user, enum SchedulerID id, unsigned cycles_late) {
SCHEDULER_UNUSED(cycles_late);
struct Scheduler* s = (struct Scheduler*)user;
scheduler_reset_event(s);
scheduler_add_absolute(s, id, SCHEDULER_TIMEOUT_CYCLES, default_reset_event, user);
}
// resets queue and cycles, adds reset event, optional custom callback
void scheduler_reset(struct Scheduler* s, int starting_cycles, SchedulerCallback reset_cb, void* user) {
for (int i = 0; i < SchedulerID_MAX; i++) {
struct SchedulerEvent* e = &s->events[i];
event_disable(e);
}
s->next_event_id = 0;
s->cycles = starting_cycles < SCHEDULER_TIMEOUT_CYCLES ? starting_cycles : SCHEDULER_TIMEOUT_CYCLES;
scheduler_add_absolute(s, SchedulerID_TIMEOUT, SCHEDULER_TIMEOUT_CYCLES, reset_cb ? reset_cb : default_reset_event, user ? user : s);
}
// fires all expired events
void scheduler_fire(struct Scheduler* s) {
while (scheduler_should_fire(s)) {
const enum SchedulerID id = s->next_event_id;
const struct SchedulerEvent event = s->events[id];
event_disable(&s->events[id]);
find_next_event(s);
s->callbacks[id].callback(s->callbacks[id].user, id, s->cycles - event.time);
}
}
// adds relative new / existing event. updates time,cb,user if existing
void scheduler_add(struct Scheduler* s, enum SchedulerID id, int event_time, SchedulerCallback cb, void* user) {
scheduler_add_absolute(s, id, s->cycles + event_time, cb, user);
}
// adds new / existing event. updates time,cb,user if existing
void scheduler_add_absolute(struct Scheduler* s, enum SchedulerID id, int event_time, SchedulerCallback cb, void* user) {
s->events[id].time = event_time;
s->callbacks[id].callback = cb;
s->callbacks[id].user = user;
// check if we updated the next event
if (id == s->next_event_id) {
find_next_event(s);
}
// check if new event fires earlier
else if (s->events[id].time < s->events[s->next_event_id].time) {
s->next_event_id = id;
}
}
// removes an event, does nothing if event not enabled.
void scheduler_remove(struct Scheduler* s, enum SchedulerID id) {
event_disable(&s->events[id]);
if (id == s->next_event_id) {
find_next_event(s);
}
}
// advances scheduler so that get_ticks() == get_next_event_cycles() if event has greater cycles
void scheduler_advance_to_next_event(struct Scheduler* s) {
// only advance if the next event time is greater than current time
if (s->events[s->next_event_id].time > s->cycles) {
s->cycles = s->events[s->next_event_id].time;
}
}
// returns if an event is found with matching id
bool scheduler_has_event(const struct Scheduler* s, enum SchedulerID id) {
return event_is_enabled(&s->events[id]);
}
// returns event cycles - get_ticks(), call has_event() first
int scheduler_get_event_cycles(const struct Scheduler* s, enum SchedulerID id) {
assert(scheduler_has_event(s, id) && "event isn't enabled");
return s->events[id].time - scheduler_get_ticks(s);
}
// returns event cycles, call has_event() first
int scheduler_get_event_cycles_absolute(const struct Scheduler* s, enum SchedulerID id) {
assert(scheduler_has_event(s, id) && "event isn't enabled");
return s->events[id].time;
}
// return cycles - get_ticks() of next event
int scheduler_get_next_event_cycles(const struct Scheduler* s) {
return s->events[s->next_event_id].time - scheduler_get_ticks(s);
}
// return cycles of next event
int scheduler_get_next_event_cycles_absolute(const struct Scheduler* s) {
return s->events[s->next_event_id].time;
}
// use this for empty events, such as signaling for a cpu
// intterupt to break out of loop.
void scheduler_dummy_event(void* user, enum SchedulerID id, unsigned cycles_late) {
SCHEDULER_UNUSED(user);
SCHEDULER_UNUSED(id);
SCHEDULER_UNUSED(cycles_late);
}
// default reset event
void scheduler_reset_event(struct Scheduler* s) {
// no sort because order remains the same.
for (int i = 0; i < SchedulerID_MAX; i++) {
struct SchedulerEvent* e = &s->events[i];
if (event_is_enabled(e)) {
e->time -= SCHEDULER_TIMEOUT_CYCLES;
}
}
s->cycles -= SCHEDULER_TIMEOUT_CYCLES;
}
#ifndef _SCHEDULER_H_
#define _SCHEDULER_H_
#include <limits.h>
#include <stdbool.h>
#ifdef __cplusplus
#extern "C" {
#endif // __cplusplus
// this is the number of cycles until scheduler_reset_event is called
enum { SCHEDULER_TIMEOUT_CYCLES = (INT_MAX / 2) };
enum SchedulerID {
// start of the events, do not remove
SchedulerID_TIMEOUT = 0,
/*---CUSTOM EVENTS---*/
SchedulerID_VDP,
SchedulerID_FRAME,
/*---END OF CUSTOM EVENTS---*/
// end of the events, do not remove
SchedulerID_MAX,
};
typedef void(*SchedulerCallback)(
void* user, // your userdata
enum SchedulerID id, // the unique id of your event
unsigned cycles_late // how many cycles late the event is
);
struct SchedulerEvent {
int time; // time until event expires (scheduler.cycles + event.cycle)
};
struct SchedulerEventCallback {
SchedulerCallback callback; // function to call on event expire
void* user; // user data passed to the callback
};
struct Scheduler {
int cycles;
enum SchedulerID next_event_id;
struct SchedulerEvent events[SchedulerID_MAX];
struct SchedulerEventCallback callbacks[SchedulerID_MAX];
};
// resets queue and cycles, adds reset event, optional custom callback
void scheduler_reset(struct Scheduler* s, int starting_cycles, SchedulerCallback reset_cb, void* user);
// fires all expired events
void scheduler_fire(struct Scheduler* s);
// adds relative new / existing event. updates time,cb,user if existing
void scheduler_add(struct Scheduler* s, enum SchedulerID id, int event_time, SchedulerCallback cb, void* user);
// adds new / existing event. updates time,cb,user if existing
void scheduler_add_absolute(struct Scheduler* s, enum SchedulerID id, int event_time, SchedulerCallback cb, void* user);
// removes an event, does nothing if event not enabled.
void scheduler_remove(struct Scheduler* s, enum SchedulerID id);
// advances scheduler so that get_ticks() == get_next_event_cycles() if event has greater cycles
void scheduler_advance_to_next_event(struct Scheduler* s);
// returns if an event is found with matching id
bool scheduler_has_event(const struct Scheduler* s, enum SchedulerID id);
// returns event cycles - get_ticks(), call has_event() first
int scheduler_get_event_cycles(const struct Scheduler* s, enum SchedulerID id);
// returns event cycles, call has_event() first
int scheduler_get_event_cycles_absolute(const struct Scheduler* s, enum SchedulerID id);
// return cycles - get_ticks() of next event
int scheduler_get_next_event_cycles(const struct Scheduler* s);
// return cycles of next event
int scheduler_get_next_event_cycles_absolute(const struct Scheduler* s);
// use this for empty events, such as signaling for a cpu
// intterupt to break out of loop.
void scheduler_dummy_event(void* user, enum SchedulerID id, unsigned cycles_late);
// default reset event
void scheduler_reset_event(struct Scheduler* s);
// advance scheduler by number of ticks
static inline void scheduler_tick(struct Scheduler* s, int ticks) {
s->cycles += ticks;
}
// returns current time of the scheduler
static inline int scheduler_get_ticks(const struct Scheduler* s) {
return s->cycles;
}
// return true if fire() should be called
static inline bool scheduler_should_fire(const struct Scheduler* s) {
return s->events[s->next_event_id].time <= s->cycles;
}
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _SCHEDULER_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment