diff --git a/source/game/event.c b/source/game/event.c new file mode 100644 index 0000000..c7cbd4a --- /dev/null +++ b/source/game/event.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include + +#include "fumotris.h" + +struct Delegate { + size_t len; + size_t capacity; + void (**events)(void *args); +}; + +inline size_t delegate_size(size_t capacity) +{ + return sizeof(void(*)(void *)) * capacity; +} + +struct Delegate NewDelegate(size_t capacity) +{ + return (struct Delegate) { + .len = 0, + .capacity = capacity, + .events = malloc(delegate_size(capacity)) + }; +} + +void Subscribe(struct Delegate *d, void (*event)(void *args)) +{ + if (d->len == d->capacity) { + d->capacity *= 2; + d->events = realloc(d->events, delegate_size(d->capacity)); + } + + d->events[d->len] = event; + d->len += 1; +} + +void Call(struct Delegate *d, void *args) +{ + for (size_t i = 0; i < d->len; i++) { + d->events[i](args); + } +} \ No newline at end of file diff --git a/source/game/event.h b/source/game/event.h new file mode 100644 index 0000000..ce7a4cd --- /dev/null +++ b/source/game/event.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "fumotris.h" + +struct Delegate { + size_t len; + size_t capacity; + void (**events)(void *args); +}; + +struct Delegate NewDelegate(size_t capacity); + +void Subscribe(struct Delegate *d, void (*event)(void *args)); + +void Call(struct Delegate *d, void *args); \ No newline at end of file diff --git a/source/game/tetr.c b/source/game/tetr.c index c774601..c579f0d 100644 --- a/source/game/tetr.c +++ b/source/game/tetr.c @@ -33,7 +33,7 @@ struct TetrMap NewTetrMap(u8 *blks, size_t wid, size_t hgt) }; } -void TetrMapToTermBuf(struct TetrMap *map, struct TermBuf *term) +void TetrMapToTermBuf(struct TetrMap *map, struct Terminal *term) { static const u8f blk_colors[8] = { 8, 14, 11, 13, 10, 9, 12, 3 }; @@ -42,8 +42,8 @@ void TetrMapToTermBuf(struct TetrMap *map, struct TermBuf *term) size_t map_i = y * map->wid + x; size_t buf_i = (y + map->y) * term->wid + (x + map->x) * 2; - struct CharBlk4 *a = &term->blks[buf_i]; - struct CharBlk4 *b = &term->blks[buf_i + 1]; + struct TChar4 *a = &term->blks[buf_i]; + struct TChar4 *b = &term->blks[buf_i + 1]; if (map->blks[map_i] == 0) { a->ch = '('; diff --git a/source/game/tetr.h b/source/game/tetr.h index 7b5fd45..896e510 100644 --- a/source/game/tetr.h +++ b/source/game/tetr.h @@ -23,6 +23,6 @@ struct TetrMap { struct TetrMap NewTetrMap(u8 *blks, size_t wid, size_t hgt); -void TetrMapToTermBuf(struct TetrMap *map, struct TermBuf *term); +void TetrMapToTermBuf(struct TetrMap *map, struct Terminal *term); bool TetrCollisionCheck(struct TetrMap *board, struct TetrMap *piece, int dx, int dy); \ No newline at end of file diff --git a/source/io/ctrl.c b/source/io/ctrl.c index f1548db..3ab135a 100644 --- a/source/io/ctrl.c +++ b/source/io/ctrl.c @@ -88,64 +88,83 @@ hashtype Hash(void *item, size_t size) return h; } -struct ident { - u16 id; - u8 type; -}; - hashtype hash_ident(u16f value, u8f type) { - struct ident id = { value, type }; - return Hash(&id, sizeof(struct ident)); + struct id { + u16 id; + u8 type; + }; + + struct id id = { value, type }; + return Hash(&id, sizeof(struct id)); } -struct bkt { +struct ctrl_bkt { hashtype hash; u16 value; u8 type; struct Axis *axis; }; -struct dict { +struct ctrl_dict { size_t capacity; size_t filled; - struct bkt *bkts; + struct ctrl_bkt *bkts; }; struct Ctrl { - struct dict codes; - struct dict binds; + struct ctrl_dict codes; + struct ctrl_dict binds; pthread_t thread; pthread_mutex_t mutex; }; -typedef struct Ctrl Ctrl; -Ctrl NewCtrl(struct dict *codes, struct dict *binds, struct Axis *axes) +struct Ctrl NewCtrl() { - memset(codes->bkts, 0, sizeof(struct bkt) * codes->capacity); - memset(binds->bkts, 0, sizeof(struct bkt) * binds->capacity); - memset(axes, 0, sizeof(struct Axis) * codes->capacity); - - for (size_t i = 0; i < codes->capacity; i++) { - codes->bkts[i].axis = &axes[i]; - } - - Ctrl ctrl; - ctrl.codes = *codes; - ctrl.binds = *binds; - ctrl.mutex = PTHREAD_MUTEX_INITIALIZER; + struct Ctrl ctrl = (struct Ctrl) { + .codes = (struct ctrl_dict) { + .capacity = 0, + .filled = 0, + .bkts = nullptr + }, + .binds = (struct ctrl_dict) { + .capacity = 0, + .filled = 0, + .bkts = nullptr + }, + .mutex = PTHREAD_MUTEX_INITIALIZER + }; return ctrl; } -struct bkt *get_bkt(struct dict *dict, size_t i) +void CtrlSet( + struct Ctrl *ctrl, + struct ctrl_bkt *code_bkts, + struct ctrl_bkt *bind_bkts, + struct Axis *axes, + size_t c_len, + size_t b_len +) { + ctrl->codes.capacity = c_len; + ctrl->codes.bkts = code_bkts; + memset(code_bkts, 0, sizeof(struct ctrl_bkt) * c_len); + + ctrl->binds.capacity = b_len; + ctrl->binds.bkts = bind_bkts; + memset(bind_bkts, 0, sizeof(struct ctrl_bkt) * b_len); + + +} + +struct ctrl_bkt *get_bkt(struct ctrl_dict *dict, size_t i) { assert(i < dict->capacity); return &dict->bkts[i]; } -void set_bkt(struct bkt *bkt, hashtype hash, u16f value, u8f type) +void set_bkt(struct ctrl_bkt *bkt, hashtype hash, u16f value, u8f type) { bkt->hash = hash; bkt->value = value; @@ -157,14 +176,14 @@ size_t wrap(size_t x, size_t wrap) return x % (SIZE_MAX - wrap + 1); } -bool find_or_set(struct dict *dict, struct bkt **out, u16f value, u8f type) +bool find_or_set(struct ctrl_dict *dict, struct ctrl_bkt **out, u16f value, u8f type) { hashtype hash = hash_ident(value, type); const size_t index = hash % dict->capacity; size_t i = index; while (i != wrap(index - 1, dict->capacity)) { - struct bkt *bkt = get_bkt(dict, i); + struct ctrl_bkt *bkt = get_bkt(dict, i); if (bkt->hash == 0) { set_bkt(bkt, hash, value, type); @@ -185,14 +204,14 @@ bool find_or_set(struct dict *dict, struct bkt **out, u16f value, u8f type) return false; } -struct bkt *find(struct dict *dict, u16f value, u8f type) +struct ctrl_bkt *find(struct ctrl_dict *dict, u16f value, u8f type) { hashtype hash = hash_ident(value, type); const size_t index = hash % dict->capacity; size_t i = index; while (i != wrap(index - 1, dict->capacity)) { - struct bkt *bkt = get_bkt(dict, i); + struct ctrl_bkt *bkt = get_bkt(dict, i); if (bkt->hash == 0) goto next; @@ -206,25 +225,25 @@ next: return nullptr; } -struct Axis *find_axis(struct dict *dict, u16f value, u8f type) +struct Axis *find_axis(struct ctrl_dict *dict, u16f value, u8f type) { - struct bkt *bkt = find(dict, value, type); + struct ctrl_bkt *bkt = find(dict, value, type); if (bkt == nullptr) return nullptr; return bkt->axis; } -bool CtrlMap(Ctrl *ctrl, u16f code, u16f bind, u8f type) +bool CtrlMap(struct Ctrl *ctrl, u16f code, u16f bind, u8f type) { assert(ctrl->codes.filled < ctrl->codes.capacity); assert(ctrl->binds.filled < ctrl->binds.capacity); - struct bkt *code_bkt; + struct ctrl_bkt *code_bkt; find_or_set(&ctrl->codes, &code_bkt, code, type); assert(code_bkt != nullptr); - struct bkt *bind_bkt; + struct ctrl_bkt *bind_bkt; bool bind_existed = find_or_set(&ctrl->binds, &bind_bkt, bind, type); assert(bind_bkt != nullptr); @@ -235,9 +254,9 @@ bool CtrlMap(Ctrl *ctrl, u16f code, u16f bind, u8f type) return true; } -struct Axis *CtrlGet(Ctrl *ctrl, u16f code, u8f type) +struct Axis *CtrlGet(struct Ctrl *ctrl, u16f code, u8f type) { - struct bkt *code_bkt = find(&ctrl->codes, code, type); + struct ctrl_bkt *code_bkt = find(&ctrl->codes, code, type); if (code_bkt == nullptr) return nullptr; @@ -267,13 +286,10 @@ void update_joystick(struct Axis *axis, struct CtrlRecord *record) axis->last_pressed = record->timestamp; } -bool CtrlPoll(Ctrl *ctrl, struct RecordBuffer *buf) +bool CtrlPoll(struct Ctrl *ctrl, struct RecordBuffer *rec_buf) { - pthread_mutex_lock(&buf->mutex); - pthread_mutex_lock(&ctrl->mutex); - - for (size_t i = 0; i < buf->count; i++) { - struct CtrlRecord *rec = &buf->records[i]; + for (size_t i = 0; i < rec_buf->count; i++) { + struct CtrlRecord *rec = &rec_buf->records[i]; struct Axis *axis = find_axis(&ctrl->binds, rec->id, rec->type); if (axis == nullptr) @@ -294,8 +310,6 @@ bool CtrlPoll(Ctrl *ctrl, struct RecordBuffer *buf) } } - buf->count = 0; - pthread_mutex_unlock(&buf->mutex); - pthread_mutex_unlock(&ctrl->mutex); + rec_buf->count = 0; return true; } \ No newline at end of file diff --git a/source/io/ctrl.h b/source/io/ctrl.h index f3851ab..068ab6d 100644 --- a/source/io/ctrl.h +++ b/source/io/ctrl.h @@ -84,32 +84,31 @@ enum CtrlCode { typedef u32 hashtype; -struct bkt { +struct ctrl_bkt { hashtype hash; u16 value; u8 type; struct Axis *axis; }; -struct dict { +struct ctrl_dict { size_t capacity; size_t filled; - struct bkt *bkts; + struct ctrl_bkt *bkts; }; struct Ctrl { - struct dict codes; - struct dict binds; + struct ctrl_dict codes; + struct ctrl_dict binds; pthread_t thread; pthread_mutex_t mutex; }; -typedef struct Ctrl Ctrl; -Ctrl NewCtrl(struct dict *codes, struct dict *binds, struct Axis *axes); +struct Ctrl NewCtrl(struct ctrl_dict *codes, struct ctrl_dict *binds, struct Axis *axes); -bool CtrlMap(Ctrl *ctrl, u16f code, u16f bind, u8f type); +bool CtrlMap(struct Ctrl *ctrl, u16f code, u16f bind, u8f type); -struct Axis *CtrlGet(Ctrl *ctrl, u16f code, u8f type); +struct Axis *CtrlGet(struct Ctrl *ctrl, u16f code, u8f type); -bool CtrlPoll(Ctrl *ctrl, struct RecordBuffer *buf); \ No newline at end of file +bool CtrlPoll(struct Ctrl *ctrl, struct RecordBuffer *buf); \ No newline at end of file diff --git a/source/io/instance.c b/source/io/instance.c deleted file mode 100644 index 26b7d58..0000000 --- a/source/io/instance.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "fumotris.h" - -struct Window { - size_t x; - size_t y; - - u16f fps; -}; \ No newline at end of file diff --git a/source/io/instance.h b/source/io/instance.h deleted file mode 100644 index 5ace3ff..0000000 --- a/source/io/instance.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include - -#include "fumotris.h" - -struct Window { - size_t x; - size_t y; - - u16f fps; -}; \ No newline at end of file diff --git a/source/io/platforms/win.c b/source/io/platforms/win.c index a4fa385..37944b1 100644 --- a/source/io/platforms/win.c +++ b/source/io/platforms/win.c @@ -3,23 +3,23 @@ #include #include #include +#include +#include #include "input.h" -#include "instance.h" #include "winhandler.h" +#include "term.h" -bool WindowsInit(struct Window *window) +bool WindowsInit(struct Terminal *term) { - if (!WinInitInputHandle()) - return false; - - if (!WinInitTimer()) + if (!WinInitHandles()) return false; if (!WinInitConsole()) return false; - + if (!WinGetRefreshRate(&term->refresh_rate)) + return false; return true; } @@ -31,5 +31,10 @@ bool WindowsBlockInput(struct RecordBuffer *buf) bool WindowsWait(double seconds) { - return WinWait(seconds); + struct timespec duration = { + .tv_sec = seconds, + .tv_nsec = fmod(seconds, 1) * 1e9 + }; + + return WinWait(duration); } \ No newline at end of file diff --git a/source/io/platforms/winhandler.c b/source/io/platforms/winhandler.c index a59f809..f049086 100644 --- a/source/io/platforms/winhandler.c +++ b/source/io/platforms/winhandler.c @@ -10,7 +10,7 @@ #include "fumotris.h" #include "gametime.h" #include "input.h" -#include "instance.h" +#include "term.h" struct Windows { HANDLE input_handle; @@ -54,7 +54,7 @@ bool WinInitConsole() return SetConsoleMode(windows.input_handle, mode); } -bool WinGetRefreshRate(struct Window *window) +bool WinGetRefreshRate(u32f *out) { LPDEVMODE mode; if(!EnumDisplaySettingsA( @@ -64,7 +64,7 @@ bool WinGetRefreshRate(struct Window *window) )) return false; - window->fps = mode->dmDisplayFrequency; + *out = mode->dmDisplayFrequency; return true; } @@ -147,19 +147,17 @@ bool WinBlockInput(struct RecordBuffer *buf) if (!include) continue; - buf->records[buf->count] = record; - buf->count += 1; + buf->records[buf->count++] = record; } pthread_mutex_unlock(&buf->mutex); return true; } -bool WinWait(struct timespec duration) +bool WinWait(struct timespec relative) { LARGE_INTEGER duration; - duration.QuadPart = (u64)(-10000000.0 * seconds); - + duration.QuadPart = -10000000 * relative.tv_sec - relative.tv_nsec / 100; if (!SetWaitableTimer( windows.draw_handles[0], // Timer diff --git a/source/io/platforms/winhandler.h b/source/io/platforms/winhandler.h index 5eaf3c3..a123a2b 100644 --- a/source/io/platforms/winhandler.h +++ b/source/io/platforms/winhandler.h @@ -11,6 +11,8 @@ bool WinInitHandles(); bool WinInitConsole(); +bool WinGetRefreshRate(u32f *out); + bool WinBlockInput(struct RecordBuffer *buf); -bool WinWait(double seconds); \ No newline at end of file +bool WinWait(struct timespec relative); \ No newline at end of file diff --git a/source/main.c b/source/main.c index 9923c95..0607a04 100644 --- a/source/main.c +++ b/source/main.c @@ -10,11 +10,22 @@ #include "fumotris.h" #include "term.h" #include "tetr.h" +#include "event.h" #ifdef _WIN32 #include "win.h" #endif +struct Instance { + struct Terminal term; + struct Ctrl ctrl; + struct RecordBuffer rec_buf; + + struct Delegate on_start; + struct Delegate on_draw; + struct Delegate on_update; +}; + const u8 I[16] = { 0, 0, 0, 0, 0, 0, 0, 0, @@ -79,39 +90,61 @@ const struct CtrlBind ctrl_binds[12] = { { MOUSE, 0, JOYSTICK } }; -void Update(Ctrl *ctrl) +void Draw(struct Terminal *term) { + pthread_mutex_lock(&term->mutex); + struct TChar4 term_blks[term->area]; + + pthread_cond_signal(&term->is_initialized); + pthread_mutex_unlock(&term->mutex); + + size_t buf_size = TermBufSize(term); + char buf[buf_size]; + + while (true) { + pthread_mutex_lock(&term->mutex); + pthread_cond_wait(&term->draw_ready, &term->mutex); + + TermOut(term, buf, buf_size); + + pthread_mutex_unlock(&term->mutex); + + puts(buf); + } } -void Loop(Ctrl *ctrl, struct RecordBuffer *in_buf) +void Update(struct Terminal *term, struct Ctrl *ctrl, struct RecordBuffer *rec_buf) { - struct CharBlk4 term_blks[20 * 20]; - struct TermBuf term = NewTermBuf(term_blks, 20, 20); + pthread_mutex_lock(&term->mutex); + pthread_cond_wait(&term->is_initialized, &term->mutex); + pthread_mutex_unlock(&term->mutex); - size_t out_max = TermMaxChars(&term); - char out[out_max]; + while (true) { + // Input + pthread_mutex_lock(&rec_buf->mutex); + CtrlPoll(ctrl, rec_buf); + pthread_mutex_unlock(&rec_buf->mutex); - u8 board_blks[10 * 20]; - struct TetrMap board = NewTetrMap(board_blks, 10, 20); + // Game logic + - u8 falling_blks[4 * 4]; - struct TetrMap falling = NewTetrMap(falling_blks, 4, 4); - memcpy(falling_blks, I, falling.area); - - for (int i = 0; i < 7779997; i++) { - CtrlPoll(ctrl, in_buf); - - TetrMapToTermBuf(&board, &term); - TetrMapToTermBuf(&falling, &term); - - TermBufToChars(&term, out, out_max); - puts(out); - - WindowsWait(1.0/30); + // Draw + pthread_mutex_lock(&term->mutex); + pthread_cond_signal(&term->draw_ready); + pthread_mutex_unlock(&term->mutex); } } +void Loop(struct Ctrl *ctrl, struct RecordBuffer *rec_buf) +{ + pthread_t draw_thread; + pthread_create(&draw_thread, nullptr, Draw, &term); + + pthread_t update_thread; + pthread_create(&update_thread, nullptr, Update, &term); +} + int main() { #ifdef _WIN32 @@ -119,33 +152,43 @@ int main() exit(1); #endif - struct bkt code_bkts[code_count]; - struct dict codes = { + struct ctrl_bkt code_bkts[code_count]; + struct ctrl_dict codes = { .capacity = code_count, .filled = 0, .bkts = code_bkts }; - struct bkt bind_bkts[code_count]; - struct dict binds = { + struct ctrl_bkt bind_bkts[code_count]; + struct ctrl_dict binds = { .capacity = code_count, .filled = 0, .bkts = bind_bkts }; struct Axis axes[code_count]; - Ctrl ctrl = NewCtrl(&codes, &binds, axes); + struct Ctrl ctrl = NewCtrl(&codes, &binds, axes); for (size_t i = 0; i < code_count; i++) { const struct CtrlBind *bind = &ctrl_binds[i]; CtrlMap(&ctrl, bind->code, bind->bind, bind->type); } - struct RecordBuffer in_buf = { + struct RecordBuffer rec_buf = { .count = 0, .mutex = PTHREAD_MUTEX_INITIALIZER }; - StartInput(&ctrl, &in_buf); - Loop(&ctrl, &in_buf); + struct Instance game = { + .term = NewTerm(nullptr, 20, 20), + .ctrl = NewCtrl(), + + .on_start = NewDelegate(16), + .on_draw = NewDelegate(16), + .on_update = NewDelegate(16) + }; + + StartInput(&ctrl, &rec_buf); + Loop(&ctrl, &rec_buf); + JoinInput(&ctrl); return 0; diff --git a/source/term/term.c b/source/term/term.c index 8ea2659..135066c 100644 --- a/source/term/term.c +++ b/source/term/term.c @@ -5,33 +5,47 @@ #include #include #include +#include #include "fumotris.h" -struct CharBlk4 { +struct TChar4 { char ch; u8 bg : 4; u8 fg : 4; }; -struct TermBuf { +struct Terminal { size_t wid; size_t hgt; size_t area; - struct CharBlk4 *blks; + u16f refresh_rate; + struct TChar4 *blks; + + pthread_mutex_t mutex; + pthread_cond_t is_initialized; + pthread_cond_t draw_ready; }; -struct TermBuf NewTermBuf(struct CharBlk4 *blks, size_t wid, size_t hgt) +struct Terminal NewTerm(struct TChar4 *blks, size_t wid, size_t hgt) { size_t area = wid * hgt; - memset(blks, 0, sizeof(struct CharBlk4) * area); + memset(blks, 0, sizeof(struct TChar4) * area); - return (struct TermBuf) { - wid, hgt, area, blks + return (struct Terminal) { + .wid = wid, + .hgt = hgt, + .area = area, + .refresh_rate = 60, + .blks = blks, + + .mutex = PTHREAD_MUTEX_INITIALIZER, + .is_initialized = PTHREAD_COND_INITIALIZER, + .draw_ready = PTHREAD_COND_INITIALIZER }; } -size_t TermMaxChars(struct TermBuf *term) +size_t TermBufSize(struct Terminal *term) { static const size_t max_color_str_len = 10; static const size_t reset_str_len = 7; @@ -52,7 +66,7 @@ size_t printcol4(char *buf, size_t at, size_t max, u8f col, char ch) return snprintf(buf + at, max - at, "\x1b[%um%c", col, ch); } -size_t printblk4(char *buf, size_t at, size_t max, struct CharBlk4 *blk) +size_t printblk4(char *buf, size_t at, size_t max, struct TChar4 *blk) { u8f bg; if (blk->bg < 8) @@ -69,17 +83,17 @@ size_t printblk4(char *buf, size_t at, size_t max, struct CharBlk4 *blk) return snprintf(buf + at, max - at, "\x1b[%u;%um%c", bg, fg, blk->ch); } -size_t TermBufToChars(struct TermBuf *term, char *buf, size_t max_chars) +size_t TermOut(struct Terminal *term, char *buf, size_t n) { u8f last_bg = 0; u8f last_fg = 0; - size_t filled = snprintf(buf, max_chars, "\x1b[H\x1b[0m"); + size_t filled = snprintf(buf, n, "\x1b[H\x1b[0m"); for(size_t y = 0; y < term->hgt; y++) { for(size_t x = 0; x < term->wid; x++) { size_t i = y * term->wid + x; - struct CharBlk4 *blk = &term->blks[i]; + struct TChar4 *blk = &term->blks[i]; // DEBUG if (blk->ch == 0) @@ -89,13 +103,13 @@ size_t TermBufToChars(struct TermBuf *term, char *buf, size_t max_chars) if (blk->bg != 0 and blk->bg != last_bg) { last_bg = blk->bg; if (blk->fg != 0 and blk->fg != last_fg) { - filled += printblk4(buf, filled, max_chars, blk); + filled += printblk4(buf, filled, n, blk); last_fg = blk->fg; } else { - filled += printcol4(buf, filled, max_chars, blk->bg, blk->ch); + filled += printcol4(buf, filled, n, blk->bg, blk->ch); } } else if (blk->fg != 0 and blk->fg != last_fg) { - filled += printcol4(buf, filled, max_chars, blk->fg, blk->ch); + filled += printcol4(buf, filled, n, blk->fg, blk->ch); last_fg = blk->fg; } else { buf[filled] = blk->ch; diff --git a/source/term/term.h b/source/term/term.h index d8c9daa..3c0d554 100644 --- a/source/term/term.h +++ b/source/term/term.h @@ -6,24 +6,30 @@ #include #include #include +#include #include "fumotris.h" -struct CharBlk4 { +struct TChar4 { char ch; u8 bg : 4; u8 fg : 4; }; -struct TermBuf { +struct Terminal { size_t wid; size_t hgt; size_t area; - struct CharBlk4 *blks; + u16f refresh_rate; + struct TChar4 *blks; + + pthread_mutex_t mutex; + pthread_cond_t is_initialized; + pthread_cond_t draw_ready; }; -struct TermBuf NewTermBuf(struct CharBlk4 *blks, size_t wid, size_t hgt); +struct Terminal NewTerm(struct TChar4 *blks, size_t wid, size_t hgt); -size_t TermMaxChars(struct TermBuf *term); +size_t TermBufSize(struct Terminal *term); -size_t TermBufToChars(struct TermBuf *term, char *buf, size_t max_chars); \ No newline at end of file +size_t TermOut(struct Terminal *term, char *buf, size_t max_chars); \ No newline at end of file