Last active
February 27, 2024 17:20
-
-
Save ITotalJustice/579dfe196bae9a590484866bfa220ca1 to your computer and use it in GitHub Desktop.
c99 scheduler, very fast, converted from my c++23 impl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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