// 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_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); } 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); } 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_SetPropertyNumber(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); } 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_SetPropertyNumber(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); } // --- API Functions --- JSC_SCALL(scoreboards_addScore, if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized"); uint32_t value = (uint32_t)js2number(js, argv[1]); if (argc > 2 && JS_IsFunction(argv[2])) { g_scoreboard_js = js; g_add_score_callback = argv[2]; } ret = JS_NewBool(js, pd_scoreboards->addScore(str, value, add_score_cb)); ) JSC_SCALL(scoreboards_getPersonalBest, if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized"); if (argc > 1 && JS_IsFunction(argv[1])) { g_scoreboard_js = js; g_personal_best_callback = argv[1]; } ret = JS_NewBool(js, pd_scoreboards->getPersonalBest(str, personal_best_cb)); ) JSC_CCALL(scoreboards_freeScore, if (!pd_scoreboards) return JS_RaiseDisrupt(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_RaiseDisrupt(js, "scoreboards not initialized"); if (argc > 0 && JS_IsFunction(argv[0])) { g_scoreboard_js = js; g_boards_list_callback = argv[0]; } return JS_NewBool(js, pd_scoreboards->getScoreboards(boards_list_cb)); ) JSC_CCALL(scoreboards_freeBoardsList, if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized"); // Managed by Playdate after callback return JS_NULL; ) JSC_SCALL(scoreboards_getScores, if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized"); if (argc > 1 && JS_IsFunction(argv[1])) { g_scoreboard_js = js; g_scores_callback = argv[1]; } ret = JS_NewBool(js, pd_scoreboards->getScores(str, scores_cb)); ) JSC_CCALL(scoreboards_freeScoresList, if (!pd_scoreboards) return JS_RaiseDisrupt(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; }