diff --git a/client/Makefile.djgpp b/client/Makefile.djgpp
index e9afa66..6e09864 100644
--- a/client/Makefile.djgpp
+++ b/client/Makefile.djgpp
@@ -32,7 +32,7 @@ BINDIR = bin
## CHANGE THIS ##
# CFLAGS, LDFLAGS, CPPFLAGS, PREFIX can be overriden on CLI
-CFLAGS := $(DEBUG) -I$(SRCDIR) -I$(SRCDIR)/dos -I$(SRCDIR)/gui -I$(SRCDIR)/thirdparty -I$(SRCDIR)/thirdparty/memwatch -I$(SRCDIR)/thirdparty/serial
+CFLAGS := $(DEBUG) -I$(SRCDIR) -I$(SRCDIR)/system -I$(SRCDIR)/dos -I$(SRCDIR)/gui -I$(SRCDIR)/thirdparty -I$(SRCDIR)/thirdparty/memwatch -I$(SRCDIR)/thirdparty/serial
CPPFLAGS :=
LDFLAGS :=
PREFIX := /usr/local
diff --git a/client/build.sh b/client/build.sh
index 3cb3665..12bd7f3 100755
--- a/client/build.sh
+++ b/client/build.sh
@@ -18,7 +18,7 @@
# along with this program. If not, see .
#
-mkdir -p bin obj/dos obj/gui obj/thirdparty/memwatch obj/thirdparty/serial
+mkdir -p bin obj/system obj/dos obj/gui obj/thirdparty/memwatch obj/thirdparty/serial
source /opt/cross/djgpp/setenv
make -f Makefile.djgpp
rm bin/client
diff --git a/client/client.pro b/client/client.pro
index aed7ea2..8feeca2 100644
--- a/client/client.pro
+++ b/client/client.pro
@@ -41,55 +41,57 @@ LINUX_SOURCES = \
INCLUDEPATH += \
$$LINUX_INCLUDES \
$$PWD/src \
+ $$PWD/src/system \
$$PWD/src/gui \
$$PWD/src/thirdparty
HEADERS = \
$$LINUX_HEADERS \
- src/gui/listbox.h \
- src/gui/terminal.h \
- src/gui/updown.h \
- src/stddclmr.h \
+ src/system/memory.h \
+ src/system/keyboard.h \
+ src/system/task.h \
+ src/system/timer.h \
+ src/system/array.h \
+ src/system/log.h \
+ src/system/mouse.h \
+ src/system/vesa.h \
+ src/system/os.h \
src/thirdparty/stb_ds.h \
src/thirdparty/stb_image.h \
src/thirdparty/memwatch/memwatch.h \
- src/gui/memory.h \
+ src/thirdparty/minicoro/minicoro.h \
+ src/gui/listbox.h \
+ src/gui/terminal.h \
+ src/gui/updown.h \
src/gui/button.h \
src/gui/checkbox.h \
src/gui/frame.h \
- src/gui/keyboard.h \
src/gui/label.h \
src/gui/picture.h \
src/gui/radio.h \
src/gui/rect.h \
- src/gui/task.h \
src/gui/textbox.h \
- src/gui/timer.h \
- src/gui/array.h \
src/gui/font.h \
src/gui/desktop.h \
src/gui/gui.h \
src/gui/widget.h \
src/gui/window.h \
- src/gui/log.h \
- src/gui/mouse.h \
- src/gui/vesa.h \
src/gui/image.h \
- src/gui/os.h
+ src/stddclmr.h
SOURCES = \
$$LINUX_SOURCES \
+ src/thirdparty/memwatch/memwatch.c \
+ src/system/memory.c \
+ src/system/array.c \
+ src/system/log.c \
+ src/system/timer.c \
+ src/system/task.c \
src/gui/listbox.c \
src/gui/terminal.c \
src/gui/updown.c \
- src/thirdparty/memwatch/memwatch.c \
- src/gui/memory.c \
- src/gui/array.c \
src/gui/font.c \
src/gui/image.c \
- src/gui/log.c \
- src/gui/timer.c \
- src/gui/task.c \
src/gui/gui.c \
src/gui/desktop.c \
src/gui/widget.c \
diff --git a/client/src/gui/task.c b/client/src/gui/task.c
deleted file mode 100644
index 66fc2de..0000000
--- a/client/src/gui/task.c
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Kangaroo Punch MultiPlayer Game Server Mark II
- * Copyright (C) 2020-2021 Scott Duensing
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-
-// Based on https://brennan.io/2020/05/24/userspace-cooperative-multitasking/
-
-
-// https://sourceforge.net/p/predef/wiki/Home/
-#if !defined(__i386__) && !defined(__i486__) && !defined(__i586__) && !defined(__i686__) && !defined(__x86_64__)
-#error "Only Intel 386 and later processors are supported."
-#endif
-
-#if !defined(__DJGPP__) && !defined(__GNUC__)
-#error "Requires GCC or DJGPP."
-#endif
-
-
-#include
-
-#include "os.h"
-#include "task.h"
-#include "array.h"
-
-
-typedef struct TaskS {
- enum {
- STATE_CREATED,
- STATE_RUNNING,
- STATE_WAITING
- } status;
- uint32_t id;
- jmp_buf buf;
- void (*function)(void *);
- void *data;
- void *stackBottom;
- void *stackTop;
- uint32_t stackSize;
-} TaskT;
-
-enum {
- INIT = 0,
- SCHEDULE,
- EXIT_TASK,
-};
-
-typedef struct PrivateS {
- jmp_buf buf;
- TaskT *current;
- TaskT **taskList;
-} PrivateT;
-
-
-static PrivateT _private;
-static uint16_t _stackSizeInK;
-
-
-static TaskT *taskChoose(void);
-static void taskFree(void);
-static void taskSchedule(void);
-
-
-static TaskT *taskChoose(void) {
- TaskT *task = NULL;
- uint32_t len = arrlenu(_private.taskList);
- uint32_t x;
-
- // Find the next task to run.
- for (x=0; xstatus == STATE_CREATED || task->status == STATE_RUNNING) {
- // Move it to the end of the list.
- arrdel(_private.taskList, x);
- arrput(_private.taskList, task);
- break;
- }
- }
-
- return task;
-}
-
-
-void taskCreate(void (*function)(void *), void *data) {
- static uint32_t id = 1;
- TaskT *task = (TaskT *)malloc(sizeof(TaskT));
-
- task->status = STATE_CREATED;
- task->function = function;
- task->data = data;
- task->id = id++;
- task->stackSize = _stackSizeInK * 1024;
- task->stackBottom = malloc(task->stackSize);
- task->stackTop = task->stackBottom + task->stackSize;
-
- arrput(_private.taskList, task);
-}
-
-
-void taskExit(void) {
- TaskT *task = _private.current;
- uint32_t len = arrlenu(_private.taskList);
- uint32_t x;
-
- // Find our task.
- for (x=0; xstackBottom);
- free(task);
-}
-
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
-void taskRun(void) {
- // This is the exit path for the scheduler!
- switch (setjmp(_private.buf)) {
- case EXIT_TASK:
- taskFree();
-
- case INIT:
- case SCHEDULE:
- taskSchedule();
- // If we get here, there's nothing else to do so exit
- return;
-
- default:
- fprintf(stderr, "Task scheduler error!\n");
- return;
- }
-}
-#pragma GCC diagnostic pop
-
-
-static void taskSchedule(void) {
- TaskT *next = taskChoose();
-
- if (!next) return;
-
- _private.current = next;
-
- if (next->status == STATE_CREATED) {
- // This task has not been started yet.
- // Assign a new stack pointer, run the task, and exit it at the end.
- register void *top = next->stackTop;
-#ifdef __x86_64__
- // 64 bit.
- asm volatile(
- "mov %[rs], %%rsp \n"
- : [ rs ] "+r" (top) ::
- );
-#else
- // 32 bit.
- asm volatile(
- "mov %[rs], %%esp \n"
- : [ rs ] "+r" (top) ::
- );
-#endif
- // Run the task.
- next->status = STATE_RUNNING;
- next->function(next->data);
-
- // The stack pointer should be back where we set it. Returning would be a very, very bad idea. Let's instead exit.
- taskExit();
- } else {
- longjmp(next->buf, 1);
- }
-
- // Execution doesn't get to here.
-}
-
-
-void taskShutdown(void) {
- // Nothing yet.
-}
-
-
-void taskStartup(uint16_t stackInK) {
- _stackSizeInK = stackInK;
- _private.current = NULL;
- _private.taskList = NULL;
-}
-
-
-void taskYield(void) {
- if (setjmp(_private.current->buf)) {
- return;
- } else {
- longjmp(_private.buf, SCHEDULE);
- }
-}
diff --git a/client/src/gui/terminal.c b/client/src/gui/terminal.c
index c88600a..b2702a0 100644
--- a/client/src/gui/terminal.c
+++ b/client/src/gui/terminal.c
@@ -25,6 +25,9 @@
#include "terminal.h"
+#define termWrite(...) logWrite(__VA_ARGS__)
+
+
#define TERMINAL_CELL_GET_FLAG(t,x,y,f) (((t)->cells[(x)][(y)].flags & (1 << (f))) != 0)
#define TERMINAL_CELL_SET_FLAG(t,x,y,f) ((t)->cells[(x)][(y)].flags |= (1 << (f)))
#define TERMINAL_CELL_CLEAR_FLAG(t,x,y,f) ((t)->cells[(x)][(y)].flags &= (~(1 << (f))))
@@ -32,7 +35,9 @@
static int16_t terminalConvertToInt(char *number, int16_t defaultValue);
static void terminalDel(WidgetT **widget);
+static void terminalFocusEvent(WidgetT *widget, uint8_t focused);
static void terminalMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event);
+static void terminalKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
static void terminalPaint(WidgetT *widget, RectT pos);
static void terminalSequenceReset(TerminalT *terminal);
@@ -94,6 +99,24 @@ static void terminalDel(WidgetT **widget) {
}
+static void terminalFocusEvent(WidgetT *widget, uint8_t focused) {
+ TerminalT *t = (TerminalT *)widget;
+ uint16_t x;
+ uint16_t y;
+
+ (void)focused;
+
+ // When focus changes, we need to mark ALL the cells dirty so they get redrawn when the window is redrawn.
+ for (y=0; yrows; y++) {
+ for (x=0; xcols; x++) {
+ TERMINAL_CELL_SET_FLAG(t, x, y, TERMINAL_FLAG_DIRTY);
+ }
+ }
+
+ GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
+}
+
+
void terminalForegroundSet(TerminalT *terminal, uint8_t color) {
terminal->foreground = color;
terminal->bold = (color > 7);
@@ -105,26 +128,28 @@ WidgetT *terminalInit(WidgetT *widget, uint16_t cols, uint16_t rows) {
uint16_t x;
uint16_t y;
- t->base.delMethod = terminalDel;
- t->base.paintMethod = terminalPaint;
- t->base.mouseEventMethod = terminalMouseEvent;
- t->font = _guiFont8; // Default font. Also see terminalNew.
- t->cols = cols;
- t->rows = rows;
- t->cursorX = 1;
- t->cursorY = 1;
- t->saveX = 1;
- t->saveY = 1;
- t->bold = 0;
- t->blink = 0;
- t->reverse = 0;
- t->background = TERMINAL_COLOR_BLACK;
- t->foreground = TERMINAL_COLOR_LIGHT_GRAY;
- t->escFound = 0;
- t->inEscape = 0;
- t->numberIndex = 0;
- t->number[0] = 0;
- t->parameters = NULL;
+ t->base.delMethod = terminalDel;
+ t->base.focusMethod = terminalFocusEvent;
+ t->base.paintMethod = terminalPaint;
+ t->base.keyboardEventMethod = terminalKeyboardEvent;
+ t->base.mouseEventMethod = terminalMouseEvent;
+ t->font = _guiFont8; // Default font. Also see terminalNew.
+ t->cols = cols;
+ t->rows = rows;
+ t->cursorX = 1;
+ t->cursorY = 1;
+ t->saveX = 1;
+ t->saveY = 1;
+ t->bold = 0;
+ t->blink = 0;
+ t->reverse = 0;
+ t->background = TERMINAL_COLOR_BLACK;
+ t->foreground = TERMINAL_COLOR_LIGHT_GRAY;
+ t->escFound = 0;
+ t->inEscape = 0;
+ t->numberIndex = 0;
+ t->number[0] = 0;
+ t->parameters = NULL;
// Allocate space for column data.
t->cells = (CellT **)malloc(sizeof(CellT *) * cols);
@@ -174,6 +199,11 @@ WidgetT *terminalInit(WidgetT *widget, uint16_t cols, uint16_t rows) {
}
+static void terminalKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {
+
+}
+
+
static void terminalMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) {
TerminalT *t = (TerminalT *)widget;
@@ -273,6 +303,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Is this a sequence that is ESC and then not [ ?
if (terminal->escFound && !terminal->inEscape) {
+ termWrite("Unexpected character: '%c' %d\n", c, c);
terminalSequenceReset(terminal);
return;
}
@@ -282,6 +313,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
case '[':
case 27:
+ termWrite("Escape found inside sequence\n");
// ESC sequence inside sequence. Invalid ANSIBBS.
//***TODO*** Can stick custom sequences here.
break;
@@ -296,6 +328,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor up.
case 'A':
y = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor up %d\n", y);
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY - y);
terminalSequenceReset(terminal);
break;
@@ -303,6 +336,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor down.
case 'B':
y = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor down %d\n", y);
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY + y);
terminalSequenceReset(terminal);
break;
@@ -310,6 +344,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor forward.
case 'C':
x = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor forward %d\n", x);
terminalCursorMove(terminal, terminal->cursorX + x, terminal->cursorY);
terminalSequenceReset(terminal);
break;
@@ -317,6 +352,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor backward.
case 'D':
x = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor backward %d\n", x);
terminalCursorMove(terminal, terminal->cursorX - x, terminal->cursorY);
terminalSequenceReset(terminal);
break;
@@ -324,6 +360,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor line down.
case 'E':
y = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor down %d lines\n", y);
terminalCursorMove(terminal, 1, terminal->cursorY + y);
//***TODO*** This should allow scrolling.
terminalSequenceReset(terminal);
@@ -332,6 +369,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor line up.
case 'F':
y = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor up %d lines\n", y);
terminalCursorMove(terminal, 1, terminal->cursorY - y);
//***TODO*** This should allow scrolling.
terminalSequenceReset(terminal);
@@ -340,6 +378,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor horizontal absolute.
case 'G':
x = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor horizontal absolute %d\n", x);
terminalCursorMove(terminal, x, terminal->cursorY);
terminalSequenceReset(terminal);
break;
@@ -351,18 +390,21 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Cursor vertical absolute. Kinda. Moves X to 1.
case 0:
y = terminalConvertToInt(terminal->number, 1);
+ termWrite("Moving cursor kinda vertictal absolute %d\n", y);
terminalCursorMove(terminal, 1, y);
break;
// Absolute position.
case 1:
- x = terminalConvertToInt(terminal->parameters[0], 1);
- y = terminalConvertToInt(terminal->number, 1);
+ x = terminalConvertToInt(terminal->number, 1);
+ y = terminalConvertToInt(terminal->parameters[0], 1);
+ termWrite("Moving cursor absolute %d %d\n", x, y);
terminalCursorMove(terminal, x, y);
break;
// Invalid nonsense.
default:
+ termWrite("Unknown cursor move: '%c' %d\n", c, c);
break;
}
terminalSequenceReset(terminal);
@@ -371,8 +413,12 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Clear display.
case 'J':
if (arrlen(terminal->parameters) == 0 && terminalConvertToInt(terminal->number, 1) == 2) {
+ termWrite("Clear display\n");
terminalScreenClear(terminal);
terminalCursorMove(terminal, 1, 1);
+ } else {
+ //***TODO*** Unimplemented screen clear.
+ termWrite("Unimplemented clear display\n");
}
terminalSequenceReset(terminal);
break;
@@ -381,6 +427,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
case 'K':
x = terminalConvertToInt(terminal->number, 0);
if (x == 0) {
+ termWrite("Clear to end of line\n");
for (y=terminal->cursorX-1; ycols; y++) {
terminal->cells[y][terminal->cursorY - 1].character = ' ';
TERMINAL_CELL_SET_FLAG(terminal, y, terminal->cursorY - 1, TERMINAL_FLAG_DIRTY);
@@ -388,6 +435,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
}
} else {
//***TODO*** Unimplemented line clear.
+ termWrite("Unimplemented line clear\n");
}
terminalSequenceReset(terminal);
break;
@@ -395,35 +443,41 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Insert lines.
case 'L':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented insert lines\n");
terminalSequenceReset(terminal);
break;
// Delete lines.
case 'M':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented delete lines\n");
terminalSequenceReset(terminal);
break;
// Delete characters.
case 'P':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented delete characters\n");
terminalSequenceReset(terminal);
break;
// Scroll up.
case 'S':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented scroll up\n");
terminalSequenceReset(terminal);
break;
// Scroll down.
case 'T':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented scroll down\n");
terminalSequenceReset(terminal);
break;
// Clear screen with normal attribute.
case 'U':
+ termWrite("Clear screen with normal attribute\n");
x = terminal->background;
terminal->background = TERMINAL_COLOR_BLACK;
terminalScreenClear(terminal);
@@ -435,6 +489,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Back-TAB.
case 'Z':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented back-TAB\n");
terminalSequenceReset(terminal);
break;
@@ -446,6 +501,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
switch (x) {
// Reset.
case 0:
+ termWrite("Attribute reset\n");
terminal->bold = 0;
terminal->blink = 0;
terminal->reverse = 0;
@@ -455,54 +511,71 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Bold.
case 1:
+ termWrite("Bold\n");
terminal->bold = 1;
break;
// Blink slow.
case 5:
+ termWrite("Blink slow\n");
terminal->blink = TERMINAL_FLAG_BLINK_SLOW;
break;
// Blink fast.
case 6:
+ termWrite("Blink fast\n");
terminal->blink = TERMINAL_FLAG_BLINK_FAST;
break;
// Reverse.
case 7:
if (!terminal->reverse) {
+ termWrite("Reverse\n");
x = terminal->foreground;
terminal->foreground = terminal->background;
terminal->background = x;
terminal->reverse = 1;
+ } else {
+ termWrite("Already reversed\n");
}
break;
// Normal intensity. (22 is the actual code, unsure exactly what 21 is.)
case 21:
case 22:
+ termWrite("Normal intensity\n");
terminal->bold = 0;
break;
// Steady.
case 25:
+ termWrite("Steady\n");
terminal->blink = 0;
break;
- // Un-reverse?
+ // Normal
case 27:
if (terminal->reverse) {
+ termWrite("Normal\n");
x = terminal->foreground;
terminal->foreground = terminal->background;
terminal->background = x;
terminal->reverse = 0;
+ } else {
+ termWrite("Already normal\n");
}
break;
// Color change.
default:
- if (x > 29 && x < 38) terminal->foreground = x - 30;
- if (x > 39 && x < 48) terminal->background = x - 40;
+ if (x > 29 && x < 38) {
+ terminal->foreground = x - 30;
+ termWrite("Foreground %d\n", terminal->foreground);
+ }
+ if (x > 39 && x < 48) {
+ terminal->background = x - 40;
+ termWrite("Background %d\n", terminal->background);
+ }
break;
} // switch (x)
@@ -513,6 +586,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Define scroll region.
case 'r':
//***TODO*** Unimplemented.
+ termWrite("Unimplemented define scroll region\n");
terminalSequenceReset(terminal);
break;
@@ -520,11 +594,13 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
case 's':
terminal->saveX = terminal->cursorX;
terminal->saveY = terminal->cursorY;
+ termWrite("Cursor save %d %d\n", terminal->saveX, terminal->saveX);
terminalSequenceReset(terminal);
break;
// Cursor restore.
case 'u':
+ termWrite("Cursor restore %d %d\n", terminal->saveX, terminal->saveX);
terminalCursorMove(terminal, terminal->saveX, terminal->saveY);
terminalSequenceReset(terminal);
break;
@@ -537,6 +613,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
terminal->number[terminal->numberIndex] = 0;
} else {
// Unknown sequence.
+ termWrite("Unknown sequence: '%c' %d\n", c, c);
terminalSequenceReset(terminal);
}
@@ -549,31 +626,37 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Bell.
case 7:
//***TODO*** Unimplemented.
+ termWrite("Unimplemented bell\n");
break;
// Backspace.
case 8:
case 128:
//***TODO*** Unimplemented.
+ termWrite("Unimplemented backspace\n");
break;
// TAB
case 9:
//***TODO*** Unimplemented.
+ termWrite("Unimplemented TAB\n");
break;
// Line feed.
case 10:
terminal->cursorY++;
+ termWrite("Line feed\n");
if (terminal->cursorY > terminal->rows) {
terminal->cursorY--;
//***TODO*** Scroll.
+ termWrite("Unimplemented line feed scroll\n");
}
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY);
break;
- // Clear screen.
+ // Clear screen (form feed).
case 12:
+ termWrite("Form feed\n");
x = terminal->background;
terminal->background = TERMINAL_COLOR_BLACK;
terminalScreenClear(terminal);
@@ -583,6 +666,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
// Carrage return.
case 13:
+ termWrite("Carrage return\n");
terminalCursorMove(terminal, 1, terminal->cursorY);
break;
@@ -605,6 +689,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) {
if (terminal->cursorY > terminal->rows) {
terminal->cursorY--;
//***TODO*** Scroll.
+ termWrite("Unimplemented render scroll\n");
}
}
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY);
@@ -621,11 +706,12 @@ void terminalScreenClear(TerminalT *terminal) {
uint16_t y;
// Does not move cursor.
- // Should this clear the blink flags?
for (y=0; yrows; y++) {
for (x=0; xcols; x++) {
terminal->cells[x][y].character = ' ';
+ TERMINAL_CELL_CLEAR_FLAG(terminal, x, y, TERMINAL_FLAG_BLINK_SLOW);
+ TERMINAL_CELL_CLEAR_FLAG(terminal, x, y, TERMINAL_FLAG_BLINK_FAST);
TERMINAL_CELL_SET_FLAG(terminal, x, y, TERMINAL_FLAG_DIRTY);
}
}
diff --git a/client/src/main.c b/client/src/main.c
index c9f7458..2effdd3 100644
--- a/client/src/main.c
+++ b/client/src/main.c
@@ -62,6 +62,10 @@
#include "terminal.h"
+static TerminalT *t1 = NULL;
+static uint8_t lastKey = 0;
+
+
void buttonClick(WidgetT *widget) {
logWrite("'%s' was clicked.\n", ((ButtonT *)widget)->title);
}
@@ -107,6 +111,7 @@ void mainLoop(void *data) {
mouse = mouseRead();
if (keyHit()) {
key = keyASCII();
+ lastKey = key; //***DEBUG***
guiProcessKeyboard(keyASCII(), keyExtended(), keyScanCode(), keyShift(), keyControl(), keyAlt());
//logWrite("Key '%d' Extended '%d' Scancode '%d' Shift '%d' Control '%d' Alt '%d'\n", key, keyExtended(), keyScanCode(), keyShift(), keyControl(), keyAlt());
} else {
@@ -132,6 +137,40 @@ void mainLoop(void *data) {
}
+void terminalTest(void *data) {
+ FILE *in = NULL;
+ char *buffer = NULL;
+ uint16_t length = 0;
+ uint16_t x = 0;
+
+ (void)data;
+
+ // Load ANSI file for terminal test.
+ in = fopen("kanga.ans", "rt");
+ fseek(in, 0, SEEK_END);
+ length = ftell(in);
+ fseek(in, 0, SEEK_SET);
+ buffer = (char *)malloc(length + 1);
+ fread(buffer, 1, length, in);
+ fclose(in);
+ buffer[length] = 0;
+
+ logWrite("ANSI loaded.\n");
+
+ while (x < length) {
+ if (lastKey == 27) break;
+// if (lastKey == ' ') {
+ lastKey = 0;
+ terminalPrintChar(t1, buffer[x]);
+ x++;
+// }
+ taskYield();
+ }
+
+ free(buffer);
+}
+
+
void test(void *data) {
DesktopT *desktop = (DesktopT *)guiRootGet();
WindowT *w1 = NULL;
@@ -153,10 +192,6 @@ void test(void *data) {
TextboxT *tb2 = NULL;
UpdownT *u1 = NULL;
ListboxT *lb1 = NULL;
- TerminalT *t1 = NULL;
- FILE *in = NULL;
- char *buffer = NULL;
- uint16_t length = 0;
(void)data;
@@ -224,16 +259,8 @@ void test(void *data) {
// Window 4 - Terminal
t1 = terminalNew(0, 0, 80, 24);
guiAttach(W(w4), W(t1));
- in = fopen("kanga.ans", "rt");
- fseek(in, 0, SEEK_END);
- length = ftell(in);
- fseek(in, 0, SEEK_SET);
- buffer = (char *)malloc(length + 1);
- fread(buffer, 1, length, in);
- fclose(in);
- buffer[length] = 0;
- terminalPrint(t1, buffer);
- free(buffer);
+
+ taskCreate(terminalTest, NULL);
}
@@ -252,6 +279,7 @@ int main(int argc, char *argv[]) {
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
printf("Kangaroo Punch MultiPlayer DOS Game Client Mark II\n");
printf("Copyright (C) 2020-2021 Scott Duensing scott@kangaroopunch.com\n\n");
+ fflush(stdout);
// Find last portion of filename.
while (x > 0) {
@@ -271,6 +299,7 @@ int main(int argc, char *argv[]) {
// Command line needs to have the desired resolution and color depth on it.
if (argc != 4) {
vbeShowInfo();
+ fflush(stdout);
memoryShutdown();
return 0;
}
@@ -280,6 +309,7 @@ int main(int argc, char *argv[]) {
// Do we have the video mode they asked for?
if (vbeStartup(xResolution, yResolution, colorDepth)) {
+ fflush(stdout);
memoryShutdown();
return 1;
}
@@ -287,7 +317,7 @@ int main(int argc, char *argv[]) {
mouseStartup();
timerStartup();
guiStartup();
- taskStartup(64);
+ taskStartup();
taskCreate(test, NULL);
taskCreate(mainLoop, NULL);
diff --git a/client/src/gui/array.c b/client/src/system/array.c
similarity index 100%
rename from client/src/gui/array.c
rename to client/src/system/array.c
diff --git a/client/src/gui/array.h b/client/src/system/array.h
similarity index 100%
rename from client/src/gui/array.h
rename to client/src/system/array.h
diff --git a/client/src/gui/keyboard.h b/client/src/system/keyboard.h
similarity index 100%
rename from client/src/gui/keyboard.h
rename to client/src/system/keyboard.h
diff --git a/client/src/gui/log.c b/client/src/system/log.c
similarity index 100%
rename from client/src/gui/log.c
rename to client/src/system/log.c
diff --git a/client/src/gui/log.h b/client/src/system/log.h
similarity index 100%
rename from client/src/gui/log.h
rename to client/src/system/log.h
diff --git a/client/src/gui/memory.c b/client/src/system/memory.c
similarity index 100%
rename from client/src/gui/memory.c
rename to client/src/system/memory.c
diff --git a/client/src/gui/memory.h b/client/src/system/memory.h
similarity index 100%
rename from client/src/gui/memory.h
rename to client/src/system/memory.h
diff --git a/client/src/gui/mouse.h b/client/src/system/mouse.h
similarity index 100%
rename from client/src/gui/mouse.h
rename to client/src/system/mouse.h
diff --git a/client/src/gui/os.h b/client/src/system/os.h
similarity index 100%
rename from client/src/gui/os.h
rename to client/src/system/os.h
diff --git a/client/src/system/task.c b/client/src/system/task.c
new file mode 100644
index 0000000..e8d42b6
--- /dev/null
+++ b/client/src/system/task.c
@@ -0,0 +1,115 @@
+/*
+ * Kangaroo Punch MultiPlayer Game Server Mark II
+ * Copyright (C) 2020-2021 Scott Duensing
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+#define MINICORO_IMPL
+#define MCO_USE_ASM
+#include "minicoro/minicoro.h"
+
+
+#include "array.h"
+#include "task.h"
+
+
+typedef struct TaskS {
+ mco_coro *coroutine;
+ void (*function)(void *);
+ void *data;
+} TaskT;
+
+
+static TaskT **_taskList = NULL;
+
+
+static void taskHandle(mco_coro* coroutine);
+
+
+uint8_t taskCreate(void (*function)(void *), void *data) {
+ mco_desc desc = mco_desc_init(taskHandle, 0);
+ mco_result res = 0;
+ TaskT *task = (TaskT *)malloc(sizeof(TaskT));
+
+ if (task) {
+ task->function = function;
+ task->data = data;
+ desc.user_data = task;
+ res = mco_create(&task->coroutine, &desc);
+ if (res != MCO_SUCCESS) {
+ mco_destroy(task->coroutine);
+ free(task);
+ return 1; // Failed
+ }
+ arrput(_taskList, task);
+ }
+
+ return 1; // Failed
+}
+
+
+static void taskHandle(mco_coro* coroutine) {
+ TaskT *task = (TaskT *)mco_get_user_data(coroutine);
+ task->function(task->data);
+}
+
+
+void taskRun(void) {
+ uint16_t taskIndex = 0;
+
+ // Run until there are no more tasks.
+ while (arrlen(_taskList) > 0) {
+
+ // Run each task in order.
+ taskIndex = 0;
+ while (taskIndex < arrlen(_taskList)) {
+
+ // Run task.
+ mco_resume(_taskList[taskIndex]->coroutine);
+ // Did it finish?
+ if (mco_status(_taskList[taskIndex]->coroutine) == MCO_DEAD) {
+ // Task ended. Remove it.
+ mco_destroy(_taskList[taskIndex]->coroutine);
+ free(_taskList[taskIndex]);
+ arrdel(_taskList, taskIndex);
+ } else {
+ // Next task.
+ taskIndex++;
+ }
+
+ } // while each task
+
+ } // while tasks exist
+
+ //arrfree(_taskList);
+ _taskList = NULL;
+}
+
+
+void taskShutdown(void) {
+ // Nada
+}
+
+
+void taskStartup(void) {
+ // Nada
+}
+
+
+void taskYield(void) {
+ mco_yield(mco_running());
+}
diff --git a/client/src/gui/task.h b/client/src/system/task.h
similarity index 80%
rename from client/src/gui/task.h
rename to client/src/system/task.h
index ed1052c..543fab2 100644
--- a/client/src/gui/task.h
+++ b/client/src/system/task.h
@@ -22,15 +22,14 @@
#define TASK_H
-#include "os.h"
+#include
-void taskCreate(void (*function)(void *), void *data);
-void taskExit(void);
-void taskRun(void);
-void taskShutdown(void);
-void taskStartup(uint16_t stackInK);
-void taskYield(void);
+uint8_t taskCreate(void (*function)(void *), void *data);
+void taskRun(void);
+void taskShutdown(void);
+void taskStartup(void);
+void taskYield(void);
#endif // TASK_H
diff --git a/client/src/gui/timer.c b/client/src/system/timer.c
similarity index 100%
rename from client/src/gui/timer.c
rename to client/src/system/timer.c
diff --git a/client/src/gui/timer.h b/client/src/system/timer.h
similarity index 100%
rename from client/src/gui/timer.h
rename to client/src/system/timer.h
diff --git a/client/src/gui/vesa.h b/client/src/system/vesa.h
similarity index 100%
rename from client/src/gui/vesa.h
rename to client/src/system/vesa.h
diff --git a/client/src/thirdparty/minicoro/LICENSE b/client/src/thirdparty/minicoro/LICENSE
new file mode 100644
index 0000000..1e5d195
--- /dev/null
+++ b/client/src/thirdparty/minicoro/LICENSE
@@ -0,0 +1,47 @@
+This software is available as a choice of the following licenses. Choose
+whichever you prefer.
+
+===============================================================================
+ALTERNATIVE 1 - Public Domain (www.unlicense.org)
+===============================================================================
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
+
+===============================================================================
+ALTERNATIVE 2 - MIT No Attribution
+===============================================================================
+Copyright (c) 2021 Eduardo Bart (https://github.com/edubart/minicoro)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/client/src/thirdparty/minicoro/README.md b/client/src/thirdparty/minicoro/README.md
new file mode 100644
index 0000000..f5093aa
--- /dev/null
+++ b/client/src/thirdparty/minicoro/README.md
@@ -0,0 +1,350 @@
+# minicoro
+
+Minicoro is single-file library for using asymmetric coroutines in C.
+The API is inspired by [Lua coroutines](https://www.lua.org/manual/5.4/manual.html#6.2) but with C use in mind.
+
+The project is being developed mainly to be a coroutine backend
+for the [Nelua](https://github.com/edubart/nelua-lang) programming language.
+
+The library assembly implementation is inspired by [Lua Coco](https://coco.luajit.org/index.html) by Mike Pall.
+
+# Features
+
+- Stackful asymmetric coroutines.
+- Supports nesting coroutines (resuming a coroutine from another coroutine).
+- Supports custom allocators.
+- Storage system to allow passing values between yield and resume.
+- Customizable stack size.
+- Coroutine API design inspired by Lua with C use in mind.
+- Yield across any C function.
+- Made to work in multithread applications.
+- Cross platform.
+- Minimal, self contained and no external dependencies.
+- Readable sources and documented.
+- Implemented via assembly, ucontext or fibers.
+- Lightweight and very efficient.
+- Works in most C89 compilers.
+- Error prone API, returning proper error codes on misuse.
+- Support running with Valgrind, ASan (AddressSanitizer) and TSan (ThreadSanitizer).
+
+# Supported Platforms
+
+Most platforms are supported through different methods:
+
+| Platform | Assembly Method | Fallback Method |
+|--------------|------------------|-------------------|
+| Android | ARM/ARM64 | N/A |
+| iOS | ARM/ARM64 | N/A |
+| Windows | x86_64 | Windows fibers |
+| Linux | x86_64/i686 | ucontext |
+| Mac OS X | x86_64 | ucontext |
+| WebAssembly | N/A | Emscripten fibers |
+| Raspberry Pi | ARM | ucontext |
+| RISC-V | rv64/rv32 | ucontext |
+
+The assembly method is used by default if supported by the compiler and CPU,
+otherwise ucontext or fiber method is used as a fallback.
+
+The assembly method is very efficient, it just take a few cycles
+to create, resume, yield or destroy a coroutine.
+
+# Caveats
+
+- Don't use coroutines with C++ exceptions, this is not supported.
+- When using C++ RAII (i.e. destructors) you must resume the coroutine until it dies to properly execute all destructors.
+- To use in multithread applications, you must compile with C compiler that supports `thread_local` qualifier.
+- Some unsupported sanitizers for C may trigger false warnings when using coroutines.
+- The `mco_coro` object is not thread safe, you should lock each coroutine into a thread.
+- Stack space is fixed, it cannot grow. By default it has about 56KB of space, this can be changed on coroutine creation.
+- Take care to not cause stack overflows (run out of stack space), otherwise your program may crash or not, the behavior is undefined.
+- On WebAssembly you must compile with emscripten flag `-s ASYNCIFY=1`.
+
+# Introduction
+
+A coroutine represents an independent "green" thread of execution.
+Unlike threads in multithread systems, however,
+a coroutine only suspends its execution by explicitly calling a yield function.
+
+You create a coroutine by calling `mco_create`.
+Its sole argument is a `mco_desc` structure with a description for the coroutine.
+The `mco_create` function only creates a new coroutine and returns a handle to it, it does not start the coroutine.
+
+You execute a coroutine by calling `mco_resume`.
+When calling a resume function the coroutine starts its execution by calling its body function.
+After the coroutine starts running, it runs until it terminates or yields.
+
+A coroutine yields by calling `mco_yield`.
+When a coroutine yields, the corresponding resume returns immediately,
+even if the yield happens inside nested function calls (that is, not in the main function).
+The next time you resume the same coroutine, it continues its execution from the point where it yielded.
+
+To associate a persistent value with the coroutine,
+you can optionally set `user_data` on its creation and later retrieve with `mco_get_user_data`.
+
+To pass values between resume and yield,
+you can optionally use `mco_push` and `mco_pop` APIs,
+they are intended to pass temporary values using a LIFO (Last In, First Out) style buffer.
+The storage system can also be used to send and receive initial values on coroutine creation or before it finishes.
+
+# Usage
+
+To use minicoro, do the following in one .c file:
+
+```c
+#define MINICORO_IMPL
+#include "minicoro.h"
+```
+
+You can do `#include "minicoro.h"` in other parts of the program just like any other header.
+
+## Minimal Example
+
+The following simple example demonstrates on how to use the library:
+
+```c
+#define MINICORO_IMPL
+#include "minicoro.h"
+#include
+
+// Coroutine entry function.
+void coro_entry(mco_coro* co) {
+ printf("coroutine 1\n");
+ mco_yield(co);
+ printf("coroutine 2\n");
+}
+
+int main() {
+ // First initialize a `desc` object through `mco_desc_init`.
+ mco_desc desc = mco_desc_init(coro_entry, 0);
+ // Configure `desc` fields when needed (e.g. customize user_data or allocation functions).
+ desc.user_data = NULL;
+ // Call `mco_create` with the output coroutine pointer and `desc` pointer.
+ mco_coro* co;
+ mco_result res = mco_create(&co, &desc);
+ assert(res == MCO_SUCCESS);
+ // The coroutine should be now in suspended state.
+ assert(mco_status(co) == MCO_SUSPENDED);
+ // Call `mco_resume` to start for the first time, switching to its context.
+ res = mco_resume(co); // Should print "coroutine 1".
+ assert(res == MCO_SUCCESS);
+ // We get back from coroutine context in suspended state (because it's unfinished).
+ assert(mco_status(co) == MCO_SUSPENDED);
+ // Call `mco_resume` to resume for a second time.
+ res = mco_resume(co); // Should print "coroutine 2".
+ assert(res == MCO_SUCCESS);
+ // The coroutine finished and should be now dead.
+ assert(mco_status(co) == MCO_DEAD);
+ // Call `mco_destroy` to destroy the coroutine.
+ res = mco_destroy(co);
+ assert(res == MCO_SUCCESS);
+ return 0;
+}
+```
+
+_NOTE_: In case you don't want to use the minicoro allocator system you should
+allocate a coroutine object yourself using `mco_desc.coro_size` and call `mco_init`,
+then later to destroy call `mco_uninit` and deallocate it.
+
+## Yielding from anywhere
+
+You can yield the current running coroutine from anywhere
+without having to pass `mco_coro` pointers around,
+to this just use `mco_yield(mco_running())`.
+
+## Passing data between yield and resume
+
+The library has the storage interface to assist passing data between yield and resume.
+It's usage is straightforward,
+use `mco_push` to send data before a `mco_resume` or `mco_yield`,
+then later use `mco_pop` after a `mco_resume` or `mco_yield` to receive data.
+Take care to not mismatch a push and pop, otherwise these functions will return
+an error.
+
+## Error handling
+
+The library return error codes in most of its API in case of misuse or system error,
+the user is encouraged to handle them properly.
+
+## Library customization
+
+The following can be defined to change the library behavior:
+
+- `MCO_API` - Public API qualifier. Default is `extern`.
+- `MCO_MIN_STACK_SIZE` - Minimum stack size when creating a coroutine. Default is 32768.
+- `MCO_DEFAULT_STORAGE_SIZE` - Size of coroutine storage buffer. Default is 1024.
+- `MCO_DEFAULT_STACK_SIZE` - Default stack size when creating a coroutine. Default is 57344.
+- `MCO_MALLOC` - Default allocation function. Default is `malloc`.
+- `MCO_FREE` - Default deallocation function. Default is `free`.
+- `MCO_DEBUG` - Enable debug mode, logging any runtime error to stdout. Defined automatically unless `NDEBUG` or `MCO_NO_DEBUG` is defined.
+- `MCO_NO_DEBUG` - Disable debug mode.
+- `MCO_NO_MULTITHREAD` - Disable multithread usage. Multithread is supported when `thread_local` is supported.
+- `MCO_NO_DEFAULT_ALLOCATORS` - Disable the default allocator using `MCO_MALLOC` and `MCO_FREE`.
+- `MCO_ZERO_MEMORY` - Zero memory of stack for new coroutines and when poping storage, intended for garbage collected environments.
+- `MCO_USE_ASM` - Force use of assembly context switch implementation.
+- `MCO_USE_UCONTEXT` - Force use of ucontext context switch implementation.
+- `MCO_USE_FIBERS` - Force use of fibers context switch implementation.
+- `MCO_USE_VALGRIND` - Define if you want run with valgrind to fix accessing memory errors.
+
+# Benchmarks
+
+The coroutine library was benchmarked for x86_64 counting CPU cycles
+for context switch (triggered in resume or yield) and initialization.
+
+| CPU Arch | OS | Method | Context switch | Initialize | Uninitialize |
+|----------|----------|----------|----------------|--------------|--------------|
+| x86_64 | Linux | assembly | 9 cycles | 31 cycles | 14 cycles |
+| x86_64 | Linux | ucontext | 352 cycles | 383 cycles | 14 cycles |
+| x86_64 | Windows | fibers | 69 cycles | 10564 cycles | 11167 cycles |
+| x86_64 | Windows | assembly | 33 cycles | 74 cycles | 14 cycles |
+
+_NOTE_: Tested on Intel Core i7-8750H CPU @ 2.20GHz with pre allocated coroutines.
+
+# Cheatsheet
+
+Here is a list of all library functions for quick reference:
+
+```c
+/* Structure used to initialize a coroutine. */
+typedef struct mco_desc {
+ void (*func)(mco_coro* co); /* Entry point function for the coroutine. */
+ void* user_data; /* Coroutine user data, can be get with `mco_get_user_data`. */
+ /* Custom allocation interface. */
+ void* (*malloc_cb)(size_t size, void* allocator_data); /* Custom allocation function. */
+ void (*free_cb)(void* ptr, void* allocator_data); /* Custom deallocation function. */
+ void* allocator_data; /* User data pointer passed to `malloc`/`free` allocation functions. */
+ size_t storage_size; /* Coroutine storage size, to be used with the storage APIs. */
+ /* These must be initialized only through `mco_init_desc`. */
+ size_t coro_size; /* Coroutine structure size. */
+ size_t stack_size; /* Coroutine stack size. */
+} mco_desc;
+
+/* Coroutine functions. */
+mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size); /* Initialize description of a coroutine. When stack size is 0 then MCO_DEFAULT_STACK_SIZE is used. */
+mco_result mco_init(mco_coro* co, mco_desc* desc); /* Initialize the coroutine. */
+mco_result mco_uninit(mco_coro* co); /* Uninitialize the coroutine, may fail if it's not dead or suspended. */
+mco_result mco_create(mco_coro** out_co, mco_desc* desc); /* Allocates and initializes a new coroutine. */
+mco_result mco_destroy(mco_coro* co); /* Uninitialize and deallocate the coroutine, may fail if it's not dead or suspended. */
+mco_result mco_resume(mco_coro* co); /* Starts or continues the execution of the coroutine. */
+mco_result mco_yield(mco_coro* co); /* Suspends the execution of a coroutine. */
+mco_state mco_status(mco_coro* co); /* Returns the status of the coroutine. */
+void* mco_get_user_data(mco_coro* co); /* Get coroutine user data supplied on coroutine creation. */
+
+/* Storage interface functions, used to pass values between yield and resume. */
+mco_result mco_push(mco_coro* co, const void* src, size_t len); /* Push bytes to the coroutine storage. Use to send values between yield and resume. */
+mco_result mco_pop(mco_coro* co, void* dest, size_t len); /* Pop bytes from the coroutine storage. Use to get values between yield and resume. */
+mco_result mco_peek(mco_coro* co, void* dest, size_t len); /* Like `mco_pop` but it does not consumes the storage. */
+size_t mco_get_bytes_stored(mco_coro* co); /* Get the available bytes that can be retrieved with a `mco_pop`. */
+size_t mco_get_storage_size(mco_coro* co); /* Get the total storage size. */
+
+/* Misc functions. */
+mco_coro* mco_running(void); /* Returns the running coroutine for the current thread. */
+const char* mco_result_description(mco_result res); /* Get the description of a result. */
+```
+
+# Complete Example
+
+The following is a more complete example, generating Fibonacci numbers:
+
+```c
+#define MINICORO_IMPL
+#include "minicoro.h"
+#include
+
+static void fail(const char* message, mco_result res) {
+ printf("%s: %s\n", message, mco_result_description(res));
+ exit(-1);
+}
+
+static void fibonacci_coro(mco_coro* co) {
+ unsigned long m = 1;
+ unsigned long n = 1;
+
+ /* Retrieve max value. */
+ unsigned long max;
+ mco_result res = mco_pop(co, &max, sizeof(max));
+ if(res != MCO_SUCCESS)
+ fail("Failed to retrieve coroutine storage", res);
+
+ while(1) {
+ /* Yield the next Fibonacci number. */
+ mco_push(co, &m, sizeof(m));
+ res = mco_yield(co);
+ if(res != MCO_SUCCESS)
+ fail("Failed to yield coroutine", res);
+
+ unsigned long tmp = m + n;
+ m = n;
+ n = tmp;
+ if(m >= max)
+ break;
+ }
+
+ /* Yield the last Fibonacci number. */
+ mco_push(co, &m, sizeof(m));
+}
+
+int main() {
+ /* Create the coroutine. */
+ mco_coro* co;
+ mco_desc desc = mco_desc_init(fibonacci_coro, 0);
+ mco_result res = mco_create(&co, &desc);
+ if(res != MCO_SUCCESS)
+ fail("Failed to create coroutine", res);
+
+ /* Set storage. */
+ unsigned long max = 1000000000;
+ mco_push(co, &max, sizeof(max));
+
+ int counter = 1;
+ while(mco_status(co) == MCO_SUSPENDED) {
+ /* Resume the coroutine. */
+ res = mco_resume(co);
+ if(res != MCO_SUCCESS)
+ fail("Failed to resume coroutine", res);
+
+ /* Retrieve storage set in last coroutine yield. */
+ unsigned long ret = 0;
+ res = mco_pop(co, &ret, sizeof(ret));
+ if(res != MCO_SUCCESS)
+ fail("Failed to retrieve coroutine storage", res);
+ printf("fib %d = %lu\n", counter, ret);
+ counter = counter + 1;
+ }
+
+ /* Destroy the coroutine. */
+ res = mco_destroy(co);
+ if(res != MCO_SUCCESS)
+ fail("Failed to destroy coroutine", res);
+ return 0;
+}
+```
+
+# Updates
+
+- **01-Sep-2021**: Added support for DOSBox (MS-DOS Emulator).
+- **30-Aug-2021**: Fix stack overflow crash on Windows 32 bits.
+- **22-Aug-2021**: Added checks for stack overflow and iOS support (thanks @srberg).
+- **12-Mar-2021**: Added support for RISC-V RV32.
+- **19-Jan-2021**: Fix compilation and issues on Mac OS X, release v0.1.1.
+- **19-Jan-2021**: First release, v0.1.0.
+- **18-Jan-2021**: Fix issues when using Clang on Linux.
+- **17-Jan-2021**: Add support for RISC-V 64 bits.
+- **16-Jan-2021**: Add support for Mac OS X x86_64, thanks @RandyGaul for testing, debugging and researching about it.
+- **15-Jan-2021**: Make assembly method the default one on Windows x86_64. Redesigned the storage API, thanks @RandyGaul for the suggestion.
+- **14-Jan-2021**: Add support for running with ASan (AddressSanitizer) and TSan (ThreadSanitizer).
+- **13-Jan-2021**: Add support for ARM and WebAssembly. Add Public Domain and MIT No Attribution license.
+- **12-Jan-2021**: Some API changes and improvements.
+- **11-Jan-2021**: Support valgrind and add benchmarks.
+- **10-Jan-2021**: Minor API improvements and document more.
+- **09-Jan-2021**: Library created.
+
+# Donation
+
+I'm a full-time open source developer.
+Any amount of the donation will be appreciated and could bring me encouragement to keep supporting this and other open source projects.
+
+[](https://www.patreon.com/edubart)
+
+# License
+
+Your choice of either Public Domain or MIT No Attribution, see LICENSE file.
diff --git a/client/src/thirdparty/minicoro/minicoro.h b/client/src/thirdparty/minicoro/minicoro.h
new file mode 100644
index 0000000..9081805
--- /dev/null
+++ b/client/src/thirdparty/minicoro/minicoro.h
@@ -0,0 +1,1846 @@
+/*
+Minimal asymmetric stackful cross-platform coroutine library in pure C.
+minicoro - v0.1.2 - 02/Sep/2021
+Eduardo Bart - edub4rt@gmail.com
+https://github.com/edubart/minicoro
+
+Minicoro is single file library for using asymmetric coroutines in C.
+The API is inspired by Lua coroutines but with C use in mind.
+
+# Features
+
+- Stackful asymmetric coroutines.
+- Supports nesting coroutines (resuming a coroutine from another coroutine).
+- Supports custom allocators.
+- Storage system to allow passing values between yield and resume.
+- Customizable stack size.
+- Coroutine API design inspired by Lua with C use in mind.
+- Yield across any C function.
+- Made to work in multithread applications.
+- Cross platform.
+- Minimal, self contained and no external dependencies.
+- Readable sources and documented.
+- Implemented via assembly, ucontext or fibers.
+- Lightweight and very efficient.
+- Works in most C89 compilers.
+- Error prone API, returning proper error codes on misuse.
+- Support running with Valgrind, ASan (AddressSanitizer) and TSan (ThreadSanitizer).
+
+# Supported Platforms
+
+Most platforms are supported through different methods:
+
+| Platform | Assembly Method | Fallback Method |
+|--------------|------------------|-------------------|
+| Android | ARM/ARM64 | N/A |
+| iOS | ARM/ARM64 | N/A |
+| Windows | x86_64 | Windows fibers |
+| Linux | x86_64/i686 | ucontext |
+| Mac OS X | x86_64 | ucontext |
+| WebAssembly | N/A | Emscripten fibers |
+| Raspberry Pi | ARM | ucontext |
+| RISC-V | rv64/rv32 | ucontext |
+
+The assembly method is used by default if supported by the compiler and CPU,
+otherwise ucontext or fiber method is used as a fallback.
+
+The assembly method is very efficient, it just take a few cycles
+to create, resume, yield or destroy a coroutine.
+
+# Caveats
+
+- Don't use coroutines with C++ exceptions, this is not supported.
+- When using C++ RAII (i.e. destructors) you must resume the coroutine until it dies to properly execute all destructors.
+- To use in multithread applications, you must compile with C compiler that supports `thread_local` qualifier.
+- Some unsupported sanitizers for C may trigger false warnings when using coroutines.
+- The `mco_coro` object is not thread safe, you should lock each coroutine into a thread.
+- Stack space is fixed, it cannot grow. By default it has about 56KB of space, this can be changed on coroutine creation.
+- Take care to not cause stack overflows (run out of stack space), otherwise your program may crash or not, the behavior is undefined.
+- On WebAssembly you must compile with emscripten flag `-s ASYNCIFY=1`.
+
+# Introduction
+
+A coroutine represents an independent "green" thread of execution.
+Unlike threads in multithread systems, however,
+a coroutine only suspends its execution by explicitly calling a yield function.
+
+You create a coroutine by calling `mco_create`.
+Its sole argument is a `mco_desc` structure with a description for the coroutine.
+The `mco_create` function only creates a new coroutine and returns a handle to it, it does not start the coroutine.
+
+You execute a coroutine by calling `mco_resume`.
+When calling a resume function the coroutine starts its execution by calling its body function.
+After the coroutine starts running, it runs until it terminates or yields.
+
+A coroutine yields by calling `mco_yield`.
+When a coroutine yields, the corresponding resume returns immediately,
+even if the yield happens inside nested function calls (that is, not in the main function).
+The next time you resume the same coroutine, it continues its execution from the point where it yielded.
+
+To associate a persistent value with the coroutine,
+you can optionally set `user_data` on its creation and later retrieve with `mco_get_user_data`.
+
+To pass values between resume and yield,
+you can optionally use `mco_push` and `mco_pop` APIs,
+they are intended to pass temporary values using a LIFO style buffer.
+The storage system can also be used to send and receive initial values on coroutine creation or before it finishes.
+
+# Usage
+
+To use minicoro, do the following in one .c file:
+
+```c
+#define MINICORO_IMPL
+#include "minicoro.h"
+```
+
+You can do `#include "minicoro.h"` in other parts of the program just like any other header.
+
+## Minimal Example
+
+The following simple example demonstrates on how to use the library:
+
+```c
+#define MINICORO_IMPL
+#include "minicoro.h"
+#include
+
+// Coroutine entry function.
+void coro_entry(mco_coro* co) {
+ printf("coroutine 1\n");
+ mco_yield(co);
+ printf("coroutine 2\n");
+}
+
+int main() {
+ // First initialize a `desc` object through `mco_desc_init`.
+ mco_desc desc = mco_desc_init(coro_entry, 0);
+ // Configure `desc` fields when needed (e.g. customize user_data or allocation functions).
+ desc.user_data = NULL;
+ // Call `mco_create` with the output coroutine pointer and `desc` pointer.
+ mco_coro* co;
+ mco_result res = mco_create(&co, &desc);
+ assert(res == MCO_SUCCESS);
+ // The coroutine should be now in suspended state.
+ assert(mco_status(co) == MCO_SUSPENDED);
+ // Call `mco_resume` to start for the first time, switching to its context.
+ res = mco_resume(co); // Should print "coroutine 1".
+ assert(res == MCO_SUCCESS);
+ // We get back from coroutine context in suspended state (because it's unfinished).
+ assert(mco_status(co) == MCO_SUSPENDED);
+ // Call `mco_resume` to resume for a second time.
+ res = mco_resume(co); // Should print "coroutine 2".
+ assert(res == MCO_SUCCESS);
+ // The coroutine finished and should be now dead.
+ assert(mco_status(co) == MCO_DEAD);
+ // Call `mco_destroy` to destroy the coroutine.
+ res = mco_destroy(co);
+ assert(res == MCO_SUCCESS);
+ return 0;
+}
+```
+
+_NOTE_: In case you don't want to use the minicoro allocator system you should
+allocate a coroutine object yourself using `mco_desc.coro_size` and call `mco_init`,
+then later to destroy call `mco_uninit` and deallocate it.
+
+## Yielding from anywhere
+
+You can yield the current running coroutine from anywhere
+without having to pass `mco_coro` pointers around,
+to this just use `mco_yield(mco_running())`.
+
+## Passing data between yield and resume
+
+The library has the storage interface to assist passing data between yield and resume.
+It's usage is straightforward,
+use `mco_push` to send data before a `mco_resume` or `mco_yield`,
+then later use `mco_pop` after a `mco_resume` or `mco_yield` to receive data.
+Take care to not mismatch a push and pop, otherwise these functions will return
+an error.
+
+## Error handling
+
+The library return error codes in most of its API in case of misuse or system error,
+the user is encouraged to handle them properly.
+
+## Library customization
+
+The following can be defined to change the library behavior:
+
+- `MCO_API` - Public API qualifier. Default is `extern`.
+- `MCO_MIN_STACK_SIZE` - Minimum stack size when creating a coroutine. Default is 32768.
+- `MCO_DEFAULT_STORAGE_SIZE` - Size of coroutine storage buffer. Default is 1024.
+- `MCO_DEFAULT_STACK_SIZE` - Default stack size when creating a coroutine. Default is 57344.
+- `MCO_MALLOC` - Default allocation function. Default is `malloc`.
+- `MCO_FREE` - Default deallocation function. Default is `free`.
+- `MCO_DEBUG` - Enable debug mode, logging any runtime error to stdout. Defined automatically unless `NDEBUG` or `MCO_NO_DEBUG` is defined.
+- `MCO_NO_DEBUG` - Disable debug mode.
+- `MCO_NO_MULTITHREAD` - Disable multithread usage. Multithread is supported when `thread_local` is supported.
+- `MCO_NO_DEFAULT_ALLOCATORS` - Disable the default allocator using `MCO_MALLOC` and `MCO_FREE`.
+- `MCO_ZERO_MEMORY` - Zero memory of stack for new coroutines and when poping storage, intended for garbage collected environments.
+- `MCO_USE_ASM` - Force use of assembly context switch implementation.
+- `MCO_USE_UCONTEXT` - Force use of ucontext context switch implementation.
+- `MCO_USE_FIBERS` - Force use of fibers context switch implementation.
+- `MCO_USE_VALGRIND` - Define if you want run with valgrind to fix accessing memory errors.
+
+# License
+
+Your choice of either Public Domain or MIT No Attribution, see end of file.
+*/
+
+
+#ifndef MINICORO_H
+#define MINICORO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Public API qualifier. */
+#ifndef MCO_API
+#define MCO_API extern
+#endif
+
+/* Size of coroutine storage buffer. */
+#ifndef MCO_DEFAULT_STORAGE_SIZE
+#define MCO_DEFAULT_STORAGE_SIZE 1024
+#endif
+
+#include /* for size_t */
+
+/* ---------------------------------------------------------------------------------------------- */
+
+/* Coroutine states. */
+typedef enum mco_state {
+ MCO_DEAD = 0, /* The coroutine has finished normally or was uninitialized before finishing. */
+ MCO_NORMAL, /* The coroutine is active but not running (that is, it has resumed another coroutine). */
+ MCO_RUNNING, /* The coroutine is active and running. */
+ MCO_SUSPENDED /* The coroutine is suspended (in a call to yield, or it has not started running yet). */
+} mco_state;
+
+/* Coroutine result codes. */
+typedef enum mco_result {
+ MCO_SUCCESS = 0,
+ MCO_GENERIC_ERROR,
+ MCO_INVALID_POINTER,
+ MCO_INVALID_COROUTINE,
+ MCO_NOT_SUSPENDED,
+ MCO_NOT_RUNNING,
+ MCO_MAKE_CONTEXT_ERROR,
+ MCO_SWITCH_CONTEXT_ERROR,
+ MCO_NOT_ENOUGH_SPACE,
+ MCO_OUT_OF_MEMORY,
+ MCO_INVALID_ARGUMENTS,
+ MCO_INVALID_OPERATION,
+ MCO_STACK_OVERFLOW,
+} mco_result;
+
+/* Coroutine structure. */
+typedef struct mco_coro mco_coro;
+struct mco_coro {
+ void* context;
+ mco_state state;
+ void (*func)(mco_coro* co);
+ mco_coro* prev_co;
+ void* user_data;
+ void* allocator_data;
+ void (*free_cb)(void* ptr, void* allocator_data);
+ void* stack_base; /* Stack base address, can be used to scan memory in a garbage collector. */
+ size_t stack_size;
+ unsigned char* storage;
+ size_t bytes_stored;
+ size_t storage_size;
+ void* asan_prev_stack; /* Used by address sanitizer. */
+ void* tsan_prev_fiber; /* Used by thread sanitizer. */
+ void* tsan_fiber; /* Used by thread sanitizer. */
+ size_t magic_number; /* Used to check stack overflow. */
+};
+
+/* Structure used to initialize a coroutine. */
+typedef struct mco_desc {
+ void (*func)(mco_coro* co); /* Entry point function for the coroutine. */
+ void* user_data; /* Coroutine user data, can be get with `mco_get_user_data`. */
+ /* Custom allocation interface. */
+ void* (*malloc_cb)(size_t size, void* allocator_data); /* Custom allocation function. */
+ void (*free_cb)(void* ptr, void* allocator_data); /* Custom deallocation function. */
+ void* allocator_data; /* User data pointer passed to `malloc`/`free` allocation functions. */
+ size_t storage_size; /* Coroutine storage size, to be used with the storage APIs. */
+ /* These must be initialized only through `mco_init_desc`. */
+ size_t coro_size; /* Coroutine structure size. */
+ size_t stack_size; /* Coroutine stack size. */
+} mco_desc;
+
+/* Coroutine functions. */
+MCO_API mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size); /* Initialize description of a coroutine. When stack size is 0 then MCO_DEFAULT_STACK_SIZE is used. */
+MCO_API mco_result mco_init(mco_coro* co, mco_desc* desc); /* Initialize the coroutine. */
+MCO_API mco_result mco_uninit(mco_coro* co); /* Uninitialize the coroutine, may fail if it's not dead or suspended. */
+MCO_API mco_result mco_create(mco_coro** out_co, mco_desc* desc); /* Allocates and initializes a new coroutine. */
+MCO_API mco_result mco_destroy(mco_coro* co); /* Uninitialize and deallocate the coroutine, may fail if it's not dead or suspended. */
+MCO_API mco_result mco_resume(mco_coro* co); /* Starts or continues the execution of the coroutine. */
+MCO_API mco_result mco_yield(mco_coro* co); /* Suspends the execution of a coroutine. */
+MCO_API mco_state mco_status(mco_coro* co); /* Returns the status of the coroutine. */
+MCO_API void* mco_get_user_data(mco_coro* co); /* Get coroutine user data supplied on coroutine creation. */
+
+/* Storage interface functions, used to pass values between yield and resume. */
+MCO_API mco_result mco_push(mco_coro* co, const void* src, size_t len); /* Push bytes to the coroutine storage. Use to send values between yield and resume. */
+MCO_API mco_result mco_pop(mco_coro* co, void* dest, size_t len); /* Pop bytes from the coroutine storage. Use to get values between yield and resume. */
+MCO_API mco_result mco_peek(mco_coro* co, void* dest, size_t len); /* Like `mco_pop` but it does not consumes the storage. */
+MCO_API size_t mco_get_bytes_stored(mco_coro* co); /* Get the available bytes that can be retrieved with a `mco_pop`. */
+MCO_API size_t mco_get_storage_size(mco_coro* co); /* Get the total storage size. */
+
+/* Misc functions. */
+MCO_API mco_coro* mco_running(void); /* Returns the running coroutine for the current thread. */
+MCO_API const char* mco_result_description(mco_result res); /* Get the description of a result. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINICORO_H */
+
+#ifdef MINICORO_IMPL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ---------------------------------------------------------------------------------------------- */
+
+/* Minimum stack size when creating a coroutine. */
+#ifndef MCO_MIN_STACK_SIZE
+#define MCO_MIN_STACK_SIZE 32768
+#endif
+
+/* Default stack size when creating a coroutine. */
+#ifndef MCO_DEFAULT_STACK_SIZE
+#define MCO_DEFAULT_STACK_SIZE 57344 /* Don't use multiples of 64K to avoid D-cache aliasing conflicts. */
+#endif
+
+/* Number used only to assist checking for stack overflows. */
+#define MCO_MAGIC_NUMBER 0x7E3CB1A9
+
+/* Detect implementation based on OS, arch and compiler. */
+#if !defined(MCO_USE_UCONTEXT) && !defined(MCO_USE_FIBERS) && !defined(MCO_USE_ASM)
+ #if defined(_WIN32)
+ #if (defined(__GNUC__) && defined(__x86_64__)) || (defined(_MSC_VER) && defined(_M_X64))
+ #define MCO_USE_ASM
+ #else
+ #define MCO_USE_FIBERS
+ #endif
+ #elif defined(__CYGWIN__) /* MSYS */
+ #define MCO_USE_UCONTEXT
+ #elif defined(__EMSCRIPTEN__)
+ #define MCO_USE_FIBERS
+ #else
+ #if __GNUC__ >= 3 /* Assembly extension supported. */
+ #if defined(__x86_64__) || \
+ defined(__i386) || defined(__i386__) || \
+ defined(__ARM_EABI__) || defined(__aarch64__) || \
+ defined(__riscv)
+ #define MCO_USE_ASM
+ #else
+ #define MCO_USE_UCONTEXT
+ #endif
+ #else
+ #define MCO_USE_UCONTEXT
+ #endif
+ #endif
+#endif
+
+#define _MCO_UNUSED(x) (void)(x)
+
+#if !defined(MCO_NO_DEBUG) && !defined(NDEBUG) && !defined(MCO_DEBUG)
+#define MCO_DEBUG
+#endif
+
+#ifndef MCO_LOG
+ #ifdef MCO_DEBUG
+ #include
+ #define MCO_LOG(s) puts(s)
+ #else
+ #define MCO_LOG(s)
+ #endif
+#endif
+
+#ifndef MCO_ASSERT
+ #ifdef MCO_DEBUG
+ #include
+ #define MCO_ASSERT(c) assert(c)
+ #else
+ #define MCO_ASSERT(c)
+ #endif
+#endif
+
+#ifndef MCO_THREAD_LOCAL
+ #ifdef MCO_NO_MULTITHREAD
+ #define MCO_THREAD_LOCAL
+ #else
+ #ifdef thread_local
+ #define MCO_THREAD_LOCAL thread_local
+ #elif __STDC_VERSION__ >= 201112 && !defined(__STDC_NO_THREADS__)
+ #define MCO_THREAD_LOCAL _Thread_local
+ #elif defined(_WIN32) && (defined(_MSC_VER) || defined(__ICL) || defined(__DMC__) || defined(__BORLANDC__))
+ #define MCO_THREAD_LOCAL __declspec(thread)
+ #elif defined(__GNUC__) || defined(__SUNPRO_C) || defined(__xlC__)
+ #define MCO_THREAD_LOCAL __thread
+ #else /* No thread local support, `mco_running` will be thread unsafe. */
+ #define MCO_THREAD_LOCAL
+ #define MCO_NO_MULTITHREAD
+ #endif
+ #endif
+#endif
+
+#ifndef MCO_FORCE_INLINE
+ #ifdef _MSC_VER
+ #define MCO_FORCE_INLINE __forceinline
+ #elif defined(__GNUC__)
+ #if defined(__STRICT_ANSI__)
+ #define MCO_FORCE_INLINE __inline__ __attribute__((always_inline))
+ #else
+ #define MCO_FORCE_INLINE inline __attribute__((always_inline))
+ #endif
+ #elif defined(__BORLANDC__) || defined(__DMC__) || defined(__SC__) || defined(__WATCOMC__) || defined(__LCC__) || defined(__DECC)
+ #define MCO_FORCE_INLINE __inline
+ #else /* No inline support. */
+ #define MCO_FORCE_INLINE
+ #endif
+#endif
+
+#ifndef MCO_NO_DEFAULT_ALLOCATORS
+#ifndef MCO_MALLOC
+ #include
+ #define MCO_MALLOC malloc
+ #define MCO_FREE free
+#endif
+static void* mco_malloc(size_t size, void* allocator_data) {
+ _MCO_UNUSED(allocator_data);
+ return MCO_MALLOC(size);
+}
+static void mco_free(void* ptr, void* allocator_data) {
+ _MCO_UNUSED(allocator_data);
+ MCO_FREE(ptr);
+}
+#endif /* MCO_NO_DEFAULT_ALLOCATORS */
+
+#if defined(__has_feature)
+ #if __has_feature(address_sanitizer)
+ #define _MCO_USE_ASAN
+ #endif
+ #if __has_feature(thread_sanitizer)
+ #define _MCO_USE_TSAN
+ #endif
+#endif
+#if defined(__SANITIZE_ADDRESS__)
+ #define _MCO_USE_ASAN
+#endif
+#if defined(__SANITIZE_THREAD__)
+ #define _MCO_USE_TSAN
+#endif
+#ifdef _MCO_USE_ASAN
+void __sanitizer_start_switch_fiber(void** fake_stack_save, const void *bottom, size_t size);
+void __sanitizer_finish_switch_fiber(void* fake_stack_save, const void **bottom_old, size_t *size_old);
+#endif
+#ifdef _MCO_USE_TSAN
+void* __tsan_get_current_fiber(void);
+void* __tsan_create_fiber(unsigned flags);
+void __tsan_destroy_fiber(void* fiber);
+void __tsan_switch_to_fiber(void* fiber, unsigned flags);
+#endif
+
+#include /* For memcpy and memset. */
+
+/* Utility for aligning addresses. */
+static MCO_FORCE_INLINE size_t _mco_align_forward(size_t addr, size_t align) {
+ return (addr + (align-1)) & ~(align-1);
+}
+
+/* Variable holding the current running coroutine per thread. */
+static MCO_THREAD_LOCAL mco_coro* mco_current_co = NULL;
+
+static MCO_FORCE_INLINE void _mco_prepare_jumpin(mco_coro* co) {
+ /* Set the old coroutine to normal state and update it. */
+ mco_coro* prev_co = mco_running(); /* Must access through `mco_running`. */
+ MCO_ASSERT(co->prev_co == NULL);
+ co->prev_co = prev_co;
+ if(prev_co) {
+ MCO_ASSERT(prev_co->state == MCO_RUNNING);
+ prev_co->state = MCO_NORMAL;
+ }
+ mco_current_co = co;
+#ifdef _MCO_USE_ASAN
+ if(prev_co) {
+ void* bottom_old = NULL;
+ size_t size_old = 0;
+ __sanitizer_finish_switch_fiber(prev_co->asan_prev_stack, (const void**)&bottom_old, &size_old);
+ prev_co->asan_prev_stack = NULL;
+ }
+ __sanitizer_start_switch_fiber(&co->asan_prev_stack, co->stack_base, co->stack_size);
+#endif
+#ifdef _MCO_USE_TSAN
+ co->tsan_prev_fiber = __tsan_get_current_fiber();
+ __tsan_switch_to_fiber(co->tsan_fiber, 0);
+#endif
+}
+
+static MCO_FORCE_INLINE void _mco_prepare_jumpout(mco_coro* co) {
+ /* Switch back to the previous running coroutine. */
+ MCO_ASSERT(mco_running() == co);
+ mco_coro* prev_co = co->prev_co;
+ co->prev_co = NULL;
+ if(prev_co) {
+ MCO_ASSERT(prev_co->state == MCO_NORMAL);
+ prev_co->state = MCO_RUNNING;
+ }
+ mco_current_co = prev_co;
+#ifdef _MCO_USE_ASAN
+ void* bottom_old = NULL;
+ size_t size_old = 0;
+ __sanitizer_finish_switch_fiber(co->asan_prev_stack, (const void**)&bottom_old, &size_old);
+ co->asan_prev_stack = NULL;
+ if(prev_co) {
+ __sanitizer_start_switch_fiber(&prev_co->asan_prev_stack, bottom_old, size_old);
+ }
+#endif
+#ifdef _MCO_USE_TSAN
+ void* tsan_prev_fiber = co->tsan_prev_fiber;
+ co->tsan_prev_fiber = NULL;
+ __tsan_switch_to_fiber(tsan_prev_fiber, 0);
+#endif
+}
+
+static void _mco_jumpin(mco_coro* co);
+static void _mco_jumpout(mco_coro* co);
+
+static void _mco_main(mco_coro* co) {
+ co->func(co); /* Run the coroutine function. */
+ co->state = MCO_DEAD; /* Coroutine finished successfully, set state to dead. */
+ _mco_jumpout(co); /* Jump back to the old context .*/
+}
+
+/* ---------------------------------------------------------------------------------------------- */
+
+#if defined(MCO_USE_UCONTEXT) || defined(MCO_USE_ASM)
+
+/*
+Some of the following assembly code is taken from LuaCoco by Mike Pall.
+See https://coco.luajit.org/index.html
+
+MIT license
+
+Copyright (C) 2004-2016 Mike Pall. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifdef MCO_USE_ASM
+
+#if defined(__x86_64__) || defined(_M_X64)
+
+#ifdef _WIN32
+
+typedef struct _mco_ctxbuf {
+ void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15, *rdi, *rsi;
+ void* xmm[20]; /* xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15 */
+ void* fiber_storage;
+ void* dealloc_stack;
+ void* stack_limit;
+ void* stack_base;
+} _mco_ctxbuf;
+
+#if defined(__GNUC__)
+#define _MCO_ASM_BLOB __attribute__((section(".text")))
+#elif defined(_MSC_VER)
+#define _MCO_ASM_BLOB __declspec(allocate(".text"))
+#pragma section(".text")
+#endif
+
+_MCO_ASM_BLOB static unsigned char _mco_wrap_main_code[] = {
+ 0x4c, 0x89, 0xe9, /* mov %r13,%rcx */
+ 0x41, 0xff, 0xe4, /* jmpq *%r12 */
+ 0xc3, /* retq */
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 /* nop */
+};
+
+_MCO_ASM_BLOB static unsigned char _mco_switch_code[] = {
+ 0x48, 0x8d, 0x05, 0x52, 0x01, 0x00, 0x00, /* lea 0x152(%rip),%rax */
+ 0x48, 0x89, 0x01, /* mov %rax,(%rcx) */
+ 0x48, 0x89, 0x61, 0x08, /* mov %rsp,0x8(%rcx) */
+ 0x48, 0x89, 0x69, 0x10, /* mov %rbp,0x10(%rcx) */
+ 0x48, 0x89, 0x59, 0x18, /* mov %rbx,0x18(%rcx) */
+ 0x4c, 0x89, 0x61, 0x20, /* mov %r12,0x20(%rcx) */
+ 0x4c, 0x89, 0x69, 0x28, /* mov %r13,0x28(%rcx) */
+ 0x4c, 0x89, 0x71, 0x30, /* mov %r14,0x30(%rcx) */
+ 0x4c, 0x89, 0x79, 0x38, /* mov %r15,0x38(%rcx) */
+ 0x48, 0x89, 0x79, 0x40, /* mov %rdi,0x40(%rcx) */
+ 0x48, 0x89, 0x71, 0x48, /* mov %rsi,0x48(%rcx) */
+ 0x66, 0x0f, 0xd6, 0x71, 0x50, /* movq %xmm6,0x50(%rcx) */
+ 0x66, 0x0f, 0xd6, 0x79, 0x60, /* movq %xmm7,0x60(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0x41, 0x70, /* movq %xmm8,0x70(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0x89, 0x80, 0x00, 0x00, 0x00, /* movq %xmm9,0x80(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0x91, 0x90, 0x00, 0x00, 0x00, /* movq %xmm10,0x90(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0x99, 0xa0, 0x00, 0x00, 0x00, /* movq %xmm11,0xa0(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0xa1, 0xb0, 0x00, 0x00, 0x00, /* movq %xmm12,0xb0(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0xa9, 0xc0, 0x00, 0x00, 0x00, /* movq %xmm13,0xc0(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0xb1, 0xd0, 0x00, 0x00, 0x00, /* movq %xmm14,0xd0(%rcx) */
+ 0x66, 0x44, 0x0f, 0xd6, 0xb9, 0xe0, 0x00, 0x00, 0x00, /* movq %xmm15,0xe0(%rcx) */
+ 0x65, 0x4c, 0x8b, 0x14, 0x25, 0x30, 0x00, 0x00, 0x00, /* mov %gs:0x30,%r10 */
+ 0x49, 0x8b, 0x42, 0x20, /* mov 0x20(%r10),%rax */
+ 0x48, 0x89, 0x81, 0xf0, 0x00, 0x00, 0x00, /* mov %rax,0xf0(%rcx) */
+ 0x49, 0x8b, 0x82, 0x78, 0x14, 0x00, 0x00, /* mov 0x1478(%r10),%rax */
+ 0x48, 0x89, 0x81, 0xf8, 0x00, 0x00, 0x00, /* mov %rax,0xf8(%rcx) */
+ 0x49, 0x8b, 0x42, 0x10, /* mov 0x10(%r10),%rax */
+ 0x48, 0x89, 0x81, 0x00, 0x01, 0x00, 0x00, /* mov %rax,0x100(%rcx) */
+ 0x49, 0x8b, 0x42, 0x08, /* mov 0x8(%r10),%rax */
+ 0x48, 0x89, 0x81, 0x08, 0x01, 0x00, 0x00, /* mov %rax,0x108(%rcx) */
+ 0x48, 0x8b, 0x82, 0x08, 0x01, 0x00, 0x00, /* mov 0x108(%rdx),%rax */
+ 0x49, 0x89, 0x42, 0x08, /* mov %rax,0x8(%r10) */
+ 0x48, 0x8b, 0x82, 0x00, 0x01, 0x00, 0x00, /* mov 0x100(%rdx),%rax */
+ 0x49, 0x89, 0x42, 0x10, /* mov %rax,0x10(%r10) */
+ 0x48, 0x8b, 0x82, 0xf8, 0x00, 0x00, 0x00, /* mov 0xf8(%rdx),%rax */
+ 0x49, 0x89, 0x82, 0x78, 0x14, 0x00, 0x00, /* mov %rax,0x1478(%r10) */
+ 0x48, 0x8b, 0x82, 0xf0, 0x00, 0x00, 0x00, /* mov 0xf0(%rdx),%rax */
+ 0x49, 0x89, 0x42, 0x20, /* mov %rax,0x20(%r10) */
+ 0xf3, 0x44, 0x0f, 0x7e, 0xba, 0xe0, 0x00, 0x00, 0x00, /* movq 0xe0(%rdx),%xmm15 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0xb2, 0xd0, 0x00, 0x00, 0x00, /* movq 0xd0(%rdx),%xmm14 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0xaa, 0xc0, 0x00, 0x00, 0x00, /* movq 0xc0(%rdx),%xmm13 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0xa2, 0xb0, 0x00, 0x00, 0x00, /* movq 0xb0(%rdx),%xmm12 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0x9a, 0xa0, 0x00, 0x00, 0x00, /* movq 0xa0(%rdx),%xmm11 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0x92, 0x90, 0x00, 0x00, 0x00, /* movq 0x90(%rdx),%xmm10 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0x8a, 0x80, 0x00, 0x00, 0x00, /* movq 0x80(%rdx),%xmm9 */
+ 0xf3, 0x44, 0x0f, 0x7e, 0x42, 0x70, /* movq 0x70(%rdx),%xmm8 */
+ 0xf3, 0x0f, 0x7e, 0x7a, 0x60, /* movq 0x60(%rdx),%xmm7 */
+ 0xf3, 0x0f, 0x7e, 0x72, 0x50, /* movq 0x50(%rdx),%xmm6 */
+ 0x48, 0x8b, 0x72, 0x48, /* mov 0x48(%rdx),%rsi */
+ 0x48, 0x8b, 0x7a, 0x40, /* mov 0x40(%rdx),%rdi */
+ 0x4c, 0x8b, 0x7a, 0x38, /* mov 0x38(%rdx),%r15 */
+ 0x4c, 0x8b, 0x72, 0x30, /* mov 0x30(%rdx),%r14 */
+ 0x4c, 0x8b, 0x6a, 0x28, /* mov 0x28(%rdx),%r13 */
+ 0x4c, 0x8b, 0x62, 0x20, /* mov 0x20(%rdx),%r12 */
+ 0x48, 0x8b, 0x5a, 0x18, /* mov 0x18(%rdx),%rbx */
+ 0x48, 0x8b, 0x6a, 0x10, /* mov 0x10(%rdx),%rbp */
+ 0x48, 0x8b, 0x62, 0x08, /* mov 0x8(%rdx),%rsp */
+ 0xff, 0x22, /* jmpq *(%rdx) */
+ 0xc3, /* retq */
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, /* nop */
+};
+
+void (*_mco_wrap_main)(void) = (void(*)(void))(void*)_mco_wrap_main_code;
+void (*_mco_switch)(_mco_ctxbuf* from, _mco_ctxbuf* to) = (void(*)(_mco_ctxbuf* from, _mco_ctxbuf* to))(void*)_mco_switch_code;
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ stack_size = stack_size - 32; /* Reserve 32 bytes for the shadow space. */
+ void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - sizeof(size_t));
+ stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */
+ ctx->rip = (void*)(_mco_wrap_main);
+ ctx->rsp = (void*)(stack_high_ptr);
+ ctx->r12 = (void*)(_mco_main);
+ ctx->r13 = (void*)(co);
+ void* stack_top = (void*)((size_t)stack_base + stack_size);
+ ctx->stack_base = stack_top;
+ ctx->stack_limit = stack_base;
+ ctx->dealloc_stack = stack_base;
+ return MCO_SUCCESS;
+}
+
+#else /* not _WIN32 */
+
+typedef struct _mco_ctxbuf {
+ void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15;
+} _mco_ctxbuf;
+
+void _mco_wrap_main(void);
+int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to);
+
+__asm__(
+ ".text\n"
+#ifdef __MACH__ /* Mac OS X assembler */
+ ".globl __mco_wrap_main\n"
+ "__mco_wrap_main:\n"
+#else /* Linux assembler */
+ ".globl _mco_wrap_main\n"
+ ".type _mco_wrap_main @function\n"
+ ".hidden _mco_wrap_main\n"
+ "_mco_wrap_main:\n"
+#endif
+ " movq %r13, %rdi\n"
+ " jmpq *%r12\n"
+#ifndef __MACH__
+ ".size _mco_wrap_main, .-_mco_wrap_main\n"
+#endif
+);
+
+__asm__(
+ ".text\n"
+#ifdef __MACH__ /* Mac OS assembler */
+ ".globl __mco_switch\n"
+ "__mco_switch:\n"
+#else /* Linux assembler */
+ ".globl _mco_switch\n"
+ ".type _mco_switch @function\n"
+ ".hidden _mco_switch\n"
+ "_mco_switch:\n"
+#endif
+ " leaq 0x3d(%rip), %rax\n"
+ " movq %rax, (%rdi)\n"
+ " movq %rsp, 8(%rdi)\n"
+ " movq %rbp, 16(%rdi)\n"
+ " movq %rbx, 24(%rdi)\n"
+ " movq %r12, 32(%rdi)\n"
+ " movq %r13, 40(%rdi)\n"
+ " movq %r14, 48(%rdi)\n"
+ " movq %r15, 56(%rdi)\n"
+ " movq 56(%rsi), %r15\n"
+ " movq 48(%rsi), %r14\n"
+ " movq 40(%rsi), %r13\n"
+ " movq 32(%rsi), %r12\n"
+ " movq 24(%rsi), %rbx\n"
+ " movq 16(%rsi), %rbp\n"
+ " movq 8(%rsi), %rsp\n"
+ " jmpq *(%rsi)\n"
+ " ret\n"
+#ifndef __MACH__
+ ".size _mco_switch, .-_mco_switch\n"
+#endif
+);
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ stack_size = stack_size - 128; /* Reserve 128 bytes for the Red Zone space (System V AMD64 ABI). */
+ void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - sizeof(size_t));
+ stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */
+ ctx->rip = (void*)(_mco_wrap_main);
+ ctx->rsp = (void*)(stack_high_ptr);
+ ctx->r12 = (void*)(_mco_main);
+ ctx->r13 = (void*)(co);
+ return MCO_SUCCESS;
+}
+
+#endif /* not _WIN32 */
+
+#elif defined(__riscv)
+
+typedef struct _mco_ctxbuf {
+ void* s[12]; /* s0-s11 */
+ void* ra;
+ void* pc;
+ void* sp;
+#ifdef __riscv_flen
+#if __riscv_flen == 64
+ double fs[12]; /* fs0-fs11 */
+#elif __riscv_flen == 32
+ float fs[12]; /* fs0-fs11 */
+#endif
+#endif /* __riscv_flen */
+} _mco_ctxbuf;
+
+void _mco_wrap_main(void);
+int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to);
+
+__asm__(
+ ".text\n"
+ ".globl _mco_wrap_main\n"
+ ".type _mco_wrap_main @function\n"
+ ".hidden _mco_wrap_main\n"
+ "_mco_wrap_main:\n"
+ " mv a0, s0\n"
+ " jr s1\n"
+ ".size _mco_wrap_main, .-_mco_wrap_main\n"
+);
+
+__asm__(
+ ".text\n"
+ ".globl _mco_switch\n"
+ ".type _mco_switch @function\n"
+ ".hidden _mco_switch\n"
+ "_mco_switch:\n"
+ #if __riscv_xlen == 64
+ " sd s0, 0x00(a0)\n"
+ " sd s1, 0x08(a0)\n"
+ " sd s2, 0x10(a0)\n"
+ " sd s3, 0x18(a0)\n"
+ " sd s4, 0x20(a0)\n"
+ " sd s5, 0x28(a0)\n"
+ " sd s6, 0x30(a0)\n"
+ " sd s7, 0x38(a0)\n"
+ " sd s8, 0x40(a0)\n"
+ " sd s9, 0x48(a0)\n"
+ " sd s10, 0x50(a0)\n"
+ " sd s11, 0x58(a0)\n"
+ " sd ra, 0x60(a0)\n"
+ " sd ra, 0x68(a0)\n" /* pc */
+ " sd sp, 0x70(a0)\n"
+ #ifdef __riscv_flen
+ #if __riscv_flen == 64
+ " fsd fs0, 0x78(a0)\n"
+ " fsd fs1, 0x80(a0)\n"
+ " fsd fs2, 0x88(a0)\n"
+ " fsd fs3, 0x90(a0)\n"
+ " fsd fs4, 0x98(a0)\n"
+ " fsd fs5, 0xa0(a0)\n"
+ " fsd fs6, 0xa8(a0)\n"
+ " fsd fs7, 0xb0(a0)\n"
+ " fsd fs8, 0xb8(a0)\n"
+ " fsd fs9, 0xc0(a0)\n"
+ " fsd fs10, 0xc8(a0)\n"
+ " fsd fs11, 0xd0(a0)\n"
+ " fld fs0, 0x78(a1)\n"
+ " fld fs1, 0x80(a1)\n"
+ " fld fs2, 0x88(a1)\n"
+ " fld fs3, 0x90(a1)\n"
+ " fld fs4, 0x98(a1)\n"
+ " fld fs5, 0xa0(a1)\n"
+ " fld fs6, 0xa8(a1)\n"
+ " fld fs7, 0xb0(a1)\n"
+ " fld fs8, 0xb8(a1)\n"
+ " fld fs9, 0xc0(a1)\n"
+ " fld fs10, 0xc8(a1)\n"
+ " fld fs11, 0xd0(a1)\n"
+ #else
+ #error "Unsupported RISC-V FLEN"
+ #endif
+ #endif /* __riscv_flen */
+ " ld s0, 0x00(a1)\n"
+ " ld s1, 0x08(a1)\n"
+ " ld s2, 0x10(a1)\n"
+ " ld s3, 0x18(a1)\n"
+ " ld s4, 0x20(a1)\n"
+ " ld s5, 0x28(a1)\n"
+ " ld s6, 0x30(a1)\n"
+ " ld s7, 0x38(a1)\n"
+ " ld s8, 0x40(a1)\n"
+ " ld s9, 0x48(a1)\n"
+ " ld s10, 0x50(a1)\n"
+ " ld s11, 0x58(a1)\n"
+ " ld ra, 0x60(a1)\n"
+ " ld a2, 0x68(a1)\n" /* pc */
+ " ld sp, 0x70(a1)\n"
+ " jr a2\n"
+ #elif __riscv_xlen == 32
+ " sw s0, 0x00(a0)\n"
+ " sw s1, 0x04(a0)\n"
+ " sw s2, 0x08(a0)\n"
+ " sw s3, 0x0c(a0)\n"
+ " sw s4, 0x10(a0)\n"
+ " sw s5, 0x14(a0)\n"
+ " sw s6, 0x18(a0)\n"
+ " sw s7, 0x1c(a0)\n"
+ " sw s8, 0x20(a0)\n"
+ " sw s9, 0x24(a0)\n"
+ " sw s10, 0x28(a0)\n"
+ " sw s11, 0x2c(a0)\n"
+ " sw ra, 0x30(a0)\n"
+ " sw ra, 0x34(a0)\n" /* pc */
+ " sw sp, 0x38(a0)\n"
+ #ifdef __riscv_flen
+ #if __riscv_flen == 64
+ " fsd fs0, 0x3c(a0)\n"
+ " fsd fs1, 0x44(a0)\n"
+ " fsd fs2, 0x4c(a0)\n"
+ " fsd fs3, 0x54(a0)\n"
+ " fsd fs4, 0x5c(a0)\n"
+ " fsd fs5, 0x64(a0)\n"
+ " fsd fs6, 0x6c(a0)\n"
+ " fsd fs7, 0x74(a0)\n"
+ " fsd fs8, 0x7c(a0)\n"
+ " fsd fs9, 0x84(a0)\n"
+ " fsd fs10, 0x8c(a0)\n"
+ " fsd fs11, 0x94(a0)\n"
+ " fld fs0, 0x3c(a1)\n"
+ " fld fs1, 0x44(a1)\n"
+ " fld fs2, 0x4c(a1)\n"
+ " fld fs3, 0x54(a1)\n"
+ " fld fs4, 0x5c(a1)\n"
+ " fld fs5, 0x64(a1)\n"
+ " fld fs6, 0x6c(a1)\n"
+ " fld fs7, 0x74(a1)\n"
+ " fld fs8, 0x7c(a1)\n"
+ " fld fs9, 0x84(a1)\n"
+ " fld fs10, 0x8c(a1)\n"
+ " fld fs11, 0x94(a1)\n"
+ #elif __riscv_flen == 32
+ " fsw fs0, 0x3c(a0)\n"
+ " fsw fs1, 0x40(a0)\n"
+ " fsw fs2, 0x44(a0)\n"
+ " fsw fs3, 0x48(a0)\n"
+ " fsw fs4, 0x4c(a0)\n"
+ " fsw fs5, 0x50(a0)\n"
+ " fsw fs6, 0x54(a0)\n"
+ " fsw fs7, 0x58(a0)\n"
+ " fsw fs8, 0x5c(a0)\n"
+ " fsw fs9, 0x60(a0)\n"
+ " fsw fs10, 0x64(a0)\n"
+ " fsw fs11, 0x68(a0)\n"
+ " flw fs0, 0x3c(a1)\n"
+ " flw fs1, 0x40(a1)\n"
+ " flw fs2, 0x44(a1)\n"
+ " flw fs3, 0x48(a1)\n"
+ " flw fs4, 0x4c(a1)\n"
+ " flw fs5, 0x50(a1)\n"
+ " flw fs6, 0x54(a1)\n"
+ " flw fs7, 0x58(a1)\n"
+ " flw fs8, 0x5c(a1)\n"
+ " flw fs9, 0x60(a1)\n"
+ " flw fs10, 0x64(a1)\n"
+ " flw fs11, 0x68(a1)\n"
+ #else
+ #error "Unsupported RISC-V FLEN"
+ #endif
+ #endif /* __riscv_flen */
+ " lw s0, 0x00(a1)\n"
+ " lw s1, 0x04(a1)\n"
+ " lw s2, 0x08(a1)\n"
+ " lw s3, 0x0c(a1)\n"
+ " lw s4, 0x10(a1)\n"
+ " lw s5, 0x14(a1)\n"
+ " lw s6, 0x18(a1)\n"
+ " lw s7, 0x1c(a1)\n"
+ " lw s8, 0x20(a1)\n"
+ " lw s9, 0x24(a1)\n"
+ " lw s10, 0x28(a1)\n"
+ " lw s11, 0x2c(a1)\n"
+ " lw ra, 0x30(a1)\n"
+ " lw a2, 0x34(a1)\n" /* pc */
+ " lw sp, 0x38(a1)\n"
+ " jr a2\n"
+ #else
+ #error "Unsupported RISC-V XLEN"
+ #endif /* __riscv_xlen */
+ ".size _mco_switch, .-_mco_switch\n"
+);
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ ctx->s[0] = (void*)(co);
+ ctx->s[1] = (void*)(_mco_main);
+ ctx->pc = (void*)(_mco_wrap_main);
+#if __riscv_xlen == 64
+ ctx->ra = (void*)(0xdeaddeaddeaddead);
+#elif __riscv_xlen == 32
+ ctx->ra = (void*)(0xdeaddead);
+#endif
+ ctx->sp = (void*)((size_t)stack_base + stack_size);
+ return MCO_SUCCESS;
+}
+
+#elif defined(__i386) || defined(__i386__)
+
+typedef struct _mco_ctxbuf {
+ void *eip, *esp, *ebp, *ebx, *esi, *edi;
+} _mco_ctxbuf;
+
+void _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to);
+
+__asm__(
+#ifdef __DJGPP__ /* DOS compiler */
+ "__mco_switch:\n"
+#else
+ ".text\n"
+ ".globl _mco_switch\n"
+ ".type _mco_switch @function\n"
+ ".hidden _mco_switch\n"
+ "_mco_switch:\n"
+#endif
+ " call 1f\n"
+ " 1:\n"
+ " popl %ecx\n"
+ " addl $(2f-1b), %ecx\n"
+ " movl 4(%esp), %eax\n"
+ " movl 8(%esp), %edx\n"
+ " movl %ecx, (%eax)\n"
+ " movl %esp, 4(%eax)\n"
+ " movl %ebp, 8(%eax)\n"
+ " movl %ebx, 12(%eax)\n"
+ " movl %esi, 16(%eax)\n"
+ " movl %edi, 20(%eax)\n"
+ " movl 20(%edx), %edi\n"
+ " movl 16(%edx), %esi\n"
+ " movl 12(%edx), %ebx\n"
+ " movl 8(%edx), %ebp\n"
+ " movl 4(%edx), %esp\n"
+ " jmp *(%edx)\n"
+ " 2:\n"
+ " ret\n"
+#ifndef __DJGPP__
+ ".size _mco_switch, .-_mco_switch\n"
+#endif
+);
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - 16 - 1*sizeof(size_t));
+ stack_high_ptr[0] = (void*)(0xdeaddead); /* Dummy return address. */
+ stack_high_ptr[1] = (void*)(co);
+ ctx->eip = (void*)(_mco_main);
+ ctx->esp = (void*)(stack_high_ptr);
+ return MCO_SUCCESS;
+}
+
+#elif defined(__ARM_EABI__)
+
+typedef struct _mco_ctxbuf {
+#ifndef __SOFTFP__
+ void* f[16];
+#endif
+ void *d[4]; /* d8-d15 */
+ void *r[4]; /* r4-r11 */
+ void *lr;
+ void *sp;
+} _mco_ctxbuf;
+
+void _mco_wrap_main(void);
+int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to);
+
+__asm__(
+ ".text\n"
+#ifdef __APPLE__
+ ".globl __mco_switch\n"
+ "__mco_switch:\n"
+#else
+ ".globl _mco_switch\n"
+ ".type _mco_switch #function\n"
+ ".hidden _mco_switch\n"
+ "_mco_switch:\n"
+#endif
+#ifndef __SOFTFP__
+ " vstmia r0!, {d8-d15}\n"
+#endif
+ " stmia r0, {r4-r11, lr}\n"
+ " str sp, [r0, #9*4]\n"
+#ifndef __SOFTFP__
+ " vldmia r1!, {d8-d15}\n"
+#endif
+ " ldr sp, [r1, #9*4]\n"
+ " ldmia r1, {r4-r11, pc}\n"
+#ifndef __APPLE__
+ ".size _mco_switch, .-_mco_switch\n"
+#endif
+);
+
+__asm__(
+ ".text\n"
+#ifdef __APPLE__
+ ".globl __mco_wrap_main\n"
+ "__mco_wrap_main:\n"
+#else
+ ".globl _mco_wrap_main\n"
+ ".type _mco_wrap_main #function\n"
+ ".hidden _mco_wrap_main\n"
+ "_mco_wrap_main:\n"
+#endif
+ " mov r0, r4\n"
+ " mov ip, r5\n"
+ " mov lr, r6\n"
+ " bx ip\n"
+#ifndef __APPLE__
+ ".size _mco_wrap_main, .-_mco_wrap_main\n"
+#endif
+);
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ ctx->d[0] = (void*)(co);
+ ctx->d[1] = (void*)(_mco_main);
+ ctx->d[2] = (void*)(0xdeaddead); /* Dummy return address. */
+ ctx->lr = (void*)(_mco_wrap_main);
+ ctx->sp = (void*)((size_t)stack_base + stack_size);
+ return MCO_SUCCESS;
+}
+
+#elif defined(__aarch64__)
+
+typedef struct _mco_ctxbuf {
+ void *x[12]; /* x19-x30 */
+ void *sp;
+ void *lr;
+ void *d[8]; /* d8-d15 */
+} _mco_ctxbuf;
+
+void _mco_wrap_main(void);
+int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to);
+
+__asm__(
+ ".text\n"
+#ifdef __APPLE__
+ ".globl __mco_switch\n"
+ "__mco_switch:\n"
+#else
+ ".globl _mco_switch\n"
+ ".type _mco_switch #function\n"
+ ".hidden _mco_switch\n"
+ "_mco_switch:\n"
+#endif
+
+ " mov x10, sp\n"
+ " mov x11, x30\n"
+ " stp x19, x20, [x0, #(0*16)]\n"
+ " stp x21, x22, [x0, #(1*16)]\n"
+ " stp d8, d9, [x0, #(7*16)]\n"
+ " stp x23, x24, [x0, #(2*16)]\n"
+ " stp d10, d11, [x0, #(8*16)]\n"
+ " stp x25, x26, [x0, #(3*16)]\n"
+ " stp d12, d13, [x0, #(9*16)]\n"
+ " stp x27, x28, [x0, #(4*16)]\n"
+ " stp d14, d15, [x0, #(10*16)]\n"
+ " stp x29, x30, [x0, #(5*16)]\n"
+ " stp x10, x11, [x0, #(6*16)]\n"
+ " ldp x19, x20, [x1, #(0*16)]\n"
+ " ldp x21, x22, [x1, #(1*16)]\n"
+ " ldp d8, d9, [x1, #(7*16)]\n"
+ " ldp x23, x24, [x1, #(2*16)]\n"
+ " ldp d10, d11, [x1, #(8*16)]\n"
+ " ldp x25, x26, [x1, #(3*16)]\n"
+ " ldp d12, d13, [x1, #(9*16)]\n"
+ " ldp x27, x28, [x1, #(4*16)]\n"
+ " ldp d14, d15, [x1, #(10*16)]\n"
+ " ldp x29, x30, [x1, #(5*16)]\n"
+ " ldp x10, x11, [x1, #(6*16)]\n"
+ " mov sp, x10\n"
+ " br x11\n"
+#ifndef __APPLE__
+ ".size _mco_switch, .-_mco_switch\n"
+#endif
+);
+
+__asm__(
+ ".text\n"
+#ifdef __APPLE__
+ ".globl __mco_wrap_main\n"
+ "__mco_wrap_main:\n"
+#else
+ ".globl _mco_wrap_main\n"
+ ".type _mco_wrap_main #function\n"
+ ".hidden _mco_wrap_main\n"
+ "_mco_wrap_main:\n"
+#endif
+ " mov x0, x19\n"
+ " mov x30, x21\n"
+ " br x20\n"
+#ifndef __APPLE__
+ ".size _mco_wrap_main, .-_mco_wrap_main\n"
+#endif
+);
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ ctx->x[0] = (void*)(co);
+ ctx->x[1] = (void*)(_mco_main);
+ ctx->x[2] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */
+ ctx->sp = (void*)((size_t)stack_base + stack_size);
+ ctx->lr = (void*)(_mco_wrap_main);
+ return MCO_SUCCESS;
+}
+
+#else
+
+#error "Unsupported architecture for assembly method."
+
+#endif /* ARCH */
+
+#elif defined(MCO_USE_UCONTEXT)
+
+#include
+
+typedef ucontext_t _mco_ctxbuf;
+
+#if defined(_LP64) || defined(__LP64__)
+static void _mco_wrap_main(unsigned int lo, unsigned int hi) {
+ mco_coro* co = (mco_coro*)(((size_t)lo) | (((size_t)hi) << 32)); /* Extract coroutine pointer. */
+ _mco_main(co);
+}
+#else
+static void _mco_wrap_main(unsigned int lo) {
+ mco_coro* co = (mco_coro*)((size_t)lo); /* Extract coroutine pointer. */
+ _mco_main(co);
+}
+#endif
+
+static MCO_FORCE_INLINE void _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to) {
+ int res = swapcontext(from, to);
+ _MCO_UNUSED(res);
+ MCO_ASSERT(res == 0);
+}
+
+static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) {
+ /* Initialize ucontext. */
+ if(getcontext(ctx) != 0) {
+ MCO_LOG("failed to get ucontext");
+ return MCO_MAKE_CONTEXT_ERROR;
+ }
+ ctx->uc_link = NULL; /* We never exit from _mco_wrap_main. */
+ ctx->uc_stack.ss_sp = stack_base;
+ ctx->uc_stack.ss_size = stack_size;
+ unsigned int lo = (unsigned int)((size_t)co);
+#if defined(_LP64) || defined(__LP64__)
+ unsigned int hi = (unsigned int)(((size_t)co)>>32);
+ makecontext(ctx, (void (*)(void))_mco_wrap_main, 2, lo, hi);
+#else
+ makecontext(ctx, (void (*)(void))_mco_wrap_main, 1, lo);
+#endif
+ return MCO_SUCCESS;
+}
+
+#endif /* defined(MCO_USE_UCONTEXT) */
+
+#ifdef MCO_USE_VALGRIND
+#include
+#endif
+
+typedef struct _mco_context {
+#ifdef MCO_USE_VALGRIND
+ unsigned int valgrind_stack_id;
+#endif
+ _mco_ctxbuf ctx;
+ _mco_ctxbuf back_ctx;
+} _mco_context;
+
+static void _mco_jumpin(mco_coro* co) {
+ _mco_context* context = (_mco_context*)co->context;
+ _mco_prepare_jumpin(co);
+ _mco_switch(&context->back_ctx, &context->ctx); /* Do the context switch. */
+}
+
+static void _mco_jumpout(mco_coro* co) {
+ _mco_context* context = (_mco_context*)co->context;
+ _mco_prepare_jumpout(co);
+ _mco_switch(&context->ctx, &context->back_ctx); /* Do the context switch. */
+}
+
+static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) {
+ /* Determine the context and stack address. */
+ size_t co_addr = (size_t)co;
+ size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16);
+ size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16);
+ size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16);
+ /* Initialize context. */
+ _mco_context* context = (_mco_context*)context_addr;
+ memset(context, 0, sizeof(_mco_context));
+ /* Initialize storage. */
+ unsigned char* storage = (unsigned char*)storage_addr;
+ memset(storage, 0, desc->storage_size);
+ /* Initialize stack. */
+ void *stack_base = (void*)stack_addr;
+ size_t stack_size = desc->stack_size;
+#ifdef MCO_ZERO_MEMORY
+ memset(stack_base, 0, stack_size);
+#endif
+ /* Make the context. */
+ mco_result res = _mco_makectx(co, &context->ctx, stack_base, stack_size);
+ if(res != MCO_SUCCESS) {
+ return res;
+ }
+#ifdef MCO_USE_VALGRIND
+ context->valgrind_stack_id = VALGRIND_STACK_REGISTER(stack_addr, stack_addr + stack_size);
+#endif
+ co->context = context;
+ co->stack_base = stack_base;
+ co->stack_size = stack_size;
+ co->storage = storage;
+ co->storage_size = desc->storage_size;
+ return MCO_SUCCESS;
+}
+
+static void _mco_destroy_context(mco_coro* co) {
+#ifdef MCO_USE_VALGRIND
+ _mco_context* context = (_mco_context*)co->context;
+ if(context && context->valgrind_stack_id != 0) {
+ VALGRIND_STACK_DEREGISTER(context->valgrind_stack_id);
+ context->valgrind_stack_id = 0;
+ }
+#else
+ _MCO_UNUSED(co);
+#endif
+}
+
+static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) {
+ desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) +
+ _mco_align_forward(sizeof(_mco_context), 16) +
+ _mco_align_forward(desc->storage_size, 16) +
+ stack_size + 16;
+ desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */
+}
+
+#endif /* defined(MCO_USE_UCONTEXT) || defined(MCO_USE_ASM) */
+
+/* ---------------------------------------------------------------------------------------------- */
+
+#ifdef MCO_USE_FIBERS
+
+#ifdef _WIN32
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0400
+#endif
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include
+
+typedef struct _mco_context {
+ void* fib;
+ void* back_fib;
+} _mco_context;
+
+static void _mco_jumpin(mco_coro* co) {
+ void *cur_fib = GetCurrentFiber();
+ if(!cur_fib || cur_fib == (void*)0x1e00) { /* See http://blogs.msdn.com/oldnewthing/archive/2004/12/31/344799.aspx */
+ cur_fib = ConvertThreadToFiber(NULL);
+ }
+ MCO_ASSERT(cur_fib != NULL);
+ _mco_context* context = (_mco_context*)co->context;
+ context->back_fib = cur_fib;
+ _mco_prepare_jumpin(co);
+ SwitchToFiber(context->fib);
+}
+
+static void CALLBACK _mco_wrap_main(void* co) {
+ _mco_main((mco_coro*)co);
+}
+
+static void _mco_jumpout(mco_coro* co) {
+ _mco_context* context = (_mco_context*)co->context;
+ void* back_fib = context->back_fib;
+ MCO_ASSERT(back_fib != NULL);
+ context->back_fib = NULL;
+ _mco_prepare_jumpout(co);
+ SwitchToFiber(back_fib);
+}
+
+/* Reverse engineered Fiber struct, used to get stack base. */
+typedef struct _mco_fiber {
+ LPVOID param; /* fiber param */
+ void* except; /* saved exception handlers list */
+ void* stack_base; /* top of fiber stack */
+ void* stack_limit; /* fiber stack low-water mark */
+ void* stack_allocation; /* base of the fiber stack allocation */
+ CONTEXT context; /* fiber context */
+ DWORD flags; /* fiber flags */
+ LPFIBER_START_ROUTINE start; /* start routine */
+ void **fls_slots; /* fiber storage slots */
+} _mco_fiber;
+
+static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) {
+ /* Determine the context address. */
+ size_t co_addr = (size_t)co;
+ size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16);
+ size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16);
+ /* Initialize context. */
+ _mco_context* context = (_mco_context*)context_addr;
+ memset(context, 0, sizeof(_mco_context));
+ /* Initialize storage. */
+ unsigned char* storage = (unsigned char*)storage_addr;
+ memset(storage, 0, desc->storage_size);
+ /* Create the fiber. */
+ _mco_fiber* fib = (_mco_fiber*)CreateFiberEx(desc->stack_size, desc->stack_size, FIBER_FLAG_FLOAT_SWITCH, _mco_wrap_main, co);
+ if(!fib) {
+ MCO_LOG("failed to create fiber");
+ return MCO_MAKE_CONTEXT_ERROR;
+ }
+ context->fib = fib;
+ co->context = context;
+ co->stack_base = (void*)((size_t)fib->stack_base - desc->stack_size);
+ co->stack_size = desc->stack_size;
+ co->storage = storage;
+ co->storage_size = desc->storage_size;
+ return MCO_SUCCESS;
+}
+
+static void _mco_destroy_context(mco_coro* co) {
+ _mco_context* context = (_mco_context*)co->context;
+ if(context && context->fib) {
+ DeleteFiber(context->fib);
+ context->fib = NULL;
+ }
+}
+
+static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) {
+ desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) +
+ _mco_align_forward(sizeof(_mco_context), 16) +
+ _mco_align_forward(desc->storage_size, 16) +
+ 16;
+ desc->stack_size = stack_size;
+}
+
+#elif defined(__EMSCRIPTEN__)
+
+#include
+
+#ifndef MCO_ASYNCFY_STACK_SIZE
+#define MCO_ASYNCFY_STACK_SIZE 16384
+#endif
+
+typedef struct _mco_context {
+ emscripten_fiber_t fib;
+ emscripten_fiber_t* back_fib;
+} _mco_context;
+
+static emscripten_fiber_t* running_fib = NULL;
+static unsigned char main_asyncify_stack[MCO_ASYNCFY_STACK_SIZE];
+static emscripten_fiber_t main_fib;
+
+static void _mco_wrap_main(void* co) {
+ _mco_main((mco_coro*)co);
+}
+
+static void _mco_jumpin(mco_coro* co) {
+ _mco_context* context = (_mco_context*)co->context;
+ emscripten_fiber_t* back_fib = running_fib;
+ if(!back_fib) {
+ back_fib = &main_fib;
+ emscripten_fiber_init_from_current_context(back_fib, main_asyncify_stack, MCO_ASYNCFY_STACK_SIZE);
+ }
+ running_fib = &context->fib;
+ context->back_fib = back_fib;
+ _mco_prepare_jumpin(co);
+ emscripten_fiber_swap(back_fib, &context->fib); /* Do the context switch. */
+}
+
+static void _mco_jumpout(mco_coro* co) {
+ _mco_context* context = (_mco_context*)co->context;
+ running_fib = context->back_fib;
+ _mco_prepare_jumpout(co);
+ emscripten_fiber_swap(&context->fib, context->back_fib); /* Do the context switch. */
+}
+
+static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) {
+ if(emscripten_has_asyncify() != 1) {
+ MCO_LOG("failed to create fiber because ASYNCIFY is not enabled");
+ return MCO_MAKE_CONTEXT_ERROR;
+ }
+ /* Determine the context address. */
+ size_t co_addr = (size_t)co;
+ size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16);
+ size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16);
+ size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16);
+ size_t asyncify_stack_addr = _mco_align_forward(stack_addr + desc->stack_size, 16);
+ /* Initialize context. */
+ _mco_context* context = (_mco_context*)context_addr;
+ memset(context, 0, sizeof(_mco_context));
+ /* Initialize storage. */
+ unsigned char* storage = (unsigned char*)storage_addr;
+ memset(storage, 0, desc->storage_size);
+ /* Initialize stack. */
+ void *stack_base = (void*)stack_addr;
+ size_t stack_size = asyncify_stack_addr - stack_addr;
+ void *asyncify_stack_base = (void*)asyncify_stack_addr;
+ size_t asyncify_stack_size = co_addr + desc->coro_size - asyncify_stack_addr;
+#ifdef MCO_ZERO_MEMORY
+ memset(stack_base, 0, stack_size);
+ memset(asyncify_stack_base, 0, asyncify_stack_size);
+#endif
+ /* Create the fiber. */
+ emscripten_fiber_init(&context->fib, _mco_wrap_main, co, stack_base, stack_size, asyncify_stack_base, asyncify_stack_size);
+ co->context = context;
+ co->stack_base = stack_base;
+ co->stack_size = stack_size;
+ co->storage = storage;
+ co->storage_size = desc->storage_size;
+ return MCO_SUCCESS;
+}
+
+static void _mco_destroy_context(mco_coro* co) {
+ /* Nothing to do. */
+ _MCO_UNUSED(co);
+}
+
+static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) {
+ desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) +
+ _mco_align_forward(sizeof(_mco_context), 16) +
+ _mco_align_forward(desc->storage_size, 16) +
+ _mco_align_forward(stack_size, 16) +
+ _mco_align_forward(MCO_ASYNCFY_STACK_SIZE, 16) +
+ 16;
+ desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */
+}
+
+#else
+
+#error "Unsupported architecture for fibers method."
+
+#endif
+
+#endif /* MCO_USE_FIBERS */
+
+/* ---------------------------------------------------------------------------------------------- */
+
+mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size) {
+ if(stack_size != 0) {
+ /* Stack size should be at least `MCO_MIN_STACK_SIZE`. */
+ if(stack_size < MCO_MIN_STACK_SIZE) {
+ stack_size = MCO_MIN_STACK_SIZE;
+ }
+ } else {
+ stack_size = MCO_DEFAULT_STACK_SIZE;
+ }
+ stack_size = _mco_align_forward(stack_size, 16); /* Stack size should be aligned to 16 bytes. */
+ mco_desc desc;
+ memset(&desc, 0, sizeof(mco_desc));
+#ifndef MCO_NO_DEFAULT_ALLOCATORS
+ /* Set default allocators. */
+ desc.malloc_cb = mco_malloc;
+ desc.free_cb = mco_free;
+#endif
+ desc.func = func;
+ desc.storage_size = MCO_DEFAULT_STORAGE_SIZE;
+ _mco_init_desc_sizes(&desc, stack_size);
+ return desc;
+}
+
+static mco_result _mco_validate_desc(mco_desc* desc) {
+ if(!desc) {
+ MCO_LOG("coroutine description is NULL");
+ return MCO_INVALID_ARGUMENTS;
+ }
+ if(!desc->func) {
+ MCO_LOG("coroutine function in invalid");
+ return MCO_INVALID_ARGUMENTS;
+ }
+ if(desc->stack_size < MCO_MIN_STACK_SIZE) {
+ MCO_LOG("coroutine stack size is too small");
+ return MCO_INVALID_ARGUMENTS;
+ }
+ if(desc->coro_size < sizeof(mco_coro)) {
+ MCO_LOG("coroutine size is invalid");
+ return MCO_INVALID_ARGUMENTS;
+ }
+ return MCO_SUCCESS;
+}
+
+mco_result mco_init(mco_coro* co, mco_desc* desc) {
+ if(!co) {
+ MCO_LOG("attempt to initialize an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ }
+ memset(co, 0, sizeof(mco_coro));
+ /* Validate coroutine description. */
+ mco_result res = _mco_validate_desc(desc);
+ if(res != MCO_SUCCESS)
+ return res;
+ /* Create the coroutine. */
+ res = _mco_create_context(co, desc);
+ if(res != MCO_SUCCESS)
+ return res;
+ co->state = MCO_SUSPENDED; /* We initialize in suspended state. */
+ co->free_cb = desc->free_cb;
+ co->allocator_data = desc->allocator_data;
+ co->func = desc->func;
+ co->user_data = desc->user_data;
+#ifdef _MCO_USE_TSAN
+ co->tsan_fiber = __tsan_create_fiber(0);
+#endif
+ co->magic_number = MCO_MAGIC_NUMBER;
+ return MCO_SUCCESS;
+}
+
+mco_result mco_uninit(mco_coro* co) {
+ if(!co) {
+ MCO_LOG("attempt to uninitialize an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ }
+ /* Cannot uninitialize while running. */
+ if(!(co->state == MCO_SUSPENDED || co->state == MCO_DEAD)) {
+ MCO_LOG("attempt to uninitialize a coroutine that is not dead or suspended");
+ return MCO_INVALID_OPERATION;
+ }
+ /* The coroutine is now dead and cannot be used anymore. */
+ co->state = MCO_DEAD;
+#ifdef _MCO_USE_TSAN
+ if(co->tsan_fiber != NULL) {
+ __tsan_destroy_fiber(co->tsan_fiber);
+ co->tsan_fiber = NULL;
+ }
+#endif
+ _mco_destroy_context(co);
+ return MCO_SUCCESS;
+}
+
+mco_result mco_create(mco_coro** out_co, mco_desc* desc) {
+ /* Validate input. */
+ if(!out_co) {
+ MCO_LOG("coroutine output pointer is NULL");
+ return MCO_INVALID_POINTER;
+ }
+ if(!desc || !desc->malloc_cb || !desc->free_cb) {
+ *out_co = NULL;
+ MCO_LOG("coroutine allocator description is not set");
+ return MCO_INVALID_ARGUMENTS;
+ }
+ /* Allocate the coroutine. */
+ mco_coro* co = (mco_coro*)desc->malloc_cb(desc->coro_size, desc->allocator_data);
+ if(!co) {
+ MCO_LOG("coroutine allocation failed");
+ *out_co = NULL;
+ return MCO_OUT_OF_MEMORY;
+ }
+ /* Initialize the coroutine. */
+ mco_result res = mco_init(co, desc);
+ if(res != MCO_SUCCESS) {
+ desc->free_cb(co, desc->allocator_data);
+ *out_co = NULL;
+ return res;
+ }
+ *out_co = co;
+ return MCO_SUCCESS;
+}
+
+mco_result mco_destroy(mco_coro* co) {
+ if(!co) {
+ MCO_LOG("attempt to destroy an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ }
+ /* Uninitialize the coroutine first. */
+ mco_result res = mco_uninit(co);
+ if(res != MCO_SUCCESS)
+ return res;
+ /* Free the coroutine. */
+ if(!co->free_cb) {
+ MCO_LOG("attempt destroy a coroutine that has no free callback");
+ return MCO_INVALID_POINTER;
+ }
+ co->free_cb(co, co->allocator_data);
+ return MCO_SUCCESS;
+}
+
+mco_result mco_resume(mco_coro* co) {
+ if(!co) {
+ MCO_LOG("attempt to resume an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ }
+ if(co->state != MCO_SUSPENDED) { /* Can only resume coroutines that are suspended. */
+ MCO_LOG("attempt to resume a coroutine that is not suspended");
+ return MCO_NOT_SUSPENDED;
+ }
+ co->state = MCO_RUNNING; /* The coroutine is now running. */
+ _mco_jumpin(co);
+ return MCO_SUCCESS;
+}
+
+mco_result mco_yield(mco_coro* co) {
+ if(!co) {
+ MCO_LOG("attempt to yield an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ }
+ /* This check happens when the stack overflow already happened, but better later than never. */
+ volatile size_t dummy;
+ size_t stack_addr = (size_t)&dummy;
+ size_t stack_min = (size_t)co->stack_base;
+ size_t stack_max = stack_min + co->stack_size;
+ if(co->magic_number != MCO_MAGIC_NUMBER || stack_addr < stack_min || stack_addr > stack_max) { /* Stack overflow. */
+ MCO_LOG("coroutine stack overflow, try increasing the stack size");
+ return MCO_STACK_OVERFLOW;
+ }
+ if(co->state != MCO_RUNNING) { /* Can only yield coroutines that are running. */
+ MCO_LOG("attempt to yield a coroutine that is not running");
+ return MCO_NOT_RUNNING;
+ }
+ co->state = MCO_SUSPENDED; /* The coroutine is now suspended. */
+ _mco_jumpout(co);
+ return MCO_SUCCESS;
+}
+
+mco_state mco_status(mco_coro* co) {
+ if(co != NULL) {
+ return co->state;
+ }
+ return MCO_DEAD;
+}
+
+void* mco_get_user_data(mco_coro* co) {
+ if(co != NULL) {
+ return co->user_data;
+ }
+ return NULL;
+}
+
+mco_result mco_push(mco_coro* co, const void* src, size_t len) {
+ if(!co) {
+ MCO_LOG("attempt to use an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ } else if(len > 0) {
+ size_t bytes_stored = co->bytes_stored + len;
+ if(bytes_stored > co->storage_size) {
+ MCO_LOG("attempt to push bytes too many bytes into coroutine storage");
+ return MCO_NOT_ENOUGH_SPACE;
+ }
+ if(!src) {
+ MCO_LOG("attempt push a null pointer into coroutine storage");
+ return MCO_INVALID_POINTER;
+ }
+ memcpy(&co->storage[co->bytes_stored], src, len);
+ co->bytes_stored = bytes_stored;
+ }
+ return MCO_SUCCESS;
+}
+
+mco_result mco_pop(mco_coro* co, void* dest, size_t len) {
+ if(!co) {
+ MCO_LOG("attempt to use an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ } else if(len > 0) {
+ if(len > co->bytes_stored) {
+ MCO_LOG("attempt to pop too many bytes from coroutine storage");
+ return MCO_NOT_ENOUGH_SPACE;
+ }
+ size_t bytes_stored = co->bytes_stored - len;
+ if(dest) {
+ memcpy(dest, &co->storage[bytes_stored], len);
+ }
+ co->bytes_stored = bytes_stored;
+#ifdef MCO_ZERO_MEMORY
+ /* Clear garbage in the discarded storage. */
+ memset(&co->storage[bytes_stored], 0, len);
+#endif
+ }
+ return MCO_SUCCESS;
+}
+
+mco_result mco_peek(mco_coro* co, void* dest, size_t len) {
+ if(!co) {
+ MCO_LOG("attempt to use an invalid coroutine");
+ return MCO_INVALID_COROUTINE;
+ } else if(len > 0) {
+ if(len > co->bytes_stored) {
+ MCO_LOG("attempt to peek too many bytes from coroutine storage");
+ return MCO_NOT_ENOUGH_SPACE;
+ }
+ if(!dest) {
+ MCO_LOG("attempt peek into a null pointer");
+ return MCO_INVALID_POINTER;
+ }
+ memcpy(dest, &co->storage[co->bytes_stored - len], len);
+ }
+ return MCO_SUCCESS;
+}
+
+size_t mco_get_bytes_stored(mco_coro* co) {
+ if(co == NULL) {
+ return 0;
+ }
+ return co->bytes_stored;
+}
+
+size_t mco_get_storage_size(mco_coro* co) {
+ if(co == NULL) {
+ return 0;
+ }
+ return co->storage_size;
+}
+
+#ifdef MCO_NO_MULTITHREAD
+mco_coro* mco_running(void) {
+ return mco_current_co;
+}
+#else
+static mco_coro* _mco_running(void) {
+ return mco_current_co;
+}
+mco_coro* mco_running(void) {
+ /*
+ Compilers aggressively optimize the use of TLS by caching loads.
+ Since fiber code can migrate between threads it’s possible for the load to be stale.
+ To prevent this from happening we avoid inline functions.
+ */
+ mco_coro* (*volatile func)(void) = _mco_running;
+ return func();
+}
+#endif
+
+const char* mco_result_description(mco_result res) {
+ switch(res) {
+ case MCO_SUCCESS:
+ return "No error";
+ case MCO_GENERIC_ERROR:
+ return "Generic error";
+ case MCO_INVALID_POINTER:
+ return "Invalid pointer";
+ case MCO_INVALID_COROUTINE:
+ return "Invalid coroutine";
+ case MCO_NOT_SUSPENDED:
+ return "Coroutine not suspended";
+ case MCO_NOT_RUNNING:
+ return "Coroutine not running";
+ case MCO_MAKE_CONTEXT_ERROR:
+ return "Make context error";
+ case MCO_SWITCH_CONTEXT_ERROR:
+ return "Switch context error";
+ case MCO_NOT_ENOUGH_SPACE:
+ return "Not enough space";
+ case MCO_OUT_OF_MEMORY:
+ return "Out of memory";
+ case MCO_INVALID_ARGUMENTS:
+ return "Invalid arguments";
+ case MCO_INVALID_OPERATION:
+ return "Invalid operation";
+ case MCO_STACK_OVERFLOW:
+ return "Stack overflow";
+ }
+ return "Unknown error";
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINICORO_IMPL */
+
+/*
+This software is available as a choice of the following licenses. Choose
+whichever you prefer.
+
+===============================================================================
+ALTERNATIVE 1 - Public Domain (www.unlicense.org)
+===============================================================================
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
+
+===============================================================================
+ALTERNATIVE 2 - MIT No Attribution
+===============================================================================
+Copyright (c) 2021 Eduardo Bart (https://github.com/edubart/minicoro)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/