177 lines
7.5 KiB
C
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_NULL;
|
|
static JSValue g_personal_best_callback = JS_NULL;
|
|
static JSValue g_boards_list_callback = JS_NULL;
|
|
static JSValue g_scores_callback = JS_NULL;
|
|
|
|
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_IsNull(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_NULL, 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_IsNull(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_NULL, 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_IsNull(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_NULL, 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_IsNull(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_NULL, 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_NULL;
|
|
)
|
|
|
|
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_NULL;
|
|
)
|
|
|
|
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_NULL;
|
|
)
|
|
|
|
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;
|
|
}
|