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.

There is a small change here to `FileUtil` to have it return a folder
path rather than a file path; the Rust layer will extend that path with
whatever it needs (e.g `user.json`).

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 4f53653 commit e4bdc9d
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 244 deletions.
9 changes: 6 additions & 3 deletions Source/Core/Common/FileUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,15 +804,18 @@ std::string GetSysDirectory()
return sysDir;
}

// This returns the folder where certain configuration files are stored (i.e,
// `user.json`, etc).
//
// On Linux platforms, the user.json file lives in the XDG_CONFIG_HOME/SlippiOnline
// directory in order to deal with the fact that we want the configuration for AppImage
// builds to be mutable.
std::string GetSlippiUserJSONPath()
std::string GetSlippiUserConfigFolder()
{
#if defined(__APPLE__)
std::string userFilePath = File::GetApplicationSupportDirectory() + "/Slippi/user.json";
std::string userFilePath = File::GetApplicationSupportDirectory() + "/Slippi";
#else
std::string userFilePath = File::GetUserPath(F_USERJSON_IDX);
std::string userFilePath = File::GetUserPath(D_SLIPPI_IDX);
#endif
return userFilePath;
}
Expand Down
4 changes: 2 additions & 2 deletions Source/Core/Common/FileUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ void SetUserPath(unsigned int dir_index, const std::string &path);
// probably doesn't belong here
std::string GetThemeDir(const std::string &theme_name);

// Gets the path where a Slippi user.json file lives.
std::string GetSlippiUserJSONPath();
// Gets the path where Slippi config files (e.g, user.json) live.
std::string GetSlippiUserConfigFolder();

// Returns the path to where the sys file are
std::string GetSysDirectory();
Expand Down
10 changes: 5 additions & 5 deletions Source/Core/Core/HW/EXI_DeviceSlippi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ CEXISlippi::CEXISlippi()
// https://github.com/dolphin-emu/dolphin/blob/7f450f1d7e7d37bd2300f3a2134cb443d07251f9/Source/Core/Core/Movie.cpp#L246-L249
std::string isoPath = SConfig::GetInstance().m_strFilename;

// @TODO: Eventually we should move `GetSlippiUserJSONPath` out of the File module.
std::string userJSONPath = File::GetSlippiUserJSONPath();
// @TODO: Eventually we should move `GetSlippiUserConfigFolder` out of the File module.
std::string userConfigFolder = File::GetSlippiUserConfigFolder();

SlippiRustEXIConfig slprs_exi_config;
slprs_exi_config.iso_path = isoPath.c_str();
slprs_exi_config.user_json_path = userJSONPath.c_str();
slprs_exi_config.user_config_folder = userConfigFolder.c_str();
slprs_exi_config.scm_slippi_semver_str = scm_slippi_semver_str.c_str();
slprs_exi_config.osd_add_msg_fn = OSDMessageHandler;

Expand All @@ -170,8 +170,8 @@ CEXISlippi::CEXISlippi()
matchmaking = std::make_unique<SlippiMatchmaking>(user.get());
gameFileLoader = std::make_unique<SlippiGameFileLoader>();
g_replayComm = std::make_unique<SlippiReplayComm>();
directCodes = std::make_unique<SlippiDirectCodes>("direct-codes.json");
teamsCodes = std::make_unique<SlippiDirectCodes>("teams-codes.json");
directCodes = std::make_unique<SlippiDirectCodes>(slprs_exi_device_ptr, SlippiDirectCodes::DIRECT);
teamsCodes = std::make_unique<SlippiDirectCodes>(slprs_exi_device_ptr, SlippiDirectCodes::TEAMS);

generator = std::default_random_engine(Common::Timer::GetTimeMs());

Expand Down
229 changes: 20 additions & 209 deletions Source/Core/Core/Slippi/SlippiDirectCodes.cpp
Original file line number Diff line number Diff line change
@@ -1,230 +1,41 @@
#include "SlippiDirectCodes.h"

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

#include "Common/CommonPaths.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 fileName)
{
m_fileName = fileName;
#include "SlippiDirectCodes.h"

// Prevent additional file reads, if we've already loaded data to memory.
// if (directCodeInfos.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 directCodesFilePath = getCodesFilePath();

INFO_LOG(SLIPPI_ONLINE, "Looking for direct codes file at %s", directCodesFilePath.c_str());

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

std::string directCodesFileContents;
File::ReadFileToString(directCodesFilePath, directCodesFileContents);

directCodeInfos = parseFile(directCodesFileContents);
}
SlippiDirectCodes::~SlippiDirectCodes() {}

void SlippiDirectCodes::AddOrUpdateCode(std::string code)
{
WARN_LOG(SLIPPI_ONLINE, "Attempting to add or update direct code: %s", 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 = directCodeInfos.begin(); it != directCodeInfos.end(); ++it)
{
if (it->connectCode == code)
{
found = true;
it->lastPlayed = timestamp;
}
}

if (!found)
{
CodeInfo newDirectCode = {code, timestamp, false};
directCodeInfos.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 sortByProperty)
{
switch (sortByProperty)
{
case SORT_BY_TIME:
std::sort(directCodeInfos.begin(), directCodeInfos.end(),
[](const CodeInfo a, const CodeInfo b) -> bool { return a.lastPlayed > b.lastPlayed; });
break;

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

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

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

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

std::string SlippiDirectCodes::get(int index)
{
Sort();

if (index < directCodeInfos.size() && index >= 0)
{
return directCodeInfos.at(index).connectCode;
}
char *code = slprs_user_direct_codes_get_code_at_index(slprs_exi_device_ptr, mapCode(kind), index);

// 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(SLIPPI_ONLINE, "Out of bounds name entry index %d", 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 >= directCodeInfos.size()) ? "1" : "";
return connectCode;
}

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

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

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

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

// TODO Define constants for string literals.
for (auto it = directCodeInfos.begin(); it != directCodeInfos.end(); ++it)
{
directCodeData["connectCode"] = it->connectCode;
directCodeData["lastPlayed"] = it->lastPlayed;
directCodeData["isFavorite"] = it->isFavorite;

fileData.emplace_back(directCodeData);
}

File::WriteStringToFile(fileData.dump(), directCodesFilePath);
}

std::string SlippiDirectCodes::getCodesFilePath()
{
std::string fileName = m_fileName + ".json";

std::string directCodesPath = File::GetUserPath(D_SLIPPI_IDX) + m_fileName;
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 fileContents)
{
std::vector<SlippiDirectCodes::CodeInfo> directCodes;

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

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

directCodes.push_back(curDirectCode);
}
}

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

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

// 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;
// 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 connectCode = "";
std::string lastPlayed = "";
bool isFavorite = false;
};

SlippiDirectCodes(std::string fileName);
SlippiDirectCodes(uintptr_t rs_exi_device_ptr, uint8_t kind);
~SlippiDirectCodes();

void ReadFile();
void AddOrUpdateCode(std::string code);
std::string get(int index);
int length();
void Sort(u8 sortByProperty = SlippiDirectCodes::SORT_BY_TIME);
std::string Autocomplete(std::string startText);
void AddOrUpdateCode(std::string code);

protected:
void WriteFile();
std::string getCodesFilePath();
std::vector<CodeInfo> parseFile(std::string fileContents);
std::vector<CodeInfo> directCodeInfos;
std::string m_fileName;
};
// 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;

// An internal marker for what kind of codes we're reading/reporting.
uint8_t kind;
};

0 comments on commit e4bdc9d

Please sign in to comment.