Skip to content

Commit

Permalink
[Achievements]Rewrite async code
Browse files Browse the repository at this point in the history
  • Loading branch information
OFFTKP committed Sep 22, 2023
1 parent a30e6a7 commit afa82a7
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 156 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ add_definitions(-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\")
add_definitions(-DGIT_BRANCH=\"${GIT_BRANCH}\")
add_definitions(-DGIT_TAG=\"${GIT_TAG}\")

set(SKYEMU_SRC src/main.c src/stb.c src/miniz.c src/res.c src/localization.c src/recursive_mutex.cpp)
set(SKYEMU_SRC src/main.c src/stb.c src/miniz.c src/res.c src/localization.c)

if(ENABLE_HTTP_CONTROL_SERVER)
add_definitions(-DENABLE_HTTP_CONTROL_SERVER=1)
Expand Down
14 changes: 1 addition & 13 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
#include "capstone/include/capstone/capstone.h"
#include "miniz.h"
#include "localization.h"
#include "recursive_mutex.h"

#if defined(EMSCRIPTEN)
#include <emscripten.h>
Expand Down Expand Up @@ -485,7 +484,6 @@ typedef struct{
sg_image image;
rc_client_achievement_list_t* achievement_list;
sg_image** achievement_images;
recursive_mutex_t mutex;
}se_ra_info_t;
#endif
gui_state_t gui_state={ .update_font_atlas=true };
Expand Down Expand Up @@ -1553,7 +1551,6 @@ static uint32_t se_ra_read_memory_callback(uint32_t address, uint8_t* buffer, ui
return 0;
}
static void se_ra_load_image_callback(const uint8_t* pixel_data, size_t image_size, int width, int height, void* user_data){
lock_mutex(ra_info.mutex);
sg_image* achievement_image=(sg_image*)user_data;
if(pixel_data){
sg_image_data im_data={0};
Expand Down Expand Up @@ -1584,7 +1581,6 @@ static void se_ra_load_image_callback(const uint8_t* pixel_data, size_t image_si
}else{
printf("[rcheevos]: failed to load game image\n");
}
unlock_mutex(ra_info.mutex);
}
static void se_ra_game_cleanup(){
if(ra_info.image.id != SG_INVALID_ID){
Expand Down Expand Up @@ -1618,7 +1614,6 @@ static void se_ra_load_game_callback(int result, const char* error_message, rc_c
return;
}

lock_mutex(ra_info.mutex);
char url[512];
const rc_client_game_t* game = rc_client_get_game_info(ra_get_client());
if (rc_client_game_get_image_url(game, url, sizeof(url)) == RC_OK){
Expand Down Expand Up @@ -1652,7 +1647,6 @@ static void se_ra_load_game_callback(int result, const char* error_message, rc_c
printf("locked\n");
}
}
unlock_mutex(ra_info.mutex);
}
static void se_ra_load_game(){
if(!emu_state.rom_loaded)return;
Expand Down Expand Up @@ -1698,7 +1692,6 @@ static void se_ra_event_handler(const rc_client_event_t* event, rc_client_t* cli
}
static void se_init_retro_achievements(){
memset(&ra_info, 0, sizeof(ra_info));
ra_info.mutex = create_mutex();
ra_initialize_client(se_ra_read_memory_callback);
rc_client_set_event_handler(ra_get_client(),se_ra_event_handler);

Expand Down Expand Up @@ -5650,6 +5643,7 @@ void se_draw_menu_panel(){
}
}
#ifdef ENABLE_RETRO_ACHIEVEMENTS
ra_run_pending_callbacks();
se_text(ICON_FK_TROPHY " Retro Achievements");
igSeparator();
const rc_client_user_t* user = rc_client_get_user_info(ra_get_client());
Expand Down Expand Up @@ -6327,9 +6321,6 @@ uint8_t* se_hcs_callback(const char* cmd, const char** params, uint64_t* result_
#endif

static void frame(void) {
#ifdef ENABLE_RETRO_ACHIEVEMENTS
lock_mutex(ra_info.mutex);
#endif
se_reset_html_click_regions();
sb_poll_controller_input(&emu_state.joy);
#ifdef USE_SDL
Expand Down Expand Up @@ -6724,9 +6715,6 @@ static void frame(void) {
gui_state.audio_watchdog_triggered++;
}
#ifdef ENABLE_RETRO_ACHIEVEMENTS
unlock_mutex(ra_info.mutex);
//These need to be run after each frame
ra_poll_requests();
rc_client_do_frame(ra_get_client());
#endif
se_free_all_images();
Expand Down
21 changes: 0 additions & 21 deletions src/recursive_mutex.cpp

This file was deleted.

8 changes: 0 additions & 8 deletions src/recursive_mutex.h

This file was deleted.

208 changes: 95 additions & 113 deletions src/retro_achievements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,8 @@ struct Image
};

std::unordered_map<std::string, Image> image_cache;

// httplib doesn't have a way to make async requests, so we need to do it ourselves
struct AsyncRequest
{
std::unique_ptr<httplib::Client> client;
std::future<httplib::Result> result_future;
rc_client_server_callback_t callback;
void* callback_data;
};

struct ImageCallbackData
{
get_image_callback_t callback;
void* userdata;
std::string url;
};

std::vector<AsyncRequest> async_requests;
std::vector<AsyncRequest> async_requests_to_be_added;
std::recursive_mutex async_requests_mutex;
std::mutex pending_callbacks_mutex;
std::vector<std::function<void()>> pending_callbacks;

std::pair<std::string, std::string> split_url(const std::string& url)
{
Expand All @@ -65,33 +47,43 @@ std::pair<std::string, std::string> split_url(const std::string& url)
static void server_callback(const rc_api_request_t* request,
rc_client_server_callback_t callback, void* callback_data, rc_client_t* client)
{
std::unique_lock<std::recursive_mutex> lock(async_requests_mutex);
// RetroAchievements may not allow hardcore unlocks if we don't properly identify ourselves.
// TODO: is there a better way to get the current version?
const char* user_agent = "SkyEmu/4.0";

auto pair = split_url(request->url);
std::string host = pair.first;
std::string query = pair.second;

AsyncRequest async_request;
async_request.client.reset(new httplib::Client(host));

// Copy it as the request is destroyed as soon as we return
std::string content_type = request->content_type;
std::string post_data = request->post_data;
if(request->post_data)
{
httplib::Result (httplib::Client::*gf)(const std::string&, const std::string&, const std::string&) = &httplib::Client::Post;
async_request.result_future = std::async(std::launch::async, gf, async_request.client.get(), query, post_data, content_type);
}
else
{
httplib::Result (httplib::Client::*gf)(const std::string&) = &httplib::Client::Get;
async_request.result_future = std::async(std::launch::async, gf, async_request.client.get(), query);
}
async_request.callback = callback;
async_request.callback_data = callback_data;
async_requests_to_be_added.push_back(std::move(async_request));

auto function = [host, query, callback, callback_data, post_data, content_type]() {
httplib::Client client(host.c_str());
httplib::Result result;
if(post_data.empty())
{
result = client.Get(query.c_str());
}
else
{
result = client.Post(query.c_str(), post_data, content_type.c_str());
}
if(result.error() == httplib::Error::Success)
{
rc_api_server_response_t response;
response.body = result->body.c_str();
response.body_length = result->body.length();
response.http_status_code = result->status;
callback(&response, callback_data);
}
else
{
printf("[rcheevos]: http request failed: %s\n", to_string(result.error()).c_str());
}
};

std::thread thread(function);
thread.detach();
}

static void log_message(const char* message, const rc_client_t* client)
Expand Down Expand Up @@ -130,41 +122,6 @@ void ra_login_credentials(const char* username, const char* password, rc_client_
rc_client_begin_login_with_password(ra_client, username, password, login_callback, NULL);
}

void ra_poll_requests()
{
std::unique_lock<std::recursive_mutex> lock(async_requests_mutex);
// Check if any of our asynchronous requests have finished, and if so, call the callback
auto it = async_requests.begin();
while(it != async_requests.end())
{
AsyncRequest& request = *it;
if (!request.result_future.valid()) {
it = async_requests.erase(it);
continue;
}
if (request.result_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
httplib::Result result = request.result_future.get();
if(result.error() == httplib::Error::Success)
{
rc_api_server_response_t response;
response.body = result->body.c_str();
response.body_length = result->body.length();
response.http_status_code = result->status;
request.callback(&response, request.callback_data);
}
else
{
printf("[rcheevos]: http request failed: %s\n", to_string(result.error()).c_str());
}
}
++it;
}

// Add any new requests at the end as to not invalidate the iterator
async_requests.insert(async_requests.end(), std::make_move_iterator(async_requests_to_be_added.begin()), std::make_move_iterator(async_requests_to_be_added.end()));
}

void ra_login_token(const char* username, const char* token, rc_client_callback_t login_callback)
{
std::string username_str = username;
Expand All @@ -175,64 +132,89 @@ void ra_login_token(const char* username, const char* token, rc_client_callback_
login_thread.detach();
}

void ra_load_game_callback(int result, const char* message, rc_client_t* client, void* user_data)
{
std::unique_lock<std::mutex> lock(pending_callbacks_mutex);
pending_callbacks.push_back([=](){
((rc_client_callback_t)user_data)(result, message, client, NULL);
});
}

void ra_load_game(const uint8_t *rom, size_t rom_size, int console_id, rc_client_callback_t callback)
{
std::thread load_game_thread([](const uint8_t *rom, size_t rom_size, int console_id, rc_client_callback_t callback){
// Make a copy of the ROM as the original may be destroyed before the thread finishes
std::vector<uint8_t> rom_copy(rom, rom + rom_size);
std::thread load_game_thread([rom_copy](int console_id, rc_client_callback_t callback){
rc_client_begin_identify_and_load_game(ra_client, console_id,
NULL, rom, rom_size, callback, NULL);
}, rom, rom_size, console_id, callback);
NULL, rom_copy.data(), rom_copy.size(), ra_load_game_callback, (void*)callback);
}, console_id, callback);
load_game_thread.detach();
}
static void ra_get_image_callback(const rc_api_server_response_t* server_response, void* callback_data)
{
ImageCallbackData* data = (ImageCallbackData*)callback_data;

if(server_response->http_status_code == 200)
{
auto& image = image_cache[data->url];
uint8_t* pixel_data = stbi_load_from_memory((const uint8_t*)server_response->body, server_response->body_length, &image.width, &image.height, NULL, 4);
image.pixel_data.resize(image.width * image.height * 4);
memcpy(image.pixel_data.data(), pixel_data, image.pixel_data.size());
data->callback(image.pixel_data.data(), image.pixel_data.size(), image.width, image.height, data->userdata);
stbi_image_free(pixel_data);
}
else
{
printf("[rcheevos]: failed to get image: %s\n", data->url.c_str());
}

delete data;
}

void ra_get_image(const char* url, get_image_callback_t callback, void* userdata)
void ra_get_image(const char* url, get_image_callback_t callback, void* user_data)
{
std::unique_lock<std::recursive_mutex> lock(async_requests_mutex);
auto pair = split_url(url);
std::string host = pair.first;
std::string query = pair.second;

ImageCallbackData* image_callback_data = new ImageCallbackData;
image_callback_data->callback = callback;
image_callback_data->userdata = userdata;
image_callback_data->url = url;

if (image_cache.find(url) != image_cache.end())
{
auto& image = image_cache[url];
image_callback_data->callback(image.pixel_data.data(), image.pixel_data.size(), image.width, image.height, image_callback_data->userdata);
delete image_callback_data;
std::unique_lock<std::mutex> lock(pending_callbacks_mutex);
pending_callbacks.push_back([callback, user_data, &image](){
callback(image.pixel_data.data(), image.pixel_data.size(), image.width, image.height, user_data);
});
return;
}

AsyncRequest async_request;
async_request.client.reset(new httplib::Client(host));
std::string url_str = url;
auto function = [url_str, host, query, user_data, callback]() {
httplib::Client client(host.c_str());
httplib::Result result = client.Get(query.c_str());
if(result.error() == httplib::Error::Success)
{
rc_api_server_response_t response;
response.body = result->body.c_str();
response.body_length = result->body.length();
response.http_status_code = result->status;
if(response.http_status_code == 200)
{
auto& image = image_cache[url_str];
uint8_t* pixel_data = stbi_load_from_memory((const uint8_t*)response.body, response.body_length, &image.width, &image.height, NULL, 4);
image.pixel_data.resize(image.width * image.height * 4);
memcpy(image.pixel_data.data(), pixel_data, image.pixel_data.size());
stbi_image_free(pixel_data);
std::unique_lock<std::mutex> lock(pending_callbacks_mutex);
pending_callbacks.push_back([callback, user_data, &image](){
callback(image.pixel_data.data(), image.pixel_data.size(), image.width, image.height, user_data);
});
}
else
{
printf("[rcheevos]: failed to get image: %s\n", url_str.c_str());
}
}
else
{
printf("[rcheevos]: http request failed: %s\n", to_string(result.error()).c_str());
}
};

std::thread thread(function);
thread.detach();
}

httplib::Result (httplib::Client::*gf)(const std::string&) = &httplib::Client::Get;
async_request.result_future = std::async(std::launch::async, gf, async_request.client.get(), query);
void ra_run_pending_callbacks()
{
std::unique_lock<std::mutex> lock(pending_callbacks_mutex);
if(pending_callbacks.empty())
return;

async_request.callback = ra_get_image_callback;
async_request.callback_data = image_callback_data;
async_requests_to_be_added.push_back(std::move(async_request));
for (auto& callback : pending_callbacks)
{
callback();
}
pending_callbacks.clear();
}

rc_client_t* ra_get_client()
Expand Down
1 change: 1 addition & 0 deletions src/retro_achievements.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ void ra_poll_requests();
void ra_shutdown_client();
void ra_load_game(const uint8_t* rom, size_t rom_size, int console_id, rc_client_callback_t callback);
void ra_get_image(const char* url, get_image_callback_t callback, void* userdata);
void ra_run_pending_callbacks();
rc_client_t* ra_get_client();
#endif

0 comments on commit afa82a7

Please sign in to comment.