AtlatestRepositorysigil-app
1
/*2
* app.c - Sigil App Module3
*4
* Wraps sokol_app.h to provide windowing, input, and application lifecycle.5
*/7
#include "app-internal.h"8
#include "sigil-internal.h"10
/* Sokol headers (implementation is in sokol.c) */11
#include "sokol_app.h"12
#include "sokol_time.h"13
#include "sokol_log.h"15
#include <stdio.h>16
#include <stdlib.h>17
#include <string.h>19
/* Global app state */20
AppState *g_app = NULL;22
/*23
* Initialize app state24
*/25
void sigil_app_state_init(SigilVM *vm)26
{27
if (g_app) return;29
g_app = calloc(1, sizeof(AppState));30
g_app->vm = vm;31
g_app->init_callback = SIGIL_FALSE;32
g_app->frame_callback = SIGIL_FALSE;33
g_app->cleanup_callback = SIGIL_FALSE;34
}36
void sigil_app_state_shutdown(void)37
{38
if (g_app) {39
free(g_app);40
g_app = NULL;41
}42
}44
/*45
* Clear per-frame input state46
*/47
void sigil_app_clear_frame_input(void)48
{49
if (!g_app) return;50
memset(g_app->keys_pressed, 0, sizeof(g_app->keys_pressed));51
memset(g_app->keys_released, 0, sizeof(g_app->keys_released));52
memset(g_app->mouse_pressed, 0, sizeof(g_app->mouse_pressed));53
memset(g_app->mouse_released, 0, sizeof(g_app->mouse_released));54
}56
/*57
* Convert Scheme key symbol to Sokol keycode58
*/59
int sigil_app_key_code_from_symbol(SigilVM *vm, Value sym)60
{61
(void)vm;62
if (!sigil_is_symbol(sym)) return SAPP_KEYCODE_INVALID;64
SigilSymbol *s = (SigilSymbol *)sigil_as_ptr(sym);65
const char *name = s->name;67
/* Letters */68
if (s->length == 1 && name[0] >= 'a' && name[0] <= 'z') {69
return SAPP_KEYCODE_A + (name[0] - 'a');70
}72
/* Numbers */73
if (s->length == 1 && name[0] >= '0' && name[0] <= '9') {74
return SAPP_KEYCODE_0 + (name[0] - '0');75
}77
/* Special keys */78
if (strcmp(name, "space") == 0) return SAPP_KEYCODE_SPACE;79
if (strcmp(name, "return") == 0 || strcmp(name, "enter") == 0) return SAPP_KEYCODE_ENTER;80
if (strcmp(name, "escape") == 0 || strcmp(name, "esc") == 0) return SAPP_KEYCODE_ESCAPE;81
if (strcmp(name, "tab") == 0) return SAPP_KEYCODE_TAB;82
if (strcmp(name, "backspace") == 0) return SAPP_KEYCODE_BACKSPACE;84
/* Arrow keys */85
if (strcmp(name, "left") == 0) return SAPP_KEYCODE_LEFT;86
if (strcmp(name, "right") == 0) return SAPP_KEYCODE_RIGHT;87
if (strcmp(name, "up") == 0) return SAPP_KEYCODE_UP;88
if (strcmp(name, "down") == 0) return SAPP_KEYCODE_DOWN;90
/* Modifiers */91
if (strcmp(name, "shift") == 0) return SAPP_KEYCODE_LEFT_SHIFT;92
if (strcmp(name, "ctrl") == 0 || strcmp(name, "control") == 0) return SAPP_KEYCODE_LEFT_CONTROL;93
if (strcmp(name, "alt") == 0) return SAPP_KEYCODE_LEFT_ALT;95
return SAPP_KEYCODE_INVALID;96
}98
/*99
* Convert mouse button symbol to index100
*/101
static int mouse_button_from_symbol(SigilVM *vm, Value sym)102
{103
(void)vm;104
if (!sigil_is_symbol(sym)) return -1;106
SigilSymbol *s = (SigilSymbol *)sigil_as_ptr(sym);107
const char *name = s->name;109
if (strcmp(name, "left") == 0) return 0;110
if (strcmp(name, "right") == 0) return 1;111
if (strcmp(name, "middle") == 0) return 2;113
return -1;114
}116
/* ============================================================117
* SOKOL CALLBACKS118
* ============================================================ */120
static uint64_t last_time = 0;122
/* Check for VM errors and print them */123
static void check_vm_error(const char *context)124
{125
if (!g_app) return;126
const char *err = sigil_error_message(g_app->vm);127
if (err) {128
fprintf(stderr, "Scheme error in %s: %s\n", context, err);129
sigil_error_clear(g_app->vm);130
sapp_request_quit();131
}132
}134
static void app_init(void)135
{136
stm_setup();137
last_time = stm_now();139
if (g_app && !sigil_is_false(g_app->init_callback)) {140
sigil_apply0(g_app->vm, g_app->init_callback);141
check_vm_error("init");142
}143
}145
static void app_frame(void)146
{147
/* Calculate frame time */148
uint64_t now = stm_now();149
g_app->frame_time = stm_sec(stm_diff(now, last_time));150
g_app->time_elapsed += g_app->frame_time;151
last_time = now;153
/* Call Scheme frame callback with delta time */154
if (g_app && !sigil_is_false(g_app->frame_callback)) {155
Value dt = sigil_flonum(g_app->frame_time);156
sigil_apply1(g_app->vm, g_app->frame_callback, dt);157
check_vm_error("frame");158
}160
/* Clear per-frame input state for next frame */161
sigil_app_clear_frame_input();162
}164
static void app_cleanup(void)165
{166
if (g_app && !sigil_is_false(g_app->cleanup_callback)) {167
sigil_apply0(g_app->vm, g_app->cleanup_callback);168
check_vm_error("cleanup");169
}170
}172
static void app_event(const sapp_event *ev)173
{174
if (!g_app) return;176
switch (ev->type) {177
case SAPP_EVENTTYPE_KEY_DOWN:178
if (ev->key_code < 512) {179
if (!g_app->keys_down[ev->key_code]) {180
g_app->keys_pressed[ev->key_code] = true;181
}182
g_app->keys_down[ev->key_code] = true;183
}184
break;186
case SAPP_EVENTTYPE_KEY_UP:187
if (ev->key_code < 512) {188
g_app->keys_down[ev->key_code] = false;189
g_app->keys_released[ev->key_code] = true;190
}191
break;193
case SAPP_EVENTTYPE_MOUSE_DOWN:194
if (ev->mouse_button < 3) {195
if (!g_app->mouse_buttons[ev->mouse_button]) {196
g_app->mouse_pressed[ev->mouse_button] = true;197
}198
g_app->mouse_buttons[ev->mouse_button] = true;199
}200
break;202
case SAPP_EVENTTYPE_MOUSE_UP:203
if (ev->mouse_button < 3) {204
g_app->mouse_buttons[ev->mouse_button] = false;205
g_app->mouse_released[ev->mouse_button] = true;206
}207
break;209
case SAPP_EVENTTYPE_MOUSE_MOVE:210
g_app->mouse_x = ev->mouse_x;211
g_app->mouse_y = ev->mouse_y;212
break;214
case SAPP_EVENTTYPE_QUIT_REQUESTED:215
g_app->quit_requested = true;216
break;218
default:219
break;220
}221
}223
/* ============================================================224
* NATIVE FUNCTIONS225
* ============================================================ */227
/*228
* (app-run init-proc frame-proc cleanup-proc [title] [width] [height])229
*230
* Run the application main loop.231
* init-proc: called once at startup232
* frame-proc: called each frame with delta-time argument233
* cleanup-proc: called before shutdown234
*/235
static Value native_app_run(SigilVM *vm, int argc, Value *args)236
{237
if (argc < 3) {238
sigil__vm_error(vm, SIGIL_ERR_ARITY, "app-run: requires init, frame, and cleanup procedures");239
return SIGIL_UNDEFINED;240
}242
/* Initialize app state */243
sigil_app_state_init(vm);245
/* Store callbacks */246
g_app->init_callback = args[0];247
g_app->frame_callback = args[1];248
g_app->cleanup_callback = args[2];250
/* Parse optional arguments */251
const char *title = "Sigil App";252
int width = 800;253
int height = 600;255
if (argc > 3 && sigil_is_string(args[3])) {256
SigilString *s = (SigilString *)sigil_as_ptr(args[3]);257
title = s->data;258
}259
if (argc > 4 && sigil_is_fixnum(args[4])) {260
width = (int)sigil_as_fixnum(args[4]);261
}262
if (argc > 5 && sigil_is_fixnum(args[5])) {263
height = (int)sigil_as_fixnum(args[5]);264
}266
/* Configure and run Sokol app */267
sapp_desc desc = {268
.init_cb = app_init,269
.frame_cb = app_frame,270
.cleanup_cb = app_cleanup,271
.event_cb = app_event,272
.width = width,273
.height = height,274
.window_title = title,275
.icon.sokol_default = true,276
.logger.func = slog_func,277
};279
sapp_run(&desc);281
/* Cleanup */282
sigil_app_state_shutdown();284
return SIGIL_NIL;285
}287
/*288
* (frame-width) -> integer289
*/290
static Value native_frame_width(SigilVM *vm, int argc, Value *args)291
{292
(void)vm; (void)argc; (void)args;293
return sigil_fixnum(sapp_width());294
}296
/*297
* (frame-height) -> integer298
*/299
static Value native_frame_height(SigilVM *vm, int argc, Value *args)300
{301
(void)vm; (void)argc; (void)args;302
return sigil_fixnum(sapp_height());303
}305
/*306
* (frame-time) -> float (seconds since last frame)307
*/308
static Value native_frame_time(SigilVM *vm, int argc, Value *args)309
{310
(void)vm; (void)argc; (void)args;311
if (!g_app) return sigil_flonum(0.0);312
return sigil_flonum(g_app->frame_time);313
}315
/*316
* (time-elapsed) -> float (seconds since app start)317
*/318
static Value native_time_elapsed(SigilVM *vm, int argc, Value *args)319
{320
(void)vm; (void)argc; (void)args;321
if (!g_app) return sigil_flonum(0.0);322
return sigil_flonum(g_app->time_elapsed);323
}325
/*326
* (mouse-x) -> float327
*/328
static Value native_mouse_x(SigilVM *vm, int argc, Value *args)329
{330
(void)vm; (void)argc; (void)args;331
if (!g_app) return sigil_flonum(0.0);332
return sigil_flonum(g_app->mouse_x);333
}335
/*336
* (mouse-y) -> float337
*/338
static Value native_mouse_y(SigilVM *vm, int argc, Value *args)339
{340
(void)vm; (void)argc; (void)args;341
if (!g_app) return sigil_flonum(0.0);342
return sigil_flonum(g_app->mouse_y);343
}345
/*346
* (mouse-down? button) -> boolean347
*/348
static Value native_mouse_down(SigilVM *vm, int argc, Value *args)349
{350
if (argc < 1) return SIGIL_FALSE;351
int btn = mouse_button_from_symbol(vm, args[0]);352
if (btn < 0 || !g_app) return SIGIL_FALSE;353
return g_app->mouse_buttons[btn] ? SIGIL_TRUE : SIGIL_FALSE;354
}356
/*357
* (mouse-pressed? button) -> boolean358
*/359
static Value native_mouse_pressed(SigilVM *vm, int argc, Value *args)360
{361
if (argc < 1) return SIGIL_FALSE;362
int btn = mouse_button_from_symbol(vm, args[0]);363
if (btn < 0 || !g_app) return SIGIL_FALSE;364
return g_app->mouse_pressed[btn] ? SIGIL_TRUE : SIGIL_FALSE;365
}367
/*368
* (mouse-released? button) -> boolean369
*/370
static Value native_mouse_released(SigilVM *vm, int argc, Value *args)371
{372
if (argc < 1) return SIGIL_FALSE;373
int btn = mouse_button_from_symbol(vm, args[0]);374
if (btn < 0 || !g_app) return SIGIL_FALSE;375
return g_app->mouse_released[btn] ? SIGIL_TRUE : SIGIL_FALSE;376
}378
/*379
* (key-down? key) -> boolean380
*/381
static Value native_key_down(SigilVM *vm, int argc, Value *args)382
{383
if (argc < 1) return SIGIL_FALSE;384
int key = sigil_app_key_code_from_symbol(vm, args[0]);385
if (key == SAPP_KEYCODE_INVALID || !g_app) return SIGIL_FALSE;386
return g_app->keys_down[key] ? SIGIL_TRUE : SIGIL_FALSE;387
}389
/*390
* (key-pressed? key) -> boolean391
*/392
static Value native_key_pressed(SigilVM *vm, int argc, Value *args)393
{394
if (argc < 1) return SIGIL_FALSE;395
int key = sigil_app_key_code_from_symbol(vm, args[0]);396
if (key == SAPP_KEYCODE_INVALID || !g_app) return SIGIL_FALSE;397
return g_app->keys_pressed[key] ? SIGIL_TRUE : SIGIL_FALSE;398
}400
/*401
* (key-released? key) -> boolean402
*/403
static Value native_key_released(SigilVM *vm, int argc, Value *args)404
{405
if (argc < 1) return SIGIL_FALSE;406
int key = sigil_app_key_code_from_symbol(vm, args[0]);407
if (key == SAPP_KEYCODE_INVALID || !g_app) return SIGIL_FALSE;408
return g_app->keys_released[key] ? SIGIL_TRUE : SIGIL_FALSE;409
}411
/*412
* (quit-requested?) -> boolean413
*/414
static Value native_quit_requested(SigilVM *vm, int argc, Value *args)415
{416
(void)vm; (void)argc; (void)args;417
if (!g_app) return SIGIL_FALSE;418
return g_app->quit_requested ? SIGIL_TRUE : SIGIL_FALSE;419
}421
/*422
* (request-quit)423
*/424
static Value native_request_quit(SigilVM *vm, int argc, Value *args)425
{426
(void)vm; (void)argc; (void)args;427
sapp_request_quit();428
return SIGIL_NIL;429
}431
/* ============================================================432
* MODULE INITIALIZATION433
* ============================================================ */435
void sigil__init_sigil_app_module(SigilVM *vm)436
{437
SigilModule *module = sigil_begin_module(vm, "(sigil app)");438
if (!module) return;440
/* Application lifecycle */441
sigil_module_register_native(vm, "app-run", native_app_run,442
SIGIL_ARITY_RANGE(3, 6),443
"Run application with init/frame/cleanup callbacks");445
/* Frame info */446
sigil_module_register_native(vm, "frame-width", native_frame_width,447
SIGIL_ARITY_EXACT(0), "Get frame buffer width");448
sigil_module_register_native(vm, "frame-height", native_frame_height,449
SIGIL_ARITY_EXACT(0), "Get frame buffer height");450
sigil_module_register_native(vm, "frame-time", native_frame_time,451
SIGIL_ARITY_EXACT(0), "Seconds since last frame");452
sigil_module_register_native(vm, "time-elapsed", native_time_elapsed,453
SIGIL_ARITY_EXACT(0), "Seconds since app start");455
/* Mouse input */456
sigil_module_register_native(vm, "mouse-x", native_mouse_x,457
SIGIL_ARITY_EXACT(0), "Mouse X position");458
sigil_module_register_native(vm, "mouse-y", native_mouse_y,459
SIGIL_ARITY_EXACT(0), "Mouse Y position");460
sigil_module_register_native(vm, "mouse-down?", native_mouse_down,461
SIGIL_ARITY_EXACT(1), "Is mouse button held?");462
sigil_module_register_native(vm, "mouse-pressed?", native_mouse_pressed,463
SIGIL_ARITY_EXACT(1), "Was mouse button just pressed?");464
sigil_module_register_native(vm, "mouse-released?", native_mouse_released,465
SIGIL_ARITY_EXACT(1), "Was mouse button just released?");467
/* Keyboard input */468
sigil_module_register_native(vm, "key-down?", native_key_down,469
SIGIL_ARITY_EXACT(1), "Is key held?");470
sigil_module_register_native(vm, "key-pressed?", native_key_pressed,471
SIGIL_ARITY_EXACT(1), "Was key just pressed?");472
sigil_module_register_native(vm, "key-released?", native_key_released,473
SIGIL_ARITY_EXACT(1), "Was key just released?");475
/* Quit handling */476
sigil_module_register_native(vm, "quit-requested?", native_quit_requested,477
SIGIL_ARITY_EXACT(0), "Was quit requested?");478
sigil_module_register_native(vm, "request-quit", native_request_quit,479
SIGIL_ARITY_EXACT(0), "Request application quit");481
/* Export all */482
sigil_module_export(vm, "app-run");483
sigil_module_export(vm, "frame-width");484
sigil_module_export(vm, "frame-height");485
sigil_module_export(vm, "frame-time");486
sigil_module_export(vm, "time-elapsed");487
sigil_module_export(vm, "mouse-x");488
sigil_module_export(vm, "mouse-y");489
sigil_module_export(vm, "mouse-down?");490
sigil_module_export(vm, "mouse-pressed?");491
sigil_module_export(vm, "mouse-released?");492
sigil_module_export(vm, "key-down?");493
sigil_module_export(vm, "key-pressed?");494
sigil_module_export(vm, "key-released?");495
sigil_module_export(vm, "quit-requested?");496
sigil_module_export(vm, "request-quit");498
sigil_end_module(vm);499
}