Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shim SlippiDirectCodes to Rust port. #14

Open
wants to merge 2 commits into
base: slippi
Choose a base branch
from
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
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 user_config_folder = 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 = user_config_folder.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;
};
Loading