From d0f5dc6951c4baaa6cdb49bcc5f1b0c42d4ee0b9 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Wed, 10 Dec 2025 15:01:20 -0600 Subject: [PATCH] playdate cake --- cake/playdate.cm | 1 + playdate/common.h | 64 +++++ playdate/display_playdate.c | 91 +++++++ playdate/file_playdate.c | 175 +++++++++++++ playdate/gfx_playdate.c | 339 ++++++++++++++++++++++++ playdate/json_playdate.c | 202 ++++++++++++++ playdate/lua_playdate.c | 157 +++++++++++ playdate/network_playdate.c | 290 +++++++++++++++++++++ playdate/scoreboards_playdate.c | 176 +++++++++++++ playdate/sound_playdate.c | 448 ++++++++++++++++++++++++++++++++ playdate/sprite_playdate.c | 395 ++++++++++++++++++++++++++++ playdate/sys_playdate.c | 267 +++++++++++++++++++ source/main_playdate.c | 18 +- toolchains.cm | 16 +- 14 files changed, 2635 insertions(+), 4 deletions(-) create mode 100644 cake/playdate.cm create mode 100644 playdate/common.h create mode 100644 playdate/display_playdate.c create mode 100644 playdate/file_playdate.c create mode 100644 playdate/gfx_playdate.c create mode 100644 playdate/json_playdate.c create mode 100644 playdate/lua_playdate.c create mode 100644 playdate/network_playdate.c create mode 100644 playdate/scoreboards_playdate.c create mode 100644 playdate/sound_playdate.c create mode 100644 playdate/sprite_playdate.c create mode 100644 playdate/sys_playdate.c diff --git a/cake/playdate.cm b/cake/playdate.cm new file mode 100644 index 00000000..e03770ec --- /dev/null +++ b/cake/playdate.cm @@ -0,0 +1 @@ +// cake file for making a playdate package \ No newline at end of file diff --git a/playdate/common.h b/playdate/common.h new file mode 100644 index 00000000..551889e9 --- /dev/null +++ b/playdate/common.h @@ -0,0 +1,64 @@ +// playdate/common.h - Common declarations for Playdate Cell integration +// This header declares the global API pointers that are initialized in main_playdate.c +// and used by all the Playdate integration modules. + +#ifndef PLAYDATE_COMMON_H +#define PLAYDATE_COMMON_H + +#include "pd_api/pd_api_file.h" +#include "pd_api/pd_api_gfx.h" +#include "pd_api/pd_api_sys.h" +#include "pd_api/pd_api_sound.h" +#include "pd_api/pd_api_sprite.h" +#include "pd_api/pd_api_display.h" +#include "pd_api/pd_api_json.h" +#include "pd_api/pd_api_lua.h" +#include "pd_api/pd_api_network.h" +#include "pd_api/pd_api_scoreboards.h" + +// Forward declare the main PlaydateAPI struct +typedef struct PlaydateAPI PlaydateAPI; + +struct PlaydateAPI +{ + const struct playdate_sys *system; + const struct playdate_file *file; + const struct playdate_graphics *graphics; + const struct playdate_sprite *sprite; + const struct playdate_display *display; + const struct playdate_sound *sound; + const struct playdate_lua *lua; + const struct playdate_json *json; + const struct playdate_scoreboards *scoreboards; + const struct playdate_network *network; +}; + +// System events +typedef enum +{ + kEventInit, + kEventInitLua, + kEventLock, + kEventUnlock, + kEventPause, + kEventResume, + kEventTerminate, + kEventKeyPressed, + kEventKeyReleased, + kEventLowPower +} PDSystemEvent; + +// Global API pointers - defined in main_playdate.c +extern PlaydateAPI *pd; +extern const struct playdate_file *pd_file; +extern const struct playdate_sys *pd_sys; +extern const struct playdate_graphics *pd_gfx; +extern const struct playdate_sprite *pd_sprite; +extern const struct playdate_display *pd_display; +extern const struct playdate_sound *pd_sound; +extern const struct playdate_lua *pd_lua; +extern const struct playdate_json *pd_json; +extern const struct playdate_scoreboards *pd_scoreboards; +extern const struct playdate_network *pd_network; + +#endif // PLAYDATE_COMMON_H diff --git a/playdate/display_playdate.c b/playdate/display_playdate.c new file mode 100644 index 00000000..5ad15a70 --- /dev/null +++ b/playdate/display_playdate.c @@ -0,0 +1,91 @@ +// display_playdate.c - Cell integration for Playdate Display API +// Wraps pd_api_display.h functions for JavaScript access + +#include "cell.h" +#include "common.h" + +// --- Display API Wrappers --- + +JSC_CCALL(display_getWidth, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + return JS_NewInt32(js, pd_display->getWidth()); +) + +JSC_CCALL(display_getHeight, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + return JS_NewInt32(js, pd_display->getHeight()); +) + +JSC_CCALL(display_setRefreshRate, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + float rate = (float)js2number(js, argv[0]); + pd_display->setRefreshRate(rate); + return JS_UNDEFINED; +) + +JSC_CCALL(display_getRefreshRate, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + return JS_NewFloat64(js, pd_display->getRefreshRate()); +) + +JSC_CCALL(display_getFPS, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + return JS_NewFloat64(js, pd_display->getFPS()); +) + +JSC_CCALL(display_setInverted, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + int flag = JS_ToBool(js, argv[0]); + pd_display->setInverted(flag); + return JS_UNDEFINED; +) + +JSC_CCALL(display_setScale, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + unsigned int scale = (unsigned int)js2number(js, argv[0]); + pd_display->setScale(scale); + return JS_UNDEFINED; +) + +JSC_CCALL(display_setMosaic, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + unsigned int x = (unsigned int)js2number(js, argv[0]); + unsigned int y = (unsigned int)js2number(js, argv[1]); + pd_display->setMosaic(x, y); + return JS_UNDEFINED; +) + +JSC_CCALL(display_setFlipped, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + int x = JS_ToBool(js, argv[0]); + int y = JS_ToBool(js, argv[1]); + pd_display->setFlipped(x, y); + return JS_UNDEFINED; +) + +JSC_CCALL(display_setOffset, + if (!pd_display) return JS_ThrowInternalError(js, "display not initialized"); + int x = (int)js2number(js, argv[0]); + int y = (int)js2number(js, argv[1]); + pd_display->setOffset(x, y); + return JS_UNDEFINED; +) + +static const JSCFunctionListEntry js_display_funcs[] = { + MIST_FUNC_DEF(display, getWidth, 0), + MIST_FUNC_DEF(display, getHeight, 0), + MIST_FUNC_DEF(display, setRefreshRate, 1), + MIST_FUNC_DEF(display, getRefreshRate, 0), + MIST_FUNC_DEF(display, getFPS, 0), + MIST_FUNC_DEF(display, setInverted, 1), + MIST_FUNC_DEF(display, setScale, 1), + MIST_FUNC_DEF(display, setMosaic, 2), + MIST_FUNC_DEF(display, setFlipped, 2), + MIST_FUNC_DEF(display, setOffset, 2), +}; + +JSValue js_display_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_display_funcs, countof(js_display_funcs)); + return mod; +} diff --git a/playdate/file_playdate.c b/playdate/file_playdate.c new file mode 100644 index 00000000..32f65535 --- /dev/null +++ b/playdate/file_playdate.c @@ -0,0 +1,175 @@ +// file_playdate.c - Cell integration for Playdate File API +// Wraps pd_api_file.h functions for JavaScript access +// Note: fd_playdate.c already provides the main file operations. +// This module provides additional raw file API access. + +#include "cell.h" +#include "common.h" +#include + +// --- Helpers --- +static SDFile* js2sdfile(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (SDFile*)(intptr_t)ptr; +} + +// --- File Functions --- + +JSC_CCALL(file_geterr, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + const char *err = pd_file->geterr(); + return err ? JS_NewString(js, err) : JS_NULL; +) + +JSC_SCALL(file_stat, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + FileStat st; + if (pd_file->stat(str, &st) != 0) { + ret = JS_NULL; + } else { + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "isdir", JS_NewBool(js, st.isdir)); + JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.size)); + JS_SetPropertyStr(js, obj, "year", JS_NewInt32(js, st.m_year)); + JS_SetPropertyStr(js, obj, "month", JS_NewInt32(js, st.m_month)); + JS_SetPropertyStr(js, obj, "day", JS_NewInt32(js, st.m_day)); + JS_SetPropertyStr(js, obj, "hour", JS_NewInt32(js, st.m_hour)); + JS_SetPropertyStr(js, obj, "minute", JS_NewInt32(js, st.m_minute)); + JS_SetPropertyStr(js, obj, "second", JS_NewInt32(js, st.m_second)); + ret = obj; + } +) + +JSC_SCALL(file_mkdir, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + ret = JS_NewBool(js, pd_file->mkdir(str) == 0); +) + +JSC_SCALL(file_unlink, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + int recursive = argc > 1 ? JS_ToBool(js, argv[1]) : 0; + ret = JS_NewBool(js, pd_file->unlink(str, recursive) == 0); +) + +JSC_SCALL(file_rename, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + const char *to = JS_ToCString(js, argv[1]); + int result = pd_file->rename(str, to); + JS_FreeCString(js, to); + ret = JS_NewBool(js, result == 0); +) + +JSC_SCALL(file_open, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + FileOptions mode = kFileRead; + if (argc > 1) mode = (FileOptions)(int)js2number(js, argv[1]); + SDFile *f = pd_file->open(str, mode); + ret = f ? JS_NewInt64(js, (int64_t)(intptr_t)f) : JS_NULL; +) + +JSC_CCALL(file_close, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + SDFile *f = js2sdfile(js, argv[0]); + return JS_NewBool(js, pd_file->close(f) == 0); +) + +JSC_CCALL(file_read, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + SDFile *f = js2sdfile(js, argv[0]); + unsigned int len = (unsigned int)js2number(js, argv[1]); + void *buf = malloc(len); + if (!buf) return JS_ThrowInternalError(js, "malloc failed"); + int read = pd_file->read(f, buf, len); + if (read < 0) { + free(buf); + return JS_NULL; + } + JSValue blob = js_new_blob_stoned_copy(js, buf, read); + free(buf); + return blob; +) + +JSC_CCALL(file_write, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + SDFile *f = js2sdfile(js, argv[0]); + size_t len; + const void *data = js_get_blob_data(js, &len, argv[1]); + if (data == (void*)-1) return JS_EXCEPTION; + int written = pd_file->write(f, data, (unsigned int)len); + return JS_NewInt32(js, written); +) + +JSC_CCALL(file_flush, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + SDFile *f = js2sdfile(js, argv[0]); + return JS_NewBool(js, pd_file->flush(f) == 0); +) + +JSC_CCALL(file_tell, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + SDFile *f = js2sdfile(js, argv[0]); + return JS_NewInt32(js, pd_file->tell(f)); +) + +JSC_CCALL(file_seek, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + SDFile *f = js2sdfile(js, argv[0]); + int pos = (int)js2number(js, argv[1]); + int whence = argc > 2 ? (int)js2number(js, argv[2]) : SEEK_SET; + return JS_NewBool(js, pd_file->seek(f, pos, whence) == 0); +) + +struct listfiles_ctx { + JSContext *js; + JSValue array; + int index; +}; + +static void listfiles_cb(const char *path, void *userdata) { + struct listfiles_ctx *ctx = (struct listfiles_ctx*)userdata; + JS_SetPropertyUint32(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path)); +} + +JSC_SCALL(file_listfiles, + if (!pd_file) return JS_ThrowInternalError(js, "file not initialized"); + int showhidden = argc > 1 ? JS_ToBool(js, argv[1]) : 0; + JSValue arr = JS_NewArray(js); + struct listfiles_ctx ctx = { js, arr, 0 }; + if (pd_file->listfiles(str, listfiles_cb, &ctx, showhidden) != 0) { + JS_FreeValue(js, arr); + ret = JS_NULL; + } else { + ret = arr; + } +) + +static const JSCFunctionListEntry js_file_funcs[] = { + MIST_FUNC_DEF(file, geterr, 0), + MIST_FUNC_DEF(file, stat, 1), + MIST_FUNC_DEF(file, mkdir, 1), + MIST_FUNC_DEF(file, unlink, 2), + MIST_FUNC_DEF(file, rename, 2), + MIST_FUNC_DEF(file, open, 2), + MIST_FUNC_DEF(file, close, 1), + MIST_FUNC_DEF(file, read, 2), + MIST_FUNC_DEF(file, write, 2), + MIST_FUNC_DEF(file, flush, 1), + MIST_FUNC_DEF(file, tell, 1), + MIST_FUNC_DEF(file, seek, 3), + MIST_FUNC_DEF(file, listfiles, 2), +}; + +JSValue js_file_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_file_funcs, countof(js_file_funcs)); + // Export constants + JS_SetPropertyStr(js, mod, "READ", JS_NewInt32(js, kFileRead)); + JS_SetPropertyStr(js, mod, "READ_DATA", JS_NewInt32(js, kFileReadData)); + JS_SetPropertyStr(js, mod, "WRITE", JS_NewInt32(js, kFileWrite)); + JS_SetPropertyStr(js, mod, "APPEND", JS_NewInt32(js, kFileAppend)); + JS_SetPropertyStr(js, mod, "SEEK_SET", JS_NewInt32(js, SEEK_SET)); + JS_SetPropertyStr(js, mod, "SEEK_CUR", JS_NewInt32(js, SEEK_CUR)); + JS_SetPropertyStr(js, mod, "SEEK_END", JS_NewInt32(js, SEEK_END)); + return mod; +} diff --git a/playdate/gfx_playdate.c b/playdate/gfx_playdate.c new file mode 100644 index 00000000..69a0444c --- /dev/null +++ b/playdate/gfx_playdate.c @@ -0,0 +1,339 @@ +// gfx_playdate.c - Cell integration for Playdate Graphics API +// Wraps pd_api_gfx.h functions for JavaScript access + +#include "cell.h" +#include "common.h" +#include + +// --- Helper: Convert JS value to LCDBitmap* --- +static LCDBitmap* js2bitmap(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (LCDBitmap*)(intptr_t)ptr; +} + +static LCDBitmapTable* js2bitmaptable(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (LCDBitmapTable*)(intptr_t)ptr; +} + +static LCDFont* js2font(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (LCDFont*)(intptr_t)ptr; +} + +// --- Drawing Functions --- + +JSC_CCALL(gfx_clear, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDColor color = (LCDColor)js2number(js, argv[0]); + pd_gfx->clear(color); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setBackgroundColor, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDSolidColor color = (LCDSolidColor)(int)js2number(js, argv[0]); + pd_gfx->setBackgroundColor(color); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setDrawMode, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmapDrawMode mode = (LCDBitmapDrawMode)(int)js2number(js, argv[0]); + LCDBitmapDrawMode prev = pd_gfx->setDrawMode(mode); + return JS_NewInt32(js, prev); +) + +JSC_CCALL(gfx_setDrawOffset, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->setDrawOffset((int)js2number(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setClipRect, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->setClipRect((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_clearClipRect, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->clearClipRect(); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setLineCapStyle, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->setLineCapStyle((LCDLineCapStyle)(int)js2number(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setFont, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->setFont(js2font(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setTextTracking, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->setTextTracking((int)js2number(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_getTextTracking, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + return JS_NewInt32(js, pd_gfx->getTextTracking()); +) + +JSC_CCALL(gfx_pushContext, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->pushContext(argc > 0 ? js2bitmap(js, argv[0]) : NULL); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_popContext, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->popContext(); + return JS_UNDEFINED; +) + +// --- Drawing Primitives --- + +JSC_CCALL(gfx_drawBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap"); + pd_gfx->drawBitmap(bmp, (int)js2number(js, argv[1]), (int)js2number(js, argv[2]), + argc > 3 ? (LCDBitmapFlip)(int)js2number(js, argv[3]) : kBitmapUnflipped); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_drawLine, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->drawLine((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3]), + (int)js2number(js, argv[4]), (LCDColor)js2number(js, argv[5])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_drawRect, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->drawRect((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3]), + (LCDColor)js2number(js, argv[4])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_fillRect, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->fillRect((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3]), + (LCDColor)js2number(js, argv[4])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_fillTriangle, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->fillTriangle((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3]), + (int)js2number(js, argv[4]), (int)js2number(js, argv[5]), + (LCDColor)js2number(js, argv[6])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_drawEllipse, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->drawEllipse((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3]), + (int)js2number(js, argv[4]), (float)js2number(js, argv[5]), + (float)js2number(js, argv[6]), (LCDColor)js2number(js, argv[7])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_fillEllipse, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->fillEllipse((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (int)js2number(js, argv[2]), (int)js2number(js, argv[3]), + (float)js2number(js, argv[4]), (float)js2number(js, argv[5]), + (LCDColor)js2number(js, argv[6])); + return JS_UNDEFINED; +) + +JSC_SCALL(gfx_drawText, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + int w = pd_gfx->drawText(str, strlen(str), kUTF8Encoding, + (int)js2number(js, argv[1]), (int)js2number(js, argv[2])); + ret = JS_NewInt32(js, w); +) + +JSC_CCALL(gfx_drawScaledBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap"); + pd_gfx->drawScaledBitmap(bmp, (int)js2number(js, argv[1]), (int)js2number(js, argv[2]), + (float)js2number(js, argv[3]), (float)js2number(js, argv[4])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_drawRotatedBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap"); + pd_gfx->drawRotatedBitmap(bmp, (int)js2number(js, argv[1]), (int)js2number(js, argv[2]), + (float)js2number(js, argv[3]), (float)js2number(js, argv[4]), + (float)js2number(js, argv[5]), (float)js2number(js, argv[6]), + (float)js2number(js, argv[7])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_setPixel, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->setPixel((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + (LCDColor)js2number(js, argv[2])); + return JS_UNDEFINED; +) + +// --- Bitmap Functions --- + +JSC_CCALL(gfx_newBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = pd_gfx->newBitmap((int)js2number(js, argv[0]), (int)js2number(js, argv[1]), + argc > 2 ? (LCDColor)js2number(js, argv[2]) : kColorWhite); + if (!bmp) return JS_ThrowInternalError(js, "failed to create bitmap"); + return JS_NewInt64(js, (int64_t)(intptr_t)bmp); +) + +JSC_CCALL(gfx_freeBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (bmp) pd_gfx->freeBitmap(bmp); + return JS_UNDEFINED; +) + +JSC_SCALL(gfx_loadBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + const char *err = NULL; + LCDBitmap *bmp = pd_gfx->loadBitmap(str, &err); + if (!bmp) ret = JS_ThrowInternalError(js, "loadBitmap: %s", err ? err : "unknown"); + else ret = JS_NewInt64(js, (int64_t)(intptr_t)bmp); +) + +JSC_CCALL(gfx_copyBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap"); + LCDBitmap *copy = pd_gfx->copyBitmap(bmp); + return copy ? JS_NewInt64(js, (int64_t)(intptr_t)copy) : JS_NULL; +) + +JSC_CCALL(gfx_clearBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap"); + pd_gfx->clearBitmap(bmp, argc > 1 ? (LCDColor)js2number(js, argv[1]) : kColorWhite); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_getBitmapData, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = js2bitmap(js, argv[0]); + if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap"); + int w, h, rb; uint8_t *mask, *data; + pd_gfx->getBitmapData(bmp, &w, &h, &rb, &mask, &data); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, w)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, h)); + JS_SetPropertyStr(js, obj, "rowbytes", JS_NewInt32(js, rb)); + return obj; +) + +// --- Font Functions --- + +JSC_SCALL(gfx_loadFont, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + const char *err = NULL; + LCDFont *font = pd_gfx->loadFont(str, &err); + if (!font) ret = JS_ThrowInternalError(js, "loadFont: %s", err ? err : "unknown"); + else ret = JS_NewInt64(js, (int64_t)(intptr_t)font); +) + +JSC_CCALL(gfx_getFontHeight, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDFont *font = js2font(js, argv[0]); + return JS_NewInt32(js, pd_gfx->getFontHeight(font)); +) + +JSC_SCALL(gfx_getTextWidth, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDFont *font = argc > 1 ? js2font(js, argv[1]) : NULL; + int tracking = argc > 2 ? (int)js2number(js, argv[2]) : 0; + ret = JS_NewInt32(js, pd_gfx->getTextWidth(font, str, strlen(str), kUTF8Encoding, tracking)); +) + +// --- Framebuffer --- + +JSC_CCALL(gfx_display, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->display(); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_markUpdatedRows, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + pd_gfx->markUpdatedRows((int)js2number(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(gfx_copyFrameBufferBitmap, + if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized"); + LCDBitmap *bmp = pd_gfx->copyFrameBufferBitmap(); + return bmp ? JS_NewInt64(js, (int64_t)(intptr_t)bmp) : JS_NULL; +) + +static const JSCFunctionListEntry js_gfx_funcs[] = { + MIST_FUNC_DEF(gfx, clear, 1), + MIST_FUNC_DEF(gfx, setBackgroundColor, 1), + MIST_FUNC_DEF(gfx, setDrawMode, 1), + MIST_FUNC_DEF(gfx, setDrawOffset, 2), + MIST_FUNC_DEF(gfx, setClipRect, 4), + MIST_FUNC_DEF(gfx, clearClipRect, 0), + MIST_FUNC_DEF(gfx, setLineCapStyle, 1), + MIST_FUNC_DEF(gfx, setFont, 1), + MIST_FUNC_DEF(gfx, setTextTracking, 1), + MIST_FUNC_DEF(gfx, getTextTracking, 0), + MIST_FUNC_DEF(gfx, pushContext, 1), + MIST_FUNC_DEF(gfx, popContext, 0), + MIST_FUNC_DEF(gfx, drawBitmap, 4), + MIST_FUNC_DEF(gfx, drawLine, 6), + MIST_FUNC_DEF(gfx, drawRect, 5), + MIST_FUNC_DEF(gfx, fillRect, 5), + MIST_FUNC_DEF(gfx, fillTriangle, 7), + MIST_FUNC_DEF(gfx, drawEllipse, 8), + MIST_FUNC_DEF(gfx, fillEllipse, 7), + MIST_FUNC_DEF(gfx, drawText, 3), + MIST_FUNC_DEF(gfx, drawScaledBitmap, 5), + MIST_FUNC_DEF(gfx, drawRotatedBitmap, 8), + MIST_FUNC_DEF(gfx, setPixel, 3), + MIST_FUNC_DEF(gfx, newBitmap, 3), + MIST_FUNC_DEF(gfx, freeBitmap, 1), + MIST_FUNC_DEF(gfx, loadBitmap, 1), + MIST_FUNC_DEF(gfx, copyBitmap, 1), + MIST_FUNC_DEF(gfx, clearBitmap, 2), + MIST_FUNC_DEF(gfx, getBitmapData, 1), + MIST_FUNC_DEF(gfx, loadFont, 1), + MIST_FUNC_DEF(gfx, getFontHeight, 1), + MIST_FUNC_DEF(gfx, getTextWidth, 3), + MIST_FUNC_DEF(gfx, display, 0), + MIST_FUNC_DEF(gfx, markUpdatedRows, 2), + MIST_FUNC_DEF(gfx, copyFrameBufferBitmap, 0), +}; + +JSValue js_gfx_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_gfx_funcs, countof(js_gfx_funcs)); + return mod; +} diff --git a/playdate/json_playdate.c b/playdate/json_playdate.c new file mode 100644 index 00000000..4c0d933e --- /dev/null +++ b/playdate/json_playdate.c @@ -0,0 +1,202 @@ +// json_playdate.c - Cell integration for Playdate JSON API +// Wraps pd_api_json.h functions for JavaScript access + +#include "cell.h" +#include "common.h" +#include +#include + +// --- JSON Decoder Context --- +typedef struct { + JSContext *js; + JSValue result; + JSValue stack[32]; + int stack_depth; +} JsonDecodeCtx; + +static void json_decode_error(json_decoder *decoder, const char *error, int linenum) { + JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; + ctx->result = JS_ThrowSyntaxError(ctx->js, "JSON parse error at line %d: %s", linenum, error); +} + +static void json_decode_will_sublist(json_decoder *decoder, const char *name, json_value_type type) { + JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; + JSValue container = (type == kJSONArray) ? JS_NewArray(ctx->js) : JS_NewObject(ctx->js); + if (ctx->stack_depth < 32) { + ctx->stack[ctx->stack_depth++] = container; + } +} + +static void* json_decode_did_sublist(json_decoder *decoder, const char *name, json_value_type type) { + JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; + if (ctx->stack_depth > 0) { + ctx->stack_depth--; + JSValue completed = ctx->stack[ctx->stack_depth]; + if (ctx->stack_depth == 0) { + ctx->result = completed; + } + return NULL; + } + return NULL; +} + +static void json_decode_table_value(json_decoder *decoder, const char *key, json_value value) { + JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; + if (ctx->stack_depth <= 0) return; + JSValue container = ctx->stack[ctx->stack_depth - 1]; + JSValue jsval; + switch (value.type) { + case kJSONNull: jsval = JS_NULL; break; + case kJSONTrue: jsval = JS_TRUE; break; + case kJSONFalse: jsval = JS_FALSE; break; + case kJSONInteger: jsval = JS_NewInt32(ctx->js, value.data.intval); break; + case kJSONFloat: jsval = JS_NewFloat64(ctx->js, value.data.floatval); break; + case kJSONString: jsval = JS_NewString(ctx->js, value.data.stringval); break; + case kJSONArray: + case kJSONTable: + if (ctx->stack_depth > 1) { + jsval = ctx->stack[ctx->stack_depth - 1]; + } else { + jsval = JS_UNDEFINED; + } + break; + default: jsval = JS_UNDEFINED; break; + } + JS_SetPropertyStr(ctx->js, container, key, jsval); +} + +static void json_decode_array_value(json_decoder *decoder, int pos, json_value value) { + JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; + if (ctx->stack_depth <= 0) return; + JSValue container = ctx->stack[ctx->stack_depth - 1]; + JSValue jsval; + switch (value.type) { + case kJSONNull: jsval = JS_NULL; break; + case kJSONTrue: jsval = JS_TRUE; break; + case kJSONFalse: jsval = JS_FALSE; break; + case kJSONInteger: jsval = JS_NewInt32(ctx->js, value.data.intval); break; + case kJSONFloat: jsval = JS_NewFloat64(ctx->js, value.data.floatval); break; + case kJSONString: jsval = JS_NewString(ctx->js, value.data.stringval); break; + default: jsval = JS_UNDEFINED; break; + } + JS_SetPropertyUint32(ctx->js, container, pos, jsval); +} + +// --- JSON Encoder Context --- +typedef struct { + char *buffer; + size_t len; + size_t cap; +} JsonEncodeCtx; + +static void json_encode_write(void *userdata, const char *str, int len) { + JsonEncodeCtx *ctx = (JsonEncodeCtx*)userdata; + if (ctx->len + len + 1 > ctx->cap) { + ctx->cap = (ctx->cap + len + 1) * 2; + ctx->buffer = realloc(ctx->buffer, ctx->cap); + } + memcpy(ctx->buffer + ctx->len, str, len); + ctx->len += len; + ctx->buffer[ctx->len] = '\0'; +} + +static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val); + +static void encode_js_object(json_encoder *enc, JSContext *js, JSValue obj) { + enc->startTable(enc); + JSPropertyEnum *props; + uint32_t len; + if (JS_GetOwnPropertyNames(js, &props, &len, obj, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) == 0) { + for (uint32_t i = 0; i < len; i++) { + const char *key = JS_AtomToCString(js, props[i].atom); + JSValue val = JS_GetProperty(js, obj, props[i].atom); + enc->addTableMember(enc, key, strlen(key)); + encode_js_value(enc, js, val); + JS_FreeValue(js, val); + JS_FreeCString(js, key); + JS_FreeAtom(js, props[i].atom); + } + js_free(js, props); + } + enc->endTable(enc); +} + +static void encode_js_array(json_encoder *enc, JSContext *js, JSValue arr) { + enc->startArray(enc); + JSValue lenVal = JS_GetPropertyStr(js, arr, "length"); + int len = (int)js2number(js, lenVal); + JS_FreeValue(js, lenVal); + for (int i = 0; i < len; i++) { + enc->addArrayMember(enc); + JSValue val = JS_GetPropertyUint32(js, arr, i); + encode_js_value(enc, js, val); + JS_FreeValue(js, val); + } + enc->endArray(enc); +} + +static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) { + if (JS_IsNull(val) || JS_IsUndefined(val)) { + enc->writeNull(enc); + } else if (JS_IsBool(val)) { + if (JS_ToBool(js, val)) enc->writeTrue(enc); + else enc->writeFalse(enc); + } else if (JS_IsNumber(val)) { + double d = js2number(js, val); + if (d == (int)d) enc->writeInt(enc, (int)d); + else enc->writeDouble(enc, d); + } else if (JS_IsString(val)) { + size_t len; + const char *str = JS_ToCStringLen(js, &len, val); + enc->writeString(enc, str, len); + JS_FreeCString(js, str); + } else if (JS_IsArray(js, val)) { + encode_js_array(enc, js, val); + } else if (JS_IsObject(val)) { + encode_js_object(enc, js, val); + } else { + enc->writeNull(enc); + } +} + +// --- API Functions --- + +JSC_SCALL(json_decodeString, + if (!pd_json) return JS_ThrowInternalError(js, "json not initialized"); + JsonDecodeCtx ctx = { js, JS_UNDEFINED, {}, 0 }; + json_decoder decoder = {0}; + decoder.decodeError = json_decode_error; + decoder.willDecodeSublist = json_decode_will_sublist; + decoder.didDecodeSublist = json_decode_did_sublist; + decoder.didDecodeTableValue = json_decode_table_value; + decoder.didDecodeArrayValue = json_decode_array_value; + decoder.userdata = &ctx; + json_value outval; + pd_json->decodeString(&decoder, str, &outval); + ret = ctx.result; +) + +JSC_CCALL(json_encode, + if (!pd_json) return JS_ThrowInternalError(js, "json not initialized"); + JsonEncodeCtx ctx = { malloc(256), 0, 256 }; + if (!ctx.buffer) return JS_ThrowInternalError(js, "malloc failed"); + ctx.buffer[0] = '\0'; + json_encoder enc; + int pretty = argc > 1 ? JS_ToBool(js, argv[1]) : 0; + pd_json->initEncoder(&enc, json_encode_write, &ctx, pretty); + encode_js_value(&enc, js, argv[0]); + JSValue result = JS_NewStringLen(js, ctx.buffer, ctx.len); + free(ctx.buffer); + return result; +) + +static const JSCFunctionListEntry js_json_funcs[] = { + MIST_FUNC_DEF(json, decodeString, 1), + MIST_FUNC_DEF(json, encode, 2), +}; + +JSValue js_json_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_json_funcs, countof(js_json_funcs)); + return mod; +} diff --git a/playdate/lua_playdate.c b/playdate/lua_playdate.c new file mode 100644 index 00000000..822d3069 --- /dev/null +++ b/playdate/lua_playdate.c @@ -0,0 +1,157 @@ +// lua_playdate.c - Cell integration for Playdate Lua API +// Wraps pd_api_lua.h functions for JavaScript access +// Note: This provides interop with Playdate's Lua runtime if needed + +#include "cell.h" +#include "common.h" +#include + +// --- Lua API Functions --- + +JSC_CCALL(lua_stop, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->stop(); + return JS_UNDEFINED; +) + +JSC_CCALL(lua_start, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->start(); + return JS_UNDEFINED; +) + +JSC_CCALL(lua_getArgCount, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + return JS_NewInt32(js, pd_lua->getArgCount()); +) + +JSC_CCALL(lua_getArgType, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + int pos = (int)js2number(js, argv[0]); + const char *outClass = NULL; + enum LuaType type = pd_lua->getArgType(pos, &outClass); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "type", JS_NewInt32(js, type)); + JS_SetPropertyStr(js, obj, "class", outClass ? JS_NewString(js, outClass) : JS_NULL); + return obj; +) + +JSC_CCALL(lua_argIsNil, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + return JS_NewBool(js, pd_lua->argIsNil((int)js2number(js, argv[0]))); +) + +JSC_CCALL(lua_getArgBool, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + return JS_NewBool(js, pd_lua->getArgBool((int)js2number(js, argv[0]))); +) + +JSC_CCALL(lua_getArgInt, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + return JS_NewInt32(js, pd_lua->getArgInt((int)js2number(js, argv[0]))); +) + +JSC_CCALL(lua_getArgFloat, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + return JS_NewFloat64(js, pd_lua->getArgFloat((int)js2number(js, argv[0]))); +) + +JSC_CCALL(lua_getArgString, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + const char *str = pd_lua->getArgString((int)js2number(js, argv[0])); + return str ? JS_NewString(js, str) : JS_NULL; +) + +JSC_CCALL(lua_getArgBytes, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + size_t len; + const char *bytes = pd_lua->getArgBytes((int)js2number(js, argv[0]), &len); + if (!bytes) return JS_NULL; + return js_new_blob_stoned_copy(js, bytes, len); +) + +JSC_CCALL(lua_pushNil, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->pushNil(); + return JS_UNDEFINED; +) + +JSC_CCALL(lua_pushBool, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->pushBool(JS_ToBool(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(lua_pushInt, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->pushInt((int)js2number(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(lua_pushFloat, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->pushFloat((float)js2number(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_SCALL(lua_pushString, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + pd_lua->pushString(str); +) + +JSC_CCALL(lua_pushBytes, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + size_t len; + const char *data = js_get_blob_data(js, &len, argv[0]); + if (data == (void*)-1) return JS_EXCEPTION; + pd_lua->pushBytes(data, len); + return JS_UNDEFINED; +) + +JSC_SCALL(lua_callFunction, + if (!pd_lua) return JS_ThrowInternalError(js, "lua not initialized"); + int nargs = argc > 1 ? (int)js2number(js, argv[1]) : 0; + const char *outerr = NULL; + int result = pd_lua->callFunction(str, nargs, &outerr); + if (result == 0 && outerr) { + ret = JS_ThrowInternalError(js, "Lua error: %s", outerr); + } else { + ret = JS_NewBool(js, result); + } +) + +static const JSCFunctionListEntry js_lua_funcs[] = { + MIST_FUNC_DEF(lua, stop, 0), + MIST_FUNC_DEF(lua, start, 0), + MIST_FUNC_DEF(lua, getArgCount, 0), + MIST_FUNC_DEF(lua, getArgType, 1), + MIST_FUNC_DEF(lua, argIsNil, 1), + MIST_FUNC_DEF(lua, getArgBool, 1), + MIST_FUNC_DEF(lua, getArgInt, 1), + MIST_FUNC_DEF(lua, getArgFloat, 1), + MIST_FUNC_DEF(lua, getArgString, 1), + MIST_FUNC_DEF(lua, getArgBytes, 1), + MIST_FUNC_DEF(lua, pushNil, 0), + MIST_FUNC_DEF(lua, pushBool, 1), + MIST_FUNC_DEF(lua, pushInt, 1), + MIST_FUNC_DEF(lua, pushFloat, 1), + MIST_FUNC_DEF(lua, pushString, 1), + MIST_FUNC_DEF(lua, pushBytes, 1), + MIST_FUNC_DEF(lua, callFunction, 2), +}; + +JSValue js_lua_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_lua_funcs, countof(js_lua_funcs)); + // Export type constants + JS_SetPropertyStr(js, mod, "TYPE_NIL", JS_NewInt32(js, kTypeNil)); + JS_SetPropertyStr(js, mod, "TYPE_BOOL", JS_NewInt32(js, kTypeBool)); + JS_SetPropertyStr(js, mod, "TYPE_INT", JS_NewInt32(js, kTypeInt)); + JS_SetPropertyStr(js, mod, "TYPE_FLOAT", JS_NewInt32(js, kTypeFloat)); + JS_SetPropertyStr(js, mod, "TYPE_STRING", JS_NewInt32(js, kTypeString)); + JS_SetPropertyStr(js, mod, "TYPE_TABLE", JS_NewInt32(js, kTypeTable)); + JS_SetPropertyStr(js, mod, "TYPE_FUNCTION", JS_NewInt32(js, kTypeFunction)); + JS_SetPropertyStr(js, mod, "TYPE_THREAD", JS_NewInt32(js, kTypeThread)); + JS_SetPropertyStr(js, mod, "TYPE_OBJECT", JS_NewInt32(js, kTypeObject)); + return mod; +} diff --git a/playdate/network_playdate.c b/playdate/network_playdate.c new file mode 100644 index 00000000..9be72642 --- /dev/null +++ b/playdate/network_playdate.c @@ -0,0 +1,290 @@ +// network_playdate.c - Cell integration for Playdate Network API +// Wraps pd_api_network.h functions for JavaScript access + +#include "cell.h" +#include "common.h" +#include +#include + +// --- Helpers --- +static HTTPConnection* js2http(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (HTTPConnection*)(intptr_t)ptr; +} + +static TCPConnection* js2tcp(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (TCPConnection*)(intptr_t)ptr; +} + +// --- Network Status --- + +JSC_CCALL(network_getStatus, + if (!pd_network) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt32(js, pd_network->getStatus()); +) + +JSC_CCALL(network_setEnabled, + if (!pd_network) return JS_ThrowInternalError(js, "network not initialized"); + // Note: callback not implemented for simplicity + pd_network->setEnabled(JS_ToBool(js, argv[0]), NULL); + return JS_UNDEFINED; +) + +// --- HTTP Functions --- + +JSC_SCALL(http_newConnection, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + int port = (int)js2number(js, argv[1]); + int usessl = argc > 2 ? JS_ToBool(js, argv[2]) : 0; + HTTPConnection *conn = pd_network->http->newConnection(str, port, usessl); + ret = conn ? JS_NewInt64(js, (int64_t)(intptr_t)conn) : JS_NULL; +) + +JSC_CCALL(http_retain, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + HTTPConnection *conn = js2http(js, argv[0]); + HTTPConnection *ret_conn = pd_network->http->retain(conn); + return ret_conn ? JS_NewInt64(js, (int64_t)(intptr_t)ret_conn) : JS_NULL; +) + +JSC_CCALL(http_release, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + HTTPConnection *conn = js2http(js, argv[0]); + if (conn) pd_network->http->release(conn); + return JS_UNDEFINED; +) + +JSC_CCALL(http_setConnectTimeout, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->http->setConnectTimeout(js2http(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(http_setKeepAlive, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->http->setKeepAlive(js2http(js, argv[0]), JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(http_setByteRange, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->http->setByteRange(js2http(js, argv[0]), (int)js2number(js, argv[1]), (int)js2number(js, argv[2])); + return JS_UNDEFINED; +) + +JSC_SCALL(http_get, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + HTTPConnection *conn = js2http(js, argv[0]); + const char *headers = argc > 2 ? JS_ToCString(js, argv[2]) : NULL; + size_t hlen = headers ? strlen(headers) : 0; + PDNetErr err = pd_network->http->get(conn, str, headers, hlen); + if (headers) JS_FreeCString(js, headers); + ret = JS_NewInt32(js, err); +) + +JSC_SCALL(http_post, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + HTTPConnection *conn = js2http(js, argv[0]); + const char *headers = argc > 2 ? JS_ToCString(js, argv[2]) : NULL; + size_t hlen = headers ? strlen(headers) : 0; + size_t blen; + const char *body = argc > 3 ? js_get_blob_data(js, &blen, argv[3]) : NULL; + if (body == (void*)-1) body = NULL; + PDNetErr err = pd_network->http->post(conn, str, headers, hlen, body, body ? blen : 0); + if (headers) JS_FreeCString(js, headers); + ret = JS_NewInt32(js, err); +) + +JSC_CCALL(http_getError, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt32(js, pd_network->http->getError(js2http(js, argv[0]))); +) + +JSC_CCALL(http_getProgress, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + int read, total; + pd_network->http->getProgress(js2http(js, argv[0]), &read, &total); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "read", JS_NewInt32(js, read)); + JS_SetPropertyStr(js, obj, "total", JS_NewInt32(js, total)); + return obj; +) + +JSC_CCALL(http_getResponseStatus, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt32(js, pd_network->http->getResponseStatus(js2http(js, argv[0]))); +) + +JSC_CCALL(http_getBytesAvailable, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt64(js, pd_network->http->getBytesAvailable(js2http(js, argv[0]))); +) + +JSC_CCALL(http_setReadTimeout, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->http->setReadTimeout(js2http(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(http_setReadBufferSize, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->http->setReadBufferSize(js2http(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(http_read, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + HTTPConnection *conn = js2http(js, argv[0]); + unsigned int buflen = (unsigned int)js2number(js, argv[1]); + void *buf = malloc(buflen); + if (!buf) return JS_ThrowInternalError(js, "malloc failed"); + int read = pd_network->http->read(conn, buf, buflen); + if (read < 0) { + free(buf); + return JS_NULL; + } + JSValue blob = js_new_blob_stoned_copy(js, buf, read); + free(buf); + return blob; +) + +JSC_CCALL(http_close, + if (!pd_network || !pd_network->http) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->http->close(js2http(js, argv[0])); + return JS_UNDEFINED; +) + +// --- TCP Functions --- + +JSC_SCALL(tcp_newConnection, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + int port = (int)js2number(js, argv[1]); + int usessl = argc > 2 ? JS_ToBool(js, argv[2]) : 0; + TCPConnection *conn = pd_network->tcp->newConnection(str, port, usessl); + ret = conn ? JS_NewInt64(js, (int64_t)(intptr_t)conn) : JS_NULL; +) + +JSC_CCALL(tcp_retain, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + TCPConnection *conn = js2tcp(js, argv[0]); + TCPConnection *ret_conn = pd_network->tcp->retain(conn); + return ret_conn ? JS_NewInt64(js, (int64_t)(intptr_t)ret_conn) : JS_NULL; +) + +JSC_CCALL(tcp_release, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + TCPConnection *conn = js2tcp(js, argv[0]); + if (conn) pd_network->tcp->release(conn); + return JS_UNDEFINED; +) + +JSC_CCALL(tcp_getError, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt32(js, pd_network->tcp->getError(js2tcp(js, argv[0]))); +) + +JSC_CCALL(tcp_setConnectTimeout, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->tcp->setConnectTimeout(js2tcp(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(tcp_close, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt32(js, pd_network->tcp->close(js2tcp(js, argv[0]))); +) + +JSC_CCALL(tcp_setReadTimeout, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->tcp->setReadTimeout(js2tcp(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(tcp_setReadBufferSize, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + pd_network->tcp->setReadBufferSize(js2tcp(js, argv[0]), (int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(tcp_getBytesAvailable, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + return JS_NewInt64(js, pd_network->tcp->getBytesAvailable(js2tcp(js, argv[0]))); +) + +JSC_CCALL(tcp_read, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + TCPConnection *conn = js2tcp(js, argv[0]); + size_t len = (size_t)js2number(js, argv[1]); + void *buf = malloc(len); + if (!buf) return JS_ThrowInternalError(js, "malloc failed"); + int read = pd_network->tcp->read(conn, buf, len); + if (read < 0) { + free(buf); + return JS_NewInt32(js, read); // Return error code + } + JSValue blob = js_new_blob_stoned_copy(js, buf, read); + free(buf); + return blob; +) + +JSC_CCALL(tcp_write, + if (!pd_network || !pd_network->tcp) return JS_ThrowInternalError(js, "network not initialized"); + TCPConnection *conn = js2tcp(js, argv[0]); + size_t len; + const void *data = js_get_blob_data(js, &len, argv[1]); + if (data == (void*)-1) return JS_EXCEPTION; + return JS_NewInt32(js, pd_network->tcp->write(conn, data, len)); +) + +static const JSCFunctionListEntry js_network_funcs[] = { + MIST_FUNC_DEF(network, getStatus, 0), + MIST_FUNC_DEF(network, setEnabled, 1), + // HTTP + MIST_FUNC_DEF(http, newConnection, 3), + MIST_FUNC_DEF(http, retain, 1), + MIST_FUNC_DEF(http, release, 1), + MIST_FUNC_DEF(http, setConnectTimeout, 2), + MIST_FUNC_DEF(http, setKeepAlive, 2), + MIST_FUNC_DEF(http, setByteRange, 3), + MIST_FUNC_DEF(http, get, 3), + MIST_FUNC_DEF(http, post, 4), + MIST_FUNC_DEF(http, getError, 1), + MIST_FUNC_DEF(http, getProgress, 1), + MIST_FUNC_DEF(http, getResponseStatus, 1), + MIST_FUNC_DEF(http, getBytesAvailable, 1), + MIST_FUNC_DEF(http, setReadTimeout, 2), + MIST_FUNC_DEF(http, setReadBufferSize, 2), + MIST_FUNC_DEF(http, read, 2), + MIST_FUNC_DEF(http, close, 1), + // TCP + MIST_FUNC_DEF(tcp, newConnection, 3), + MIST_FUNC_DEF(tcp, retain, 1), + MIST_FUNC_DEF(tcp, release, 1), + MIST_FUNC_DEF(tcp, getError, 1), + MIST_FUNC_DEF(tcp, setConnectTimeout, 2), + MIST_FUNC_DEF(tcp, close, 1), + MIST_FUNC_DEF(tcp, setReadTimeout, 2), + MIST_FUNC_DEF(tcp, setReadBufferSize, 2), + MIST_FUNC_DEF(tcp, getBytesAvailable, 1), + MIST_FUNC_DEF(tcp, read, 2), + MIST_FUNC_DEF(tcp, write, 2), +}; + +JSValue js_network_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_network_funcs, countof(js_network_funcs)); + // Export constants + JS_SetPropertyStr(js, mod, "NET_OK", JS_NewInt32(js, NET_OK)); + JS_SetPropertyStr(js, mod, "NET_NO_DEVICE", JS_NewInt32(js, NET_NO_DEVICE)); + JS_SetPropertyStr(js, mod, "NET_BUSY", JS_NewInt32(js, NET_BUSY)); + JS_SetPropertyStr(js, mod, "NET_WRITE_ERROR", JS_NewInt32(js, NET_WRITE_ERROR)); + JS_SetPropertyStr(js, mod, "NET_READ_ERROR", JS_NewInt32(js, NET_READ_ERROR)); + JS_SetPropertyStr(js, mod, "NET_CONNECTION_CLOSED", JS_NewInt32(js, NET_CONNECTION_CLOSED)); + JS_SetPropertyStr(js, mod, "WIFI_NOT_CONNECTED", JS_NewInt32(js, kWifiNotConnected)); + JS_SetPropertyStr(js, mod, "WIFI_CONNECTED", JS_NewInt32(js, kWifiConnected)); + JS_SetPropertyStr(js, mod, "WIFI_NOT_AVAILABLE", JS_NewInt32(js, kWifiNotAvailable)); + return mod; +} diff --git a/playdate/scoreboards_playdate.c b/playdate/scoreboards_playdate.c new file mode 100644 index 00000000..411a2b23 --- /dev/null +++ b/playdate/scoreboards_playdate.c @@ -0,0 +1,176 @@ +// scoreboards_playdate.c - Cell integration for Playdate Scoreboards API +// Wraps pd_api_scoreboards.h functions for JavaScript access + +#include "cell.h" +#include "common.h" +#include + +// --- Callback Contexts --- +// Note: These callbacks are async. For simplicity, we store results globally. +// A more robust implementation would use promises or callback registration. + +static JSContext *g_scoreboard_js = NULL; +static JSValue g_add_score_callback = JS_UNDEFINED; +static JSValue g_personal_best_callback = JS_UNDEFINED; +static JSValue g_boards_list_callback = JS_UNDEFINED; +static JSValue g_scores_callback = JS_UNDEFINED; + +static JSValue score_to_js(JSContext *js, PDScore *score) { + if (!score) return JS_NULL; + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "rank", JS_NewInt64(js, score->rank)); + JS_SetPropertyStr(js, obj, "value", JS_NewInt64(js, score->value)); + JS_SetPropertyStr(js, obj, "player", score->player ? JS_NewString(js, score->player) : JS_NULL); + return obj; +} + +static void add_score_cb(PDScore *score, const char *errorMessage) { + if (!g_scoreboard_js || JS_IsUndefined(g_add_score_callback)) return; + JSValue args[2]; + args[0] = score_to_js(g_scoreboard_js, score); + args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL; + JSValue ret = JS_Call(g_scoreboard_js, g_add_score_callback, JS_UNDEFINED, 2, args); + JS_FreeValue(g_scoreboard_js, ret); + JS_FreeValue(g_scoreboard_js, args[0]); + JS_FreeValue(g_scoreboard_js, args[1]); +} + +static void personal_best_cb(PDScore *score, const char *errorMessage) { + if (!g_scoreboard_js || JS_IsUndefined(g_personal_best_callback)) return; + JSValue args[2]; + args[0] = score_to_js(g_scoreboard_js, score); + args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL; + JSValue ret = JS_Call(g_scoreboard_js, g_personal_best_callback, JS_UNDEFINED, 2, args); + JS_FreeValue(g_scoreboard_js, ret); + JS_FreeValue(g_scoreboard_js, args[0]); + JS_FreeValue(g_scoreboard_js, args[1]); +} + +static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) { + if (!g_scoreboard_js || JS_IsUndefined(g_boards_list_callback)) return; + JSValue args[2]; + if (boards) { + JSValue arr = JS_NewArray(g_scoreboard_js); + for (unsigned int i = 0; i < boards->count; i++) { + JSValue board = JS_NewObject(g_scoreboard_js); + JS_SetPropertyStr(g_scoreboard_js, board, "boardID", + boards->boards[i].boardID ? JS_NewString(g_scoreboard_js, boards->boards[i].boardID) : JS_NULL); + JS_SetPropertyStr(g_scoreboard_js, board, "name", + boards->boards[i].name ? JS_NewString(g_scoreboard_js, boards->boards[i].name) : JS_NULL); + JS_SetPropertyUint32(g_scoreboard_js, arr, i, board); + } + args[0] = arr; + } else { + args[0] = JS_NULL; + } + args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL; + JSValue ret = JS_Call(g_scoreboard_js, g_boards_list_callback, JS_UNDEFINED, 2, args); + JS_FreeValue(g_scoreboard_js, ret); + JS_FreeValue(g_scoreboard_js, args[0]); + JS_FreeValue(g_scoreboard_js, args[1]); +} + +static void scores_cb(PDScoresList *scores, const char *errorMessage) { + if (!g_scoreboard_js || JS_IsUndefined(g_scores_callback)) return; + JSValue args[2]; + if (scores) { + JSValue obj = JS_NewObject(g_scoreboard_js); + JS_SetPropertyStr(g_scoreboard_js, obj, "boardID", + scores->boardID ? JS_NewString(g_scoreboard_js, scores->boardID) : JS_NULL); + JS_SetPropertyStr(g_scoreboard_js, obj, "count", JS_NewInt32(g_scoreboard_js, scores->count)); + JS_SetPropertyStr(g_scoreboard_js, obj, "lastUpdated", JS_NewInt64(g_scoreboard_js, scores->lastUpdated)); + JS_SetPropertyStr(g_scoreboard_js, obj, "playerIncluded", JS_NewBool(g_scoreboard_js, scores->playerIncluded)); + JS_SetPropertyStr(g_scoreboard_js, obj, "limit", JS_NewInt32(g_scoreboard_js, scores->limit)); + JSValue arr = JS_NewArray(g_scoreboard_js); + for (unsigned int i = 0; i < scores->count; i++) { + JS_SetPropertyUint32(g_scoreboard_js, arr, i, score_to_js(g_scoreboard_js, &scores->scores[i])); + } + JS_SetPropertyStr(g_scoreboard_js, obj, "scores", arr); + args[0] = obj; + } else { + args[0] = JS_NULL; + } + args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL; + JSValue ret = JS_Call(g_scoreboard_js, g_scores_callback, JS_UNDEFINED, 2, args); + JS_FreeValue(g_scoreboard_js, ret); + JS_FreeValue(g_scoreboard_js, args[0]); + JS_FreeValue(g_scoreboard_js, args[1]); +} + +// --- API Functions --- + +JSC_SCALL(scoreboards_addScore, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + uint32_t value = (uint32_t)js2number(js, argv[1]); + if (argc > 2 && JS_IsFunction(js, argv[2])) { + g_scoreboard_js = js; + JS_FreeValue(js, g_add_score_callback); + g_add_score_callback = JS_DupValue(js, argv[2]); + } + ret = JS_NewBool(js, pd_scoreboards->addScore(str, value, add_score_cb)); +) + +JSC_SCALL(scoreboards_getPersonalBest, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + if (argc > 1 && JS_IsFunction(js, argv[1])) { + g_scoreboard_js = js; + JS_FreeValue(js, g_personal_best_callback); + g_personal_best_callback = JS_DupValue(js, argv[1]); + } + ret = JS_NewBool(js, pd_scoreboards->getPersonalBest(str, personal_best_cb)); +) + +JSC_CCALL(scoreboards_freeScore, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + // Note: PDScore from callbacks is managed by Playdate, this is for user-allocated scores + // In practice, JS doesn't allocate PDScore directly, so this is a no-op wrapper + return JS_UNDEFINED; +) + +JSC_CCALL(scoreboards_getScoreboards, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + if (argc > 0 && JS_IsFunction(js, argv[0])) { + g_scoreboard_js = js; + JS_FreeValue(js, g_boards_list_callback); + g_boards_list_callback = JS_DupValue(js, argv[0]); + } + return JS_NewBool(js, pd_scoreboards->getScoreboards(boards_list_cb)); +) + +JSC_CCALL(scoreboards_freeBoardsList, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + // Managed by Playdate after callback + return JS_UNDEFINED; +) + +JSC_SCALL(scoreboards_getScores, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + if (argc > 1 && JS_IsFunction(js, argv[1])) { + g_scoreboard_js = js; + JS_FreeValue(js, g_scores_callback); + g_scores_callback = JS_DupValue(js, argv[1]); + } + ret = JS_NewBool(js, pd_scoreboards->getScores(str, scores_cb)); +) + +JSC_CCALL(scoreboards_freeScoresList, + if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized"); + // Managed by Playdate after callback + return JS_UNDEFINED; +) + +static const JSCFunctionListEntry js_scoreboards_funcs[] = { + MIST_FUNC_DEF(scoreboards, addScore, 3), + MIST_FUNC_DEF(scoreboards, getPersonalBest, 2), + MIST_FUNC_DEF(scoreboards, freeScore, 1), + MIST_FUNC_DEF(scoreboards, getScoreboards, 1), + MIST_FUNC_DEF(scoreboards, freeBoardsList, 1), + MIST_FUNC_DEF(scoreboards, getScores, 2), + MIST_FUNC_DEF(scoreboards, freeScoresList, 1), +}; + +JSValue js_scoreboards_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_scoreboards_funcs, countof(js_scoreboards_funcs)); + return mod; +} diff --git a/playdate/sound_playdate.c b/playdate/sound_playdate.c new file mode 100644 index 00000000..4abd8973 --- /dev/null +++ b/playdate/sound_playdate.c @@ -0,0 +1,448 @@ +// sound_playdate.c - Cell integration for Playdate Sound API +// Wraps pd_api_sound.h functions for JavaScript access + +#include "cell.h" +#include "common.h" + +// --- Helpers --- +static FilePlayer* js2fileplayer(JSContext *js, JSValueConst val) { + int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (FilePlayer*)(intptr_t)ptr; +} +static SamplePlayer* js2sampleplayer(JSContext *js, JSValueConst val) { + int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (SamplePlayer*)(intptr_t)ptr; +} +static AudioSample* js2sample(JSContext *js, JSValueConst val) { + int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (AudioSample*)(intptr_t)ptr; +} +static PDSynth* js2synth(JSContext *js, JSValueConst val) { + int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (PDSynth*)(intptr_t)ptr; +} +static SoundChannel* js2channel(JSContext *js, JSValueConst val) { + int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (SoundChannel*)(intptr_t)ptr; +} + +// --- Global --- +JSC_CCALL(sound_getCurrentTime, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewInt64(js, pd_sound->getCurrentTime()); +) +JSC_CCALL(sound_getDefaultChannel, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + SoundChannel *ch = pd_sound->getDefaultChannel(); + return ch ? JS_NewInt64(js, (int64_t)(intptr_t)ch) : JS_NULL; +) +JSC_CCALL(sound_addChannel, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->addChannel(js2channel(js, argv[0]))); +) +JSC_CCALL(sound_removeChannel, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->removeChannel(js2channel(js, argv[0]))); +) +JSC_CCALL(sound_setOutputsActive, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->setOutputsActive(JS_ToBool(js, argv[0]), JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_getError, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + const char *err = pd_sound->getError(); + return err ? JS_NewString(js, err) : JS_NULL; +) + +// --- FilePlayer --- +JSC_CCALL(sound_newFilePlayer, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + FilePlayer *p = pd_sound->fileplayer->newPlayer(); + return p ? JS_NewInt64(js, (int64_t)(intptr_t)p) : JS_NULL; +) +JSC_CCALL(sound_freeFilePlayer, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + FilePlayer *p = js2fileplayer(js, argv[0]); + if (p) pd_sound->fileplayer->freePlayer(p); + return JS_UNDEFINED; +) +JSC_SCALL(sound_loadIntoFilePlayer, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + FilePlayer *p = js2fileplayer(js, argv[0]); + ret = JS_NewBool(js, pd_sound->fileplayer->loadIntoPlayer(p, str)); +) +JSC_CCALL(sound_filePlayerPlay, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + FilePlayer *p = js2fileplayer(js, argv[0]); + int repeat = argc > 1 ? (int)js2number(js, argv[1]) : 1; + return JS_NewBool(js, pd_sound->fileplayer->play(p, repeat)); +) +JSC_CCALL(sound_filePlayerIsPlaying, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->fileplayer->isPlaying(js2fileplayer(js, argv[0]))); +) +JSC_CCALL(sound_filePlayerPause, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->pause(js2fileplayer(js, argv[0])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerStop, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->stop(js2fileplayer(js, argv[0])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerSetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->setVolume(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerGetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + float l, r; pd_sound->fileplayer->getVolume(js2fileplayer(js, argv[0]), &l, &r); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "left", JS_NewFloat64(js, l)); + JS_SetPropertyStr(js, obj, "right", JS_NewFloat64(js, r)); + return obj; +) +JSC_CCALL(sound_filePlayerGetLength, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->fileplayer->getLength(js2fileplayer(js, argv[0]))); +) +JSC_CCALL(sound_filePlayerSetOffset, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->setOffset(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerGetOffset, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->fileplayer->getOffset(js2fileplayer(js, argv[0]))); +) +JSC_CCALL(sound_filePlayerSetRate, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->setRate(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerGetRate, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->fileplayer->getRate(js2fileplayer(js, argv[0]))); +) +JSC_CCALL(sound_filePlayerSetLoopRange, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->setLoopRange(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerDidUnderrun, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->fileplayer->didUnderrun(js2fileplayer(js, argv[0]))); +) +JSC_CCALL(sound_filePlayerSetStopOnUnderrun, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->setStopOnUnderrun(js2fileplayer(js, argv[0]), JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_filePlayerSetBufferLength, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->fileplayer->setBufferLength(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +// --- Sample --- +JSC_SCALL(sound_loadSample, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + AudioSample *s = pd_sound->sample->load(str); + ret = s ? JS_NewInt64(js, (int64_t)(intptr_t)s) : JS_NULL; +) +JSC_CCALL(sound_freeSample, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + AudioSample *s = js2sample(js, argv[0]); + if (s) pd_sound->sample->freeSample(s); + return JS_UNDEFINED; +) +JSC_CCALL(sound_getSampleLength, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->sample->getLength(js2sample(js, argv[0]))); +) +JSC_CCALL(sound_newSampleBuffer, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + AudioSample *s = pd_sound->sample->newSampleBuffer((int)js2number(js, argv[0])); + return s ? JS_NewInt64(js, (int64_t)(intptr_t)s) : JS_NULL; +) + +// --- SamplePlayer --- +JSC_CCALL(sound_newSamplePlayer, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + SamplePlayer *p = pd_sound->sampleplayer->newPlayer(); + return p ? JS_NewInt64(js, (int64_t)(intptr_t)p) : JS_NULL; +) +JSC_CCALL(sound_freeSamplePlayer, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + SamplePlayer *p = js2sampleplayer(js, argv[0]); + if (p) pd_sound->sampleplayer->freePlayer(p); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerSetSample, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->setSample(js2sampleplayer(js, argv[0]), js2sample(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerPlay, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + SamplePlayer *p = js2sampleplayer(js, argv[0]); + int repeat = argc > 1 ? (int)js2number(js, argv[1]) : 1; + float rate = argc > 2 ? (float)js2number(js, argv[2]) : 1.0f; + return JS_NewBool(js, pd_sound->sampleplayer->play(p, repeat, rate)); +) +JSC_CCALL(sound_samplePlayerIsPlaying, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->sampleplayer->isPlaying(js2sampleplayer(js, argv[0]))); +) +JSC_CCALL(sound_samplePlayerStop, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->stop(js2sampleplayer(js, argv[0])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerSetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->setVolume(js2sampleplayer(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerGetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + float l, r; pd_sound->sampleplayer->getVolume(js2sampleplayer(js, argv[0]), &l, &r); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "left", JS_NewFloat64(js, l)); + JS_SetPropertyStr(js, obj, "right", JS_NewFloat64(js, r)); + return obj; +) +JSC_CCALL(sound_samplePlayerGetLength, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->sampleplayer->getLength(js2sampleplayer(js, argv[0]))); +) +JSC_CCALL(sound_samplePlayerSetOffset, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->setOffset(js2sampleplayer(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerGetOffset, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->sampleplayer->getOffset(js2sampleplayer(js, argv[0]))); +) +JSC_CCALL(sound_samplePlayerSetRate, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->setRate(js2sampleplayer(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerGetRate, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->sampleplayer->getRate(js2sampleplayer(js, argv[0]))); +) +JSC_CCALL(sound_samplePlayerSetPlayRange, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->setPlayRange(js2sampleplayer(js, argv[0]), (int)js2number(js, argv[1]), (int)js2number(js, argv[2])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_samplePlayerSetPaused, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->sampleplayer->setPaused(js2sampleplayer(js, argv[0]), JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +// --- Synth --- +JSC_CCALL(sound_newSynth, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + PDSynth *s = pd_sound->synth->newSynth(); + return s ? JS_NewInt64(js, (int64_t)(intptr_t)s) : JS_NULL; +) +JSC_CCALL(sound_freeSynth, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + PDSynth *s = js2synth(js, argv[0]); + if (s) pd_sound->synth->freeSynth(s); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetWaveform, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setWaveform(js2synth(js, argv[0]), (SoundWaveform)(int)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetAttackTime, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setAttackTime(js2synth(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetDecayTime, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setDecayTime(js2synth(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetSustainLevel, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setSustainLevel(js2synth(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetReleaseTime, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setReleaseTime(js2synth(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetTranspose, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setTranspose(js2synth(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthPlayNote, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + PDSynth *s = js2synth(js, argv[0]); + float freq = (float)js2number(js, argv[1]); + float vel = argc > 2 ? (float)js2number(js, argv[2]) : 1.0f; + float len = argc > 3 ? (float)js2number(js, argv[3]) : -1.0f; + uint32_t when = argc > 4 ? (uint32_t)js2number(js, argv[4]) : 0; + pd_sound->synth->playNote(s, freq, vel, len, when); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthPlayMIDINote, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + PDSynth *s = js2synth(js, argv[0]); + MIDINote note = (MIDINote)js2number(js, argv[1]); + float vel = argc > 2 ? (float)js2number(js, argv[2]) : 1.0f; + float len = argc > 3 ? (float)js2number(js, argv[3]) : -1.0f; + uint32_t when = argc > 4 ? (uint32_t)js2number(js, argv[4]) : 0; + pd_sound->synth->playMIDINote(s, note, vel, len, when); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthNoteOff, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->noteOff(js2synth(js, argv[0]), argc > 1 ? (uint32_t)js2number(js, argv[1]) : 0); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthStop, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->stop(js2synth(js, argv[0])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthSetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->synth->setVolume(js2synth(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_synthGetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + float l, r; pd_sound->synth->getVolume(js2synth(js, argv[0]), &l, &r); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "left", JS_NewFloat64(js, l)); + JS_SetPropertyStr(js, obj, "right", JS_NewFloat64(js, r)); + return obj; +) +JSC_CCALL(sound_synthIsPlaying, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->synth->isPlaying(js2synth(js, argv[0]))); +) +JSC_CCALL(sound_synthGetParameterCount, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewInt32(js, pd_sound->synth->getParameterCount(js2synth(js, argv[0]))); +) +JSC_CCALL(sound_synthSetParameter, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewBool(js, pd_sound->synth->setParameter(js2synth(js, argv[0]), (int)js2number(js, argv[1]), (float)js2number(js, argv[2]))); +) + +// --- Channel --- +JSC_CCALL(sound_newChannel, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + SoundChannel *ch = pd_sound->channel->newChannel(); + return ch ? JS_NewInt64(js, (int64_t)(intptr_t)ch) : JS_NULL; +) +JSC_CCALL(sound_freeChannel, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + SoundChannel *ch = js2channel(js, argv[0]); + if (ch) pd_sound->channel->freeChannel(ch); + return JS_UNDEFINED; +) +JSC_CCALL(sound_channelSetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->channel->setVolume(js2channel(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) +JSC_CCALL(sound_channelGetVolume, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + return JS_NewFloat64(js, pd_sound->channel->getVolume(js2channel(js, argv[0]))); +) +JSC_CCALL(sound_channelSetPan, + if (!pd_sound) return JS_ThrowInternalError(js, "sound not initialized"); + pd_sound->channel->setPan(js2channel(js, argv[0]), (float)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +static const JSCFunctionListEntry js_sound_funcs[] = { + MIST_FUNC_DEF(sound, getCurrentTime, 0), + MIST_FUNC_DEF(sound, getDefaultChannel, 0), + MIST_FUNC_DEF(sound, addChannel, 1), + MIST_FUNC_DEF(sound, removeChannel, 1), + MIST_FUNC_DEF(sound, setOutputsActive, 2), + MIST_FUNC_DEF(sound, getError, 0), + MIST_FUNC_DEF(sound, newFilePlayer, 0), + MIST_FUNC_DEF(sound, freeFilePlayer, 1), + MIST_FUNC_DEF(sound, loadIntoFilePlayer, 2), + MIST_FUNC_DEF(sound, filePlayerPlay, 2), + MIST_FUNC_DEF(sound, filePlayerIsPlaying, 1), + MIST_FUNC_DEF(sound, filePlayerPause, 1), + MIST_FUNC_DEF(sound, filePlayerStop, 1), + MIST_FUNC_DEF(sound, filePlayerSetVolume, 3), + MIST_FUNC_DEF(sound, filePlayerGetVolume, 1), + MIST_FUNC_DEF(sound, filePlayerGetLength, 1), + MIST_FUNC_DEF(sound, filePlayerSetOffset, 2), + MIST_FUNC_DEF(sound, filePlayerGetOffset, 1), + MIST_FUNC_DEF(sound, filePlayerSetRate, 2), + MIST_FUNC_DEF(sound, filePlayerGetRate, 1), + MIST_FUNC_DEF(sound, filePlayerSetLoopRange, 3), + MIST_FUNC_DEF(sound, filePlayerDidUnderrun, 1), + MIST_FUNC_DEF(sound, filePlayerSetStopOnUnderrun, 2), + MIST_FUNC_DEF(sound, filePlayerSetBufferLength, 2), + MIST_FUNC_DEF(sound, loadSample, 1), + MIST_FUNC_DEF(sound, freeSample, 1), + MIST_FUNC_DEF(sound, getSampleLength, 1), + MIST_FUNC_DEF(sound, newSampleBuffer, 1), + MIST_FUNC_DEF(sound, newSamplePlayer, 0), + MIST_FUNC_DEF(sound, freeSamplePlayer, 1), + MIST_FUNC_DEF(sound, samplePlayerSetSample, 2), + MIST_FUNC_DEF(sound, samplePlayerPlay, 3), + MIST_FUNC_DEF(sound, samplePlayerIsPlaying, 1), + MIST_FUNC_DEF(sound, samplePlayerStop, 1), + MIST_FUNC_DEF(sound, samplePlayerSetVolume, 3), + MIST_FUNC_DEF(sound, samplePlayerGetVolume, 1), + MIST_FUNC_DEF(sound, samplePlayerGetLength, 1), + MIST_FUNC_DEF(sound, samplePlayerSetOffset, 2), + MIST_FUNC_DEF(sound, samplePlayerGetOffset, 1), + MIST_FUNC_DEF(sound, samplePlayerSetRate, 2), + MIST_FUNC_DEF(sound, samplePlayerGetRate, 1), + MIST_FUNC_DEF(sound, samplePlayerSetPlayRange, 3), + MIST_FUNC_DEF(sound, samplePlayerSetPaused, 2), + MIST_FUNC_DEF(sound, newSynth, 0), + MIST_FUNC_DEF(sound, freeSynth, 1), + MIST_FUNC_DEF(sound, synthSetWaveform, 2), + MIST_FUNC_DEF(sound, synthSetAttackTime, 2), + MIST_FUNC_DEF(sound, synthSetDecayTime, 2), + MIST_FUNC_DEF(sound, synthSetSustainLevel, 2), + MIST_FUNC_DEF(sound, synthSetReleaseTime, 2), + MIST_FUNC_DEF(sound, synthSetTranspose, 2), + MIST_FUNC_DEF(sound, synthPlayNote, 5), + MIST_FUNC_DEF(sound, synthPlayMIDINote, 5), + MIST_FUNC_DEF(sound, synthNoteOff, 2), + MIST_FUNC_DEF(sound, synthStop, 1), + MIST_FUNC_DEF(sound, synthSetVolume, 3), + MIST_FUNC_DEF(sound, synthGetVolume, 1), + MIST_FUNC_DEF(sound, synthIsPlaying, 1), + MIST_FUNC_DEF(sound, synthGetParameterCount, 1), + MIST_FUNC_DEF(sound, synthSetParameter, 3), + MIST_FUNC_DEF(sound, newChannel, 0), + MIST_FUNC_DEF(sound, freeChannel, 1), + MIST_FUNC_DEF(sound, channelSetVolume, 2), + MIST_FUNC_DEF(sound, channelGetVolume, 1), + MIST_FUNC_DEF(sound, channelSetPan, 2), +}; + +JSValue js_sound_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_sound_funcs, countof(js_sound_funcs)); + return mod; +} diff --git a/playdate/sprite_playdate.c b/playdate/sprite_playdate.c new file mode 100644 index 00000000..787a5127 --- /dev/null +++ b/playdate/sprite_playdate.c @@ -0,0 +1,395 @@ +// sprite_playdate.c - Cell integration for Playdate Sprite API +// Wraps pd_api_sprite.h functions for JavaScript access + +#include "cell.h" +#include "common.h" + +// --- Helpers --- + +static LCDSprite* js2sprite(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (LCDSprite*)(intptr_t)ptr; +} + +static LCDBitmap* js2bitmap(JSContext *js, JSValueConst val) { + int64_t ptr; + if (JS_ToInt64(js, &ptr, val) < 0) return NULL; + return (LCDBitmap*)(intptr_t)ptr; +} + +// --- Sprite Functions --- + +JSC_CCALL(sprite_setAlwaysRedraw, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + pd_sprite->setAlwaysRedraw(JS_ToBool(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_drawSprites, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + pd_sprite->drawSprites(); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_updateAndDrawSprites, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + pd_sprite->updateAndDrawSprites(); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_newSprite, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = pd_sprite->newSprite(); + if (!s) return JS_ThrowInternalError(js, "failed to create sprite"); + return JS_NewInt64(js, (int64_t)(intptr_t)s); +) + +JSC_CCALL(sprite_freeSprite, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (s) pd_sprite->freeSprite(s); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_copy, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + LCDSprite *copy = pd_sprite->copy(s); + return copy ? JS_NewInt64(js, (int64_t)(intptr_t)copy) : JS_NULL; +) + +JSC_CCALL(sprite_addSprite, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->addSprite(s); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_removeSprite, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->removeSprite(s); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_removeAllSprites, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + pd_sprite->removeAllSprites(); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getSpriteCount, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + return JS_NewInt32(js, pd_sprite->getSpriteCount()); +) + +JSC_CCALL(sprite_setBounds, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + PDRect bounds = { (float)js2number(js, argv[1]), (float)js2number(js, argv[2]), + (float)js2number(js, argv[3]), (float)js2number(js, argv[4]) }; + pd_sprite->setBounds(s, bounds); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getBounds, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + PDRect b = pd_sprite->getBounds(s); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, b.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, b.y)); + JS_SetPropertyStr(js, obj, "width", JS_NewFloat64(js, b.width)); + JS_SetPropertyStr(js, obj, "height", JS_NewFloat64(js, b.height)); + return obj; +) + +JSC_CCALL(sprite_moveTo, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->moveTo(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_moveBy, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->moveBy(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_setImage, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + LCDBitmap *img = js2bitmap(js, argv[1]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + LCDBitmapFlip flip = argc > 2 ? (LCDBitmapFlip)(int)js2number(js, argv[2]) : kBitmapUnflipped; + pd_sprite->setImage(s, img, flip); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getImage, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + LCDBitmap *img = pd_sprite->getImage(s); + return img ? JS_NewInt64(js, (int64_t)(intptr_t)img) : JS_NULL; +) + +JSC_CCALL(sprite_setSize, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setSize(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_setZIndex, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setZIndex(s, (int16_t)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getZIndex, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + return JS_NewInt32(js, pd_sprite->getZIndex(s)); +) + +JSC_CCALL(sprite_setDrawMode, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setDrawMode(s, (LCDBitmapDrawMode)(int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_setImageFlip, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setImageFlip(s, (LCDBitmapFlip)(int)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getImageFlip, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + return JS_NewInt32(js, pd_sprite->getImageFlip(s)); +) + +JSC_CCALL(sprite_setVisible, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setVisible(s, JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_isVisible, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + return JS_NewBool(js, pd_sprite->isVisible(s)); +) + +JSC_CCALL(sprite_setOpaque, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setOpaque(s, JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_markDirty, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->markDirty(s); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_setTag, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setTag(s, (uint8_t)js2number(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getTag, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + return JS_NewInt32(js, pd_sprite->getTag(s)); +) + +JSC_CCALL(sprite_setIgnoresDrawOffset, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setIgnoresDrawOffset(s, JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getPosition, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + float x, y; + pd_sprite->getPosition(s, &x, &y); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, y)); + return obj; +) + +JSC_CCALL(sprite_setCenter, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setCenter(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getCenter, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + float x, y; + pd_sprite->getCenter(s, &x, &y); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, y)); + return obj; +) + +// --- Collision Functions --- + +JSC_CCALL(sprite_resetCollisionWorld, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + pd_sprite->resetCollisionWorld(); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_setCollideRect, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + PDRect rect = { (float)js2number(js, argv[1]), (float)js2number(js, argv[2]), + (float)js2number(js, argv[3]), (float)js2number(js, argv[4]) }; + pd_sprite->setCollideRect(s, rect); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_getCollideRect, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + PDRect r = pd_sprite->getCollideRect(s); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, r.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, r.y)); + JS_SetPropertyStr(js, obj, "width", JS_NewFloat64(js, r.width)); + JS_SetPropertyStr(js, obj, "height", JS_NewFloat64(js, r.height)); + return obj; +) + +JSC_CCALL(sprite_clearCollideRect, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->clearCollideRect(s); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_setCollisionsEnabled, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setCollisionsEnabled(s, JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_collisionsEnabled, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + return JS_NewBool(js, pd_sprite->collisionsEnabled(s)); +) + +JSC_CCALL(sprite_setUpdatesEnabled, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + pd_sprite->setUpdatesEnabled(s, JS_ToBool(js, argv[1])); + return JS_UNDEFINED; +) + +JSC_CCALL(sprite_updatesEnabled, + if (!pd_sprite) return JS_ThrowInternalError(js, "sprite not initialized"); + LCDSprite *s = js2sprite(js, argv[0]); + if (!s) return JS_ThrowTypeError(js, "invalid sprite"); + return JS_NewBool(js, pd_sprite->updatesEnabled(s)); +) + +static const JSCFunctionListEntry js_sprite_funcs[] = { + MIST_FUNC_DEF(sprite, setAlwaysRedraw, 1), + MIST_FUNC_DEF(sprite, drawSprites, 0), + MIST_FUNC_DEF(sprite, updateAndDrawSprites, 0), + MIST_FUNC_DEF(sprite, newSprite, 0), + MIST_FUNC_DEF(sprite, freeSprite, 1), + MIST_FUNC_DEF(sprite, copy, 1), + MIST_FUNC_DEF(sprite, addSprite, 1), + MIST_FUNC_DEF(sprite, removeSprite, 1), + MIST_FUNC_DEF(sprite, removeAllSprites, 0), + MIST_FUNC_DEF(sprite, getSpriteCount, 0), + MIST_FUNC_DEF(sprite, setBounds, 5), + MIST_FUNC_DEF(sprite, getBounds, 1), + MIST_FUNC_DEF(sprite, moveTo, 3), + MIST_FUNC_DEF(sprite, moveBy, 3), + MIST_FUNC_DEF(sprite, setImage, 3), + MIST_FUNC_DEF(sprite, getImage, 1), + MIST_FUNC_DEF(sprite, setSize, 3), + MIST_FUNC_DEF(sprite, setZIndex, 2), + MIST_FUNC_DEF(sprite, getZIndex, 1), + MIST_FUNC_DEF(sprite, setDrawMode, 2), + MIST_FUNC_DEF(sprite, setImageFlip, 2), + MIST_FUNC_DEF(sprite, getImageFlip, 1), + MIST_FUNC_DEF(sprite, setVisible, 2), + MIST_FUNC_DEF(sprite, isVisible, 1), + MIST_FUNC_DEF(sprite, setOpaque, 2), + MIST_FUNC_DEF(sprite, markDirty, 1), + MIST_FUNC_DEF(sprite, setTag, 2), + MIST_FUNC_DEF(sprite, getTag, 1), + MIST_FUNC_DEF(sprite, setIgnoresDrawOffset, 2), + MIST_FUNC_DEF(sprite, getPosition, 1), + MIST_FUNC_DEF(sprite, setCenter, 3), + MIST_FUNC_DEF(sprite, getCenter, 1), + MIST_FUNC_DEF(sprite, resetCollisionWorld, 0), + MIST_FUNC_DEF(sprite, setCollideRect, 5), + MIST_FUNC_DEF(sprite, getCollideRect, 1), + MIST_FUNC_DEF(sprite, clearCollideRect, 1), + MIST_FUNC_DEF(sprite, setCollisionsEnabled, 2), + MIST_FUNC_DEF(sprite, collisionsEnabled, 1), + MIST_FUNC_DEF(sprite, setUpdatesEnabled, 2), + MIST_FUNC_DEF(sprite, updatesEnabled, 1), +}; + +JSValue js_sprite_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_sprite_funcs, countof(js_sprite_funcs)); + return mod; +} diff --git a/playdate/sys_playdate.c b/playdate/sys_playdate.c new file mode 100644 index 00000000..ceb1df7e --- /dev/null +++ b/playdate/sys_playdate.c @@ -0,0 +1,267 @@ +// sys_playdate.c - Cell integration for Playdate System API +// Wraps pd_api_sys.h functions for JavaScript access + +#include "cell.h" +#include "common.h" +#include + +// --- System Functions --- + +JSC_CCALL(sys_logToConsole, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + const char *str = JS_ToCString(js, argv[0]); + if (str) { + pd_sys->logToConsole("%s", str); + JS_FreeCString(js, str); + } + return JS_UNDEFINED; +) + +JSC_CCALL(sys_error, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + const char *str = JS_ToCString(js, argv[0]); + if (str) { + pd_sys->error("%s", str); + JS_FreeCString(js, str); + } + return JS_UNDEFINED; +) + +JSC_CCALL(sys_getLanguage, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + PDLanguage lang = pd_sys->getLanguage(); + return JS_NewInt32(js, lang); +) + +JSC_CCALL(sys_getCurrentTimeMilliseconds, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewInt64(js, pd_sys->getCurrentTimeMilliseconds()); +) + +JSC_CCALL(sys_getSecondsSinceEpoch, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + unsigned int ms = 0; + unsigned int secs = pd_sys->getSecondsSinceEpoch(&ms); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "seconds", JS_NewInt64(js, secs)); + JS_SetPropertyStr(js, obj, "milliseconds", JS_NewInt32(js, ms)); + return obj; +) + +JSC_CCALL(sys_drawFPS, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + int x = (int)js2number(js, argv[0]); + int y = (int)js2number(js, argv[1]); + pd_sys->drawFPS(x, y); + return JS_UNDEFINED; +) + +JSC_CCALL(sys_getButtonState, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + PDButtons current, pushed, released; + pd_sys->getButtonState(¤t, &pushed, &released); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "current", JS_NewInt32(js, current)); + JS_SetPropertyStr(js, obj, "pushed", JS_NewInt32(js, pushed)); + JS_SetPropertyStr(js, obj, "released", JS_NewInt32(js, released)); + return obj; +) + +JSC_CCALL(sys_setPeripheralsEnabled, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + PDPeripherals mask = (PDPeripherals)(int)js2number(js, argv[0]); + pd_sys->setPeripheralsEnabled(mask); + return JS_UNDEFINED; +) + +JSC_CCALL(sys_getAccelerometer, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + float x, y, z; + pd_sys->getAccelerometer(&x, &y, &z); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, y)); + JS_SetPropertyStr(js, obj, "z", JS_NewFloat64(js, z)); + return obj; +) + +JSC_CCALL(sys_getCrankChange, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewFloat64(js, pd_sys->getCrankChange()); +) + +JSC_CCALL(sys_getCrankAngle, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewFloat64(js, pd_sys->getCrankAngle()); +) + +JSC_CCALL(sys_isCrankDocked, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewBool(js, pd_sys->isCrankDocked()); +) + +JSC_CCALL(sys_setCrankSoundsDisabled, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + int flag = JS_ToBool(js, argv[0]); + int prev = pd_sys->setCrankSoundsDisabled(flag); + return JS_NewBool(js, prev); +) + +JSC_CCALL(sys_getFlipped, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewBool(js, pd_sys->getFlipped()); +) + +JSC_CCALL(sys_setAutoLockDisabled, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + pd_sys->setAutoLockDisabled(JS_ToBool(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_CCALL(sys_getReduceFlashing, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewBool(js, pd_sys->getReduceFlashing()); +) + +JSC_CCALL(sys_getElapsedTime, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewFloat64(js, pd_sys->getElapsedTime()); +) + +JSC_CCALL(sys_resetElapsedTime, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + pd_sys->resetElapsedTime(); + return JS_UNDEFINED; +) + +JSC_CCALL(sys_getBatteryPercentage, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewFloat64(js, pd_sys->getBatteryPercentage()); +) + +JSC_CCALL(sys_getBatteryVoltage, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewFloat64(js, pd_sys->getBatteryVoltage()); +) + +JSC_CCALL(sys_getTimezoneOffset, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewInt32(js, pd_sys->getTimezoneOffset()); +) + +JSC_CCALL(sys_shouldDisplay24HourTime, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + return JS_NewBool(js, pd_sys->shouldDisplay24HourTime()); +) + +JSC_CCALL(sys_convertEpochToDateTime, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + uint32_t epoch = (uint32_t)js2number(js, argv[0]); + struct PDDateTime dt; + pd_sys->convertEpochToDateTime(epoch, &dt); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "year", JS_NewInt32(js, dt.year)); + JS_SetPropertyStr(js, obj, "month", JS_NewInt32(js, dt.month)); + JS_SetPropertyStr(js, obj, "day", JS_NewInt32(js, dt.day)); + JS_SetPropertyStr(js, obj, "weekday", JS_NewInt32(js, dt.weekday)); + JS_SetPropertyStr(js, obj, "hour", JS_NewInt32(js, dt.hour)); + JS_SetPropertyStr(js, obj, "minute", JS_NewInt32(js, dt.minute)); + JS_SetPropertyStr(js, obj, "second", JS_NewInt32(js, dt.second)); + return obj; +) + +JSC_CCALL(sys_convertDateTimeToEpoch, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + struct PDDateTime dt = {0}; + dt.year = (uint16_t)js2number(js, JS_GetPropertyStr(js, argv[0], "year")); + dt.month = (uint8_t)js2number(js, JS_GetPropertyStr(js, argv[0], "month")); + dt.day = (uint8_t)js2number(js, JS_GetPropertyStr(js, argv[0], "day")); + dt.hour = (uint8_t)js2number(js, JS_GetPropertyStr(js, argv[0], "hour")); + dt.minute = (uint8_t)js2number(js, JS_GetPropertyStr(js, argv[0], "minute")); + dt.second = (uint8_t)js2number(js, JS_GetPropertyStr(js, argv[0], "second")); + return JS_NewInt64(js, pd_sys->convertDateTimeToEpoch(&dt)); +) + +JSC_CCALL(sys_clearICache, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + pd_sys->clearICache(); + return JS_UNDEFINED; +) + +JSC_CCALL(sys_delay, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + pd_sys->delay((uint32_t)js2number(js, argv[0])); + return JS_UNDEFINED; +) + +JSC_SCALL(sys_restartGame, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + pd_sys->restartGame(str); +) + +JSC_CCALL(sys_getLaunchArgs, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + const char *path = NULL; + const char *args = pd_sys->getLaunchArgs(&path); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "args", args ? JS_NewString(js, args) : JS_NULL); + JS_SetPropertyStr(js, obj, "path", path ? JS_NewString(js, path) : JS_NULL); + return obj; +) + +JSC_CCALL(sys_getSystemInfo, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + const struct PDInfo *info = pd_sys->getSystemInfo(); + if (!info) return JS_NULL; + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "osversion", JS_NewInt64(js, info->osversion)); + JS_SetPropertyStr(js, obj, "language", JS_NewInt32(js, info->language)); + return obj; +) + +// --- Menu Functions --- + +JSC_CCALL(sys_removeAllMenuItems, + if (!pd_sys) return JS_ThrowInternalError(js, "system not initialized"); + pd_sys->removeAllMenuItems(); + return JS_UNDEFINED; +) + +static const JSCFunctionListEntry js_sys_funcs[] = { + MIST_FUNC_DEF(sys, logToConsole, 1), + MIST_FUNC_DEF(sys, error, 1), + MIST_FUNC_DEF(sys, getLanguage, 0), + MIST_FUNC_DEF(sys, getCurrentTimeMilliseconds, 0), + MIST_FUNC_DEF(sys, getSecondsSinceEpoch, 0), + MIST_FUNC_DEF(sys, drawFPS, 2), + MIST_FUNC_DEF(sys, getButtonState, 0), + MIST_FUNC_DEF(sys, setPeripheralsEnabled, 1), + MIST_FUNC_DEF(sys, getAccelerometer, 0), + MIST_FUNC_DEF(sys, getCrankChange, 0), + MIST_FUNC_DEF(sys, getCrankAngle, 0), + MIST_FUNC_DEF(sys, isCrankDocked, 0), + MIST_FUNC_DEF(sys, setCrankSoundsDisabled, 1), + MIST_FUNC_DEF(sys, getFlipped, 0), + MIST_FUNC_DEF(sys, setAutoLockDisabled, 1), + MIST_FUNC_DEF(sys, getReduceFlashing, 0), + MIST_FUNC_DEF(sys, getElapsedTime, 0), + MIST_FUNC_DEF(sys, resetElapsedTime, 0), + MIST_FUNC_DEF(sys, getBatteryPercentage, 0), + MIST_FUNC_DEF(sys, getBatteryVoltage, 0), + MIST_FUNC_DEF(sys, getTimezoneOffset, 0), + MIST_FUNC_DEF(sys, shouldDisplay24HourTime, 0), + MIST_FUNC_DEF(sys, convertEpochToDateTime, 1), + MIST_FUNC_DEF(sys, convertDateTimeToEpoch, 1), + MIST_FUNC_DEF(sys, clearICache, 0), + MIST_FUNC_DEF(sys, delay, 1), + MIST_FUNC_DEF(sys, restartGame, 1), + MIST_FUNC_DEF(sys, getLaunchArgs, 0), + MIST_FUNC_DEF(sys, getSystemInfo, 0), + MIST_FUNC_DEF(sys, removeAllMenuItems, 0), +}; + +JSValue js_sys_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_sys_funcs, countof(js_sys_funcs)); + return mod; +} diff --git a/source/main_playdate.c b/source/main_playdate.c index 90413d1c..5159105f 100644 --- a/source/main_playdate.c +++ b/source/main_playdate.c @@ -3,12 +3,19 @@ // and initializes the global Playdate API pointers used by other modules. #include "cell.h" -#include "pd_api.h" +#include "playdate/common.h" -// Global Playdate API pointers - used by fd_playdate.c, http_playdate.c, etc. +// Global Playdate API pointers - used by all playdate integration modules PlaydateAPI *pd = NULL; const struct playdate_file *pd_file = NULL; const struct playdate_sys *pd_sys = NULL; +const struct playdate_graphics *pd_gfx = NULL; +const struct playdate_sprite *pd_sprite = NULL; +const struct playdate_display *pd_display = NULL; +const struct playdate_sound *pd_sound = NULL; +const struct playdate_lua *pd_lua = NULL; +const struct playdate_json *pd_json = NULL; +const struct playdate_scoreboards *pd_scoreboards = NULL; const struct playdate_network *pd_network = NULL; // Forward declaration @@ -34,6 +41,13 @@ int eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg) pd = playdate; pd_file = playdate->file; pd_sys = playdate->system; + pd_gfx = playdate->graphics; + pd_sprite = playdate->sprite; + pd_display = playdate->display; + pd_sound = playdate->sound; + pd_lua = playdate->lua; + pd_json = playdate->json; + pd_scoreboards = playdate->scoreboards; pd_network = playdate->network; // Set up the update callback diff --git a/toolchains.cm b/toolchains.cm index a478b274..d95c565c 100644 --- a/toolchains.cm +++ b/toolchains.cm @@ -10,8 +10,20 @@ return { cpu_family: 'arm', cpu: 'cortex-m7', endian: 'little', - c_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-fno-exceptions'], - c_link_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-nostartfiles', "-T/Users/john/Developer/PlaydateSDK/C_API/buildsupport/link_map.ld"] + c_args: ['-DTARGET_PLAYDATE=1', '-DTARGET_EXTENSION=1', '-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-fno-exceptions'], + c_link_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-nostartfiles', '-T/Users/john/Developer/PlaydateSDK/C_API/buildsupport/link_map.ld'] + }, + playdate_simulator: { + c: 'clang', + cpp: 'clang++', + ar: 'ar', + strip: 'strip', + system: 'playdate', + cpu_family: 'x86_64', + cpu: 'x86_64', + endian: 'little', + c_args: ['-U__APPLE__ -DTARGET_SIMULATOR=1', '-DTARGET_EXTENSION=1', '-fPIC'], + c_link_args: ['-shared'] }, windows: { c: 'x86_64-w64-mingw32-gcc',