Skip to content

Commit

Permalink
Shim SlippiDirectCodes to Rust port.
Browse files Browse the repository at this point in the history
This changes out the internals of the `SlippiDirectCodes` class, wherein
it'll now just silently call over to the Rust side. This keeps the
changes minimal for now as we continue to migrate things out.

This notably lacks the change that Ishiiruka had to determine the user
config folder path, since it appears the mainline build doesn't need it?
Might need further testing there.

This compiles but has not been fully tested/vetted yet. Building this
branch currently requires the corresponding branch from
slippi-rust-extensions.
  • Loading branch information
ryanmcgrath committed Feb 6, 2024
1 parent d463645 commit 0938d65
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 242 deletions.
8 changes: 4 additions & 4 deletions Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ CEXISlippi::CEXISlippi(Core::System& system, const std::string current_file_name
{
INFO_LOG_FMT(SLIPPI, "EXI SLIPPI Constructor called.");

std::string user_file_path = File::GetUserPath(F_USERJSON_IDX);
std::string userConfigFolder = File::GetUserPath(D_SLIPPI_IDX);

SlippiRustEXIConfig slprs_exi_config;
slprs_exi_config.iso_path = current_file_name.c_str();
slprs_exi_config.user_json_path = user_file_path.c_str();
slprs_exi_config.user_config_folder = userConfigFolder.c_str();
slprs_exi_config.scm_slippi_semver_str = Common::GetSemVerStr().c_str();
slprs_exi_config.osd_add_msg_fn = OSDMessageHandler;

Expand All @@ -156,8 +156,8 @@ CEXISlippi::CEXISlippi(Core::System& system, const std::string current_file_name
matchmaking = std::make_unique<SlippiMatchmaking>(user.get());
game_file_loader = std::make_unique<SlippiGameFileLoader>();
g_replay_comm = std::make_unique<SlippiReplayComm>();
direct_codes = std::make_unique<SlippiDirectCodes>("direct-codes.json");
teams_codes = std::make_unique<SlippiDirectCodes>("teams-codes.json");
direct_codes = std::make_unique<SlippiDirectCodes>(slprs_exi_device_ptr, SlippiDirectCodes::DIRECT);
teams_codes = std::make_unique<SlippiDirectCodes>(slprs_exi_device_ptr, SlippiDirectCodes::TEAMS);

// initialize the spectate server so we can connect without starting a game
SlippiSpectateServer::getInstance();
Expand Down
229 changes: 19 additions & 210 deletions Source/Core/Core/Slippi/SlippiDirectCodes.cpp
Original file line number Diff line number Diff line change
@@ -1,232 +1,41 @@
#include "SlippiDirectCodes.h"

#ifdef _WIN32
#include "AtlBase.h"
#include "AtlConv.h"
#endif

#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"

#include "Core/ConfigManager.h"
#include "SlippiRustExtensions.h"

#include <codecvt>
#include <locale>
#include <time.h>

#include <json.hpp>
using json = nlohmann::json;

SlippiDirectCodes::SlippiDirectCodes(std::string file_name)
{
m_file_name = file_name;
#include "SlippiDirectCodes.h"

// Prevent additional file reads, if we've already loaded data to memory.
// if (m_direct_code_infos.empty())
ReadFile();
Sort();
DirectCodeKind mapCode(uint8_t code_kind) {
return code_kind == SlippiDirectCodes::DIRECT ?
DirectCodeKind::DirectCodes :
DirectCodeKind::TeamsCodes;
}

SlippiDirectCodes::~SlippiDirectCodes()
SlippiDirectCodes::SlippiDirectCodes(uintptr_t rs_exi_device_ptr, uint8_t code_kind)
{
// Add additional cleanup behavior here? Just added something
// So compiler wouldn't nag.
return;
slprs_exi_device_ptr = rs_exi_device_ptr;
kind = code_kind;
}

void SlippiDirectCodes::ReadFile()
{
std::string direct_codes_file_path = getCodesFilePath();

INFO_LOG_FMT(SLIPPI_ONLINE, "Looking for direct codes file at {}", direct_codes_file_path);

if (!File::Exists(direct_codes_file_path))
{
// Attempt to create empty file with array as parent json item.
if (File::CreateFullPath(direct_codes_file_path) &&
File::CreateEmptyFile(direct_codes_file_path))
{
File::WriteStringToFile(direct_codes_file_path, "[\n]");
}
else
{
WARN_LOG_FMT(SLIPPI_ONLINE, "Was unable to create {}", direct_codes_file_path.c_str());
}
}

std::string direct_codes_file_contents;
File::ReadFileToString(direct_codes_file_path, direct_codes_file_contents);

m_direct_code_infos = parseFile(direct_codes_file_contents);
}
SlippiDirectCodes::~SlippiDirectCodes() {}

void SlippiDirectCodes::AddOrUpdateCode(std::string code)
{
WARN_LOG_FMT(SLIPPI_ONLINE, "Attempting to add or update direct code: {}", code.c_str());

time_t curTime;
time(&curTime);
u8 dateTimeStrLength = sizeof "20171015T095717";
std::vector<char> dateTimeBuf(dateTimeStrLength);
strftime(&dateTimeBuf[0], dateTimeStrLength, "%Y%m%dT%H%M%S", localtime(&curTime));
std::string timestamp(&dateTimeBuf[0]);

bool found = false;
for (auto it = m_direct_code_infos.begin(); it != m_direct_code_infos.end(); ++it)
{
if (it->connect_code == code)
{
found = true;
it->last_played = timestamp;
}
}

if (!found)
{
CodeInfo newDirectCode = {code, timestamp, false};
m_direct_code_infos.push_back(newDirectCode);
}

// TODO: Maybe remove from here?
// Or start a thread that is periodically called, if file writes will happen enough.
WriteFile();
}

void SlippiDirectCodes::Sort(u8 sort_by_property)
{
switch (sort_by_property)
{
case SORT_BY_TIME:
std::sort(
m_direct_code_infos.begin(), m_direct_code_infos.end(),
[](const CodeInfo a, const CodeInfo b) -> bool { return a.last_played > b.last_played; });
break;

case SORT_BY_NAME:
std::sort(
m_direct_code_infos.begin(), m_direct_code_infos.end(),
[](const CodeInfo a, const CodeInfo b) -> bool { return a.connect_code < b.connect_code; });
break;
}
}

std::string SlippiDirectCodes::Autocomplete(std::string start_text)
{
// Pre-sort direct codes.
Sort();

// Find first entry in our sorted vector that starts with the given text.
for (auto it = m_direct_code_infos.begin(); it != m_direct_code_infos.end(); it++)
{
if (it->connect_code.rfind(start_text, 0) == 0)
{
return it->connect_code;
}
}

return start_text;
slprs_user_direct_codes_add_or_update(slprs_exi_device_ptr, mapCode(kind), code.c_str());
}

std::string SlippiDirectCodes::get(int index)
{
Sort();
char *code = slprs_user_direct_codes_get_code_at_index(slprs_exi_device_ptr, mapCode(kind), index);

if (index < m_direct_code_infos.size() && index >= 0)
{
return m_direct_code_infos.at(index).connect_code;
}
// To be safe, just do an extra copy into a full C++ string type - i.e, the ownership
// that we're passing out from behind this method is clear.
std::string connectCode = std::string(code);

INFO_LOG_FMT(SLIPPI_ONLINE, "Out of bounds name entry index {}", index);
// Since the C string was allocated on the Rust side, we need to free it using that allocator.
slprs_user_direct_codes_free_code(code);

return (index >= m_direct_code_infos.size()) ? "1" : "";
return connectCode;
}

int SlippiDirectCodes::length()
{
return (int)m_direct_code_infos.size();
}

void SlippiDirectCodes::WriteFile()
{
std::string direct_codes_file_path = getCodesFilePath();

// Outer empty array.
json file_data = json::array();

// Inner contents.
json direct_code_data = json::object();

// TODO Define constants for string literals.
for (auto it = m_direct_code_infos.begin(); it != m_direct_code_infos.end(); ++it)
{
direct_code_data["connect_code"] = it->connect_code;
direct_code_data["last_played"] = it->last_played;
direct_code_data["is_favorite"] = it->is_favorite;

file_data.emplace_back(direct_code_data);
}

File::WriteStringToFile(direct_codes_file_path, file_data.dump());
}

std::string SlippiDirectCodes::getCodesFilePath()
{
std::string directCodesPath = File::GetUserPath(D_SLIPPI_IDX) + m_file_name;
return directCodesPath;
}

inline std::string readString(json obj, std::string key)
{
auto item = obj.find(key);
if (item == obj.end() || item.value().is_null())
{
return "";
}

return obj[key];
}

inline bool readBool(json obj, std::string key)
{
auto item = obj.find(key);
if (item == obj.end() || item.value().is_null())
{
return false;
}

return obj[key];
}

std::vector<SlippiDirectCodes::CodeInfo> SlippiDirectCodes::parseFile(std::string file_contents)
{
std::vector<SlippiDirectCodes::CodeInfo> direct_codes;

json res = json::parse(file_contents, nullptr, false);
// Unlike the user.json, the encapsulating type should be an array.
if (res.is_discarded() || !res.is_array())
{
WARN_LOG_FMT(SLIPPI_ONLINE, "Malformed json in direct codes file.");
return direct_codes;
}

// Retrieve all saved direct codes and related info
for (auto it = res.begin(); it != res.end(); ++it)
{
if (it.value().is_object())
{
CodeInfo cur_direct_code;
cur_direct_code.connect_code = readString(*it, "connect_code");
cur_direct_code.last_played = readString(*it, "last_played");
cur_direct_code.is_favorite = readBool(*it, "favorite");

direct_codes.push_back(cur_direct_code);
}
}

return direct_codes;
return slprs_user_direct_codes_get_length(slprs_exi_device_ptr, mapCode(kind));
}
51 changes: 23 additions & 28 deletions Source/Core/Core/Slippi/SlippiDirectCodes.h
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
#pragma once

#include <atomic>
#include <string>
#include <thread>
#include <vector>
#include "Common/CommonTypes.h"

// This class is currently a shim for the Rust codes interface. We're doing it this way
// to migrate things over without needing to do larger invasive changes.
//
// The remaining methods on here are simply layers that direct the call over to the Rust
// side. A quirk of this is that we're using the EXI device pointer, so this class absolutely
// cannot outlive the EXI device - but we control that and just need to do our due diligence
// when making changes.
class SlippiDirectCodes
{
public:
static const uint8_t SORT_BY_TIME = 1;
static const uint8_t SORT_BY_FAVORITE = 2;
static const uint8_t SORT_BY_NAME = 3;
public:
// We can't currently expose `SlippiRustExtensions.h` in header files, so
// we export these two types for code clarity and map them in the implementation.
static const uint8_t DIRECT = 0;
static const uint8_t TEAMS = 1;

struct CodeInfo
{
std::string connect_code = "";
std::string last_played = "";
bool is_favorite = false;
};
SlippiDirectCodes(uintptr_t rs_exi_device_ptr, uint8_t kind);
~SlippiDirectCodes();

SlippiDirectCodes(std::string file_name);
~SlippiDirectCodes();
std::string get(int index);
int length();
void AddOrUpdateCode(std::string code);

void ReadFile();
void AddOrUpdateCode(std::string code);
std::string get(int index);
int length();
void Sort(u8 sort_by_property = SlippiDirectCodes::SORT_BY_TIME);
std::string Autocomplete(std::string start_text);
protected:
// A pointer to a "shadow" EXI Device that lives on the Rust side of things.
// Do *not* do any cleanup of this! The EXI device will handle it.
uintptr_t slprs_exi_device_ptr;

protected:
void WriteFile();
std::string getCodesFilePath();
std::vector<CodeInfo> parseFile(std::string file_contents);
std::vector<CodeInfo> m_direct_code_infos;
std::string m_file_name;
// An internal marker for what kind of codes we're reading/reporting.
uint8_t kind;
};

0 comments on commit 0938d65

Please sign in to comment.