Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/cgame/cgame.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,32 @@ typedef struct cg_import_s {
*/
void (*HttpGetAsync)(const char *url, Net_HttpCallback callback, void *user_data);

/**
* @brief Performs an asynchronous HTTP `GET` request and deserializes a single JSON object.
* @param url The URL to fetch.
* @param properties The JsonProperty descriptors for the destination struct.
* @param size The size of the destination struct.
* @param callback Invoked on a background thread when the request completes.
* @param user_data User data pointer passed through to the callback.
* @remarks The parsed instance is valid only for the duration of the callback; copy it if needed.
*/
void (*HttpGetInstanceAsync)(const char *url, const JsonProperty *properties,
size_t size, Net_HttpInstanceCallback callback, void *user_data);

/**
* @brief Performs an asynchronous HTTP `GET` request and deserializes a JSON array.
* @param url The URL to fetch.
* @param properties The JsonProperty descriptors for the destination struct array.
* @param stride The byte distance between consecutive structs.
* @param count The capacity of the destination array.
* @param callback Invoked on a background thread when the request completes.
* @param user_data User data pointer passed through to the callback.
* @remarks The parsed instances are valid only for the duration of the callback; copy them if needed.
*/
void (*HttpGetInstancesAsync)(const char *url, const JsonProperty *properties,
size_t stride, size_t count,
Net_HttpInstancesCallback callback, void *user_data);

/**
* @}
* @defgroup filesystem Filesystem
Expand Down
99 changes: 87 additions & 12 deletions src/cgame/default/ui/home/LeaderboardViewController.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,54 @@ static const char *formatTime(int32_t seconds) {
}

/**
* @brief Fetches leaderboard rows using the given sort column.
* @brief Pending leaderboard fetch results, shared with the HTTP session thread.
*/
static bool fetchLeaderboard(LeaderboardViewController *this, const TableColumn *column) {
static struct {
SDL_Mutex *lock;
uint32_t generation;
LeaderboardEntry entries[LEADERBOARD_MAX_ENTRIES];
size_t num_entries;
} leaderboard_fetch;

/**
* @brief Net_HttpInstancesCallback for `fetchLeaderboard`. Runs on the HTTP session
* thread, so the parsed rows are staged and marshaled to the main thread via an
* MVC notification.
*/
static void fetchLeaderboardComplete(int32_t status, void *instances, size_t count, void *user_data) {

if (status != 200) {
return;
}

const uint32_t generation = (uint32_t) (uintptr_t) user_data;

SDL_LockMutex(leaderboard_fetch.lock);

if (generation != leaderboard_fetch.generation) {
SDL_UnlockMutex(leaderboard_fetch.lock);
return;
}

memset(leaderboard_fetch.entries, 0, sizeof(leaderboard_fetch.entries));

leaderboard_fetch.num_entries = count < LEADERBOARD_MAX_ENTRIES ? count : LEADERBOARD_MAX_ENTRIES;
if (instances && leaderboard_fetch.num_entries) {
memcpy(leaderboard_fetch.entries, instances, leaderboard_fetch.num_entries * sizeof(LeaderboardEntry));
}

SDL_UnlockMutex(leaderboard_fetch.lock);

SDL_PushEvent(&(SDL_Event) {
.user.type = MVC_NOTIFICATION_EVENT,
.user.code = NOTIFICATION_LEADERBOARD_FETCHED
});
}

/**
* @brief Asynchronously fetches leaderboard rows using the given sort column.
*/
static void fetchLeaderboard(LeaderboardViewController *this, const TableColumn *column) {

const char *sort = column ? sortParamForColumn(column->identifier) : NULL;
const char *dir = (column && column->order == OrderAscending) ? "asc" : "desc";
Expand All @@ -90,12 +135,13 @@ static bool fetchLeaderboard(LeaderboardViewController *this, const TableColumn
g_snprintf(url, sizeof(url), QUETOO_STATS_URL "?limit=%d&ai=0", LEADERBOARD_MAX_ENTRIES);
}

size_t num_entries = 0;
const int32_t status = cgi.HttpGetInstances(url, leaderboard_properties,
this->entries, sizeof(LeaderboardEntry),
LEADERBOARD_MAX_ENTRIES, &num_entries);
this->num_entries = num_entries;
return status == 200;
SDL_LockMutex(leaderboard_fetch.lock);
const uint32_t generation = ++leaderboard_fetch.generation;
SDL_UnlockMutex(leaderboard_fetch.lock);

cgi.HttpGetInstancesAsync(url, leaderboard_properties,
sizeof(LeaderboardEntry), LEADERBOARD_MAX_ENTRIES,
fetchLeaderboardComplete, (void *) (uintptr_t) generation);
}

/**
Expand Down Expand Up @@ -181,8 +227,6 @@ static void didSetSortColumn(TableView *tableView) {
LeaderboardViewController *this = tableView->delegate.self;

fetchLeaderboard(this, tableView->sortColumn);
$(this->leaderboard, reloadData);
selectOwnRow(this);
}

#pragma mark - ViewController
Expand All @@ -196,6 +240,10 @@ static void loadView(ViewController *self) {

LeaderboardViewController *this = (LeaderboardViewController *) self;

if (leaderboard_fetch.lock == NULL) {
leaderboard_fetch.lock = SDL_CreateMutex();
}

Outlet outlets[] = MakeOutlets(
MakeOutlet("leaderboard", &this->leaderboard)
);
Expand All @@ -222,6 +270,34 @@ static void loadView(ViewController *self) {

}

/**
* @see ViewController::respondToEvent(ViewController *, const SDL_Event *)
*/
static void respondToEvent(ViewController *self, const SDL_Event *event) {

LeaderboardViewController *this = (LeaderboardViewController *) self;

if (event->type == MVC_NOTIFICATION_EVENT) {

if (event->user.code == NOTIFICATION_LEADERBOARD_FETCHED) {

SDL_LockMutex(leaderboard_fetch.lock);
this->num_entries = leaderboard_fetch.num_entries;
memcpy(this->entries, leaderboard_fetch.entries, sizeof(this->entries));
SDL_UnlockMutex(leaderboard_fetch.lock);

$(this->leaderboard, reloadData);
selectOwnRow(this);

} else if (event->user.code == NOTIFICATION_GUID_HASHED) {
$(this->leaderboard, reloadData);
selectOwnRow(this);
}
}

super(ViewController, self, respondToEvent, event);
}

/**
* @see ViewController::viewWillAppear(ViewController *)
*/
Expand All @@ -232,8 +308,6 @@ static void viewWillAppear(ViewController *self) {
LeaderboardViewController *this = (LeaderboardViewController *) self;

fetchLeaderboard(this, this->leaderboard->sortColumn);
$(this->leaderboard, reloadData);
selectOwnRow(this);
}

#pragma mark - Class lifecycle
Expand All @@ -243,6 +317,7 @@ static void viewWillAppear(ViewController *self) {
*/
static void initialize(Class *clazz) {
((ViewControllerInterface *) clazz->interface)->loadView = loadView;
((ViewControllerInterface *) clazz->interface)->respondToEvent = respondToEvent;
((ViewControllerInterface *) clazz->interface)->viewWillAppear = viewWillAppear;
}

Expand Down
Loading
Loading