New multitasking code. Now supports Intel, ARM, and RISC-V. Project reorganized to help isolate GUI code.
This commit is contained in:
parent
fb0d5ad0ac
commit
5287b9870e
23 changed files with 2545 additions and 292 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
115
client/src/system/task.c
Normal 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());
|
||||
}
|
|
@ -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
47
client/src/thirdparty/minicoro/LICENSE
vendored
Normal 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
350
client/src/thirdparty/minicoro/README.md
vendored
Normal 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.
|
||||
|
||||
[](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
1846
client/src/thirdparty/minicoro/minicoro.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue