Files
cell/playdate/scoreboards_playdate.c
2025-12-10 15:01:20 -06:00

177 lines
7.5 KiB
C

// 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 <string.h>
// --- 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;
}