summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pulp.c174
1 files changed, 155 insertions, 19 deletions
diff --git a/src/pulp.c b/src/pulp.c
index 140a5f4..939d913 100644
--- a/src/pulp.c
+++ b/src/pulp.c
@@ -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;
}
© All Rights Reserved