diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/pulp.c | 174 |
1 files changed, 155 insertions, 19 deletions
@@ -33,23 +33,29 @@ #include <time.h> #include <stdlib.h> #include <sys/time.h> + +#ifdef __WIN32__ +/* windows doesn't have ucontext.h, but it does have a fiber API */ +#include <windows.h> +#else #include <ucontext.h> +#endif #include "list.h" #include "pulp.h" -/* for when this needs to support windows: - * https://www.codeproject.com/Tips/4225/Unix-ucontext-t-Operations-on-Windows-Platforms - */ - #define PULP_USECS_PER_SEC 1000000ULL #define PULP_USECS_PER_MSEC 1000ULL #define PULP_FIBER_STACK_SIZE (16 * 1024) #define PULP_FIBER_ALLOC_NUM 32 typedef struct pulp_context_t { +#ifdef __WIN32__ + LPVOID win32_fiber; +#else ucontext_t ucontext; char stack[PULP_FIBER_STACK_SIZE]; +#endif } pulp_context_t; typedef struct pulp_fiber_t { @@ -62,6 +68,9 @@ typedef struct pulp_fiber_t { } state; pulp_context_t context; +#ifdef __WIN32__ + thunk_t *thunk; +#endif } pulp_fiber_t; typedef struct pulp_fiber_alloc_t { @@ -82,7 +91,7 @@ typedef struct pulp_t { } fibers; list_head_t allocs; /* list of pulp_fiber_alloc_t */ - pulp_context_t schedule_context; + pulp_context_t trampoline_context; pulp_context_t caller_context; } pulp_t; @@ -140,6 +149,76 @@ static inline void set_current_fiber(pulp_t *pulp, list_head_t *head) } +#ifdef __WIN32__ +/* sets the supplied context as the executing context */ +static void enter_context(pulp_context_t *context) +{ + assert(context); + + SwitchToFiber(context->win32_fiber); +} + + +/* win32 fibers can't return or the program exits, so their functions need to + * run from a trampoline function, and there's only one pointer supplied so now + * I need the fiber's thunk saved in the fiber structure, and I find the fiber + * to run via pulp->current. + * + * Note this helper is only used with thunk/pulp_fiber_new() contexts. + */ +static void win32_trampoline(void *ptr) +{ + pulp_t *pulp = ptr; + + assert(pulp); + assert(pulp->current); + assert(pulp->current->thunk); + + (void) thunk_dispatch(pulp->current->thunk); + enter_context(&pulp->trampoline_context); +} + + +/* setup a context to run func w/ptr */ +static void setup_context(pulp_t *pulp, pulp_context_t *context, void *func, void *ptr) +{ + thunk_t *thunk = ptr; + + assert(pulp); + assert(context); + assert(func); + + if (thunk && thunk->dispatch == func) /* yuck */ + context->win32_fiber = CreateFiber(PULP_FIBER_STACK_SIZE, win32_trampoline, pulp); + else + context->win32_fiber = CreateFiber(PULP_FIBER_STACK_SIZE, func, ptr); +} + + +/* swaps the new supplied context with the executing context */ +/* the current context is saved in old */ +static void swap_context(pulp_context_t *old, pulp_context_t *new) +{ + assert(old); + assert(new); + + if (old == new) + return; + + enter_context(new); +} + + +/* destroy the fiber */ +static void destroy_context(pulp_context_t *context) +{ + assert(context); + + DeleteFiber(context->win32_fiber); +} + +#else + /* setup a context to run func w/ptr */ static void setup_context(pulp_t *pulp, pulp_context_t *context, void *func, void *ptr) { @@ -150,8 +229,8 @@ static void setup_context(pulp_t *pulp, pulp_context_t *context, void *func, voi getcontext(&context->ucontext); context->ucontext.uc_stack.ss_sp = context->stack; context->ucontext.uc_stack.ss_size = sizeof(context->stack); - context->ucontext.uc_link = &pulp->schedule_context.ucontext; - makecontext(&context->ucontext, func, 1, ptr); + context->ucontext.uc_link = &pulp->trampoline_context.ucontext; + makecontext(&context->ucontext, func, 1, ptr); /* FIXME: technically, this is only supposed to take int arguments */ } @@ -178,15 +257,41 @@ static void swap_context(pulp_context_t *old, pulp_context_t *new) } -/* handle a return out of a fiber, equivalent to the fiber exiting */ -static void schedule_context(pulp_t *pulp) +/* ucontext has nothing to destroy */ +static void destroy_context(pulp_context_t *context) +{ +} +#endif + + +/* Infinitely schedules fibers, this gets its own context in + * pulp->trampoline_context. + * + * On ucontext systems, this is what uc_link executes. + * + * On win32, there's a win32_trampoline helper which wraps the fiber's + * function, entering pulp->trampoline_context when the fiber's function + * returns, like ucontext does for me with the uc_link member. + * + * It's necessary that fiber cleanup occur in a separate context, since you + * can't destroy the calling context. + * + * Note that though this is coded as an infinite loop, pulp_schedule()'s + * default target is pulp->caller_context. When the run queue is drained, the + * caller context is entered, and pulp_tick() returns. + */ +static void trampoline(pulp_t *pulp) { assert(pulp); - if (pulp->current) - put_current_fiber(pulp, &pulp->fibers.free); + for (;;) { + if (pulp->current) { + destroy_context(&pulp->current->context); + put_current_fiber(pulp, &pulp->fibers.free); + } - pulp_schedule(pulp); + pulp_schedule(pulp); + } } @@ -224,7 +329,7 @@ pulp_t * pulp_new(void) return NULL; } - setup_context(pulp, &pulp->schedule_context, schedule_context, pulp); + setup_context(pulp, &pulp->trampoline_context, trampoline, pulp); pulp->now = now(); return pulp; @@ -286,10 +391,18 @@ static void pulp_schedule(pulp_t *pulp) /* Tick a pulp scheduler - runs fibers until all are idle/sleeping. - * An estimate of how much time may pass before the next tick should occur is stored in next_tick_delay_us. - * If pulp_exit() is called by a fiber, or no more fibers exist, the return value is -1, and next_tick_delay_us is ignored. - * If all fibers are idle, the return value is 0, and next_tick_delay_us is ignored. - * If any fibers are sleeping, the return value is 1, and next_tick_delay_us is useful. + * + * An estimate of how much time may pass before the next tick should occur is + * stored in next_tick_delay_us. + * + * If pulp_exit() is called by a fiber, or no more fibers exist, the return + * value is -1, and next_tick_delay_us is ignored. + * + * If all fibers are idle, the return value is 0, and next_tick_delay_us is + * ignored. + * + * If any fibers are sleeping, the return value is 1, and next_tick_delay_us is + * useful. */ int pulp_tick(pulp_t *pulp, unsigned *next_tick_delay_us) { @@ -297,7 +410,22 @@ int pulp_tick(pulp_t *pulp, unsigned *next_tick_delay_us) expire_alarms(pulp); - swap_context(&pulp->caller_context, &pulp->schedule_context); +#ifdef __WIN32__ + /* XXX: Hopefully it's not too expensive on win32 to become fiberized + * then unfiberized on every tick. I'm doing it this way because + * employing multiple pulp_t instances in the same process is a use + * case I wish to support. There's value in being able to have e.g. + * a set of fibers for one game context which are only ticked when that + * context is active/visible, but otherwise has its state kept around + * in stasis while any number of other pulp_t intances get ticked when + * active. + */ + pulp->caller_context.win32_fiber = ConvertThreadToFiber(NULL); + swap_context(&pulp->caller_context, &pulp->trampoline_context); + ConvertFiberToThread(); +#else + swap_context(&pulp->caller_context, &pulp->trampoline_context); +#endif if (pulp->exited) return -1; @@ -334,10 +462,14 @@ void pulp_run(pulp_t *pulp) /* or fall-through to a sleep on NDEBUG */ case 1: { + /* TODO: get the minimum sleep from the priority queue when implemented, for now we spin a bit. */ +#ifdef __WIN32__ + Sleep(10); /* FIXME, note this function is just for testing anyways. */ +#else struct timespec ts_delay = { 0, delay * 1000 }; - /* TODO: get the minimum sleep from the priority queue when implemented, for now we spin a bit. */ nanosleep(&ts_delay, NULL); +#endif break; } @@ -398,6 +530,10 @@ pulp_fiber_t * pulp_fiber_new(pulp_t *pulp, unsigned delay_ms, thunk_t *thunk) list_move_tail(&fiber->fibers, &pulp->fibers.sleep); } +#ifdef __WIN32__ + fiber->thunk = thunk; +#endif + return fiber; } |