New multitasking code. Now supports Intel, ARM, and RISC-V. Project reorganized to help isolate GUI code.

This commit is contained in:
Scott Duensing 2021-11-04 18:39:48 -05:00
parent fb0d5ad0ac
commit 5287b9870e
23 changed files with 2545 additions and 292 deletions

View file

@ -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

View file

@ -18,7 +18,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
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

View file

@ -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 \

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
// 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 <setjmp.h>
#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; x<len; x++) {
task = _private.taskList[x];
if (task->status == 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; x<len; x++) {
if (task == _private.taskList[x]) {
// Remove us from the list.
arrdel(_private.taskList, x);
}
}
// Don't free the task or we won't have a stack.
// Defer until we longjmp back into the old stack.
longjmp(_private.buf, EXIT_TASK);
// Execution doesn't get to here.
}
static void taskFree(void) {
TaskT *task = _private.current;
_private.current = NULL;
free(task->stackBottom);
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);
}
}

View file

@ -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; y<t->rows; y++) {
for (x=0; x<t->cols; 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; y<terminal->cols; 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; y<terminal->rows; y++) {
for (x=0; x<terminal->cols; 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);
}
}

View file

@ -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);

115
client/src/system/task.c Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#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());
}

View file

@ -22,15 +22,14 @@
#define TASK_H
#include "os.h"
#include <stdint.h>
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

47
client/src/thirdparty/minicoro/LICENSE vendored Normal file
View file

@ -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 <http://unlicense.org/>
===============================================================================
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.

350
client/src/thirdparty/minicoro/README.md vendored Normal file
View file

@ -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 <stdio.h>
// 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 <stdio.h>
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.
[![Become a Patron](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/edubart)
# License
Your choice of either Public Domain or MIT No Attribution, see LICENSE file.

1846
client/src/thirdparty/minicoro/minicoro.h vendored Normal file

File diff suppressed because it is too large Load diff