Skip to content

Commit

Permalink
Merge pull request #126 from MafiaHub/review/asset-streamer
Browse files Browse the repository at this point in the history
Asset Streamer
  • Loading branch information
Segfaultd authored Feb 24, 2025
2 parents 8df2d83 + 162d109 commit d051a9e
Show file tree
Hide file tree
Showing 16 changed files with 401 additions and 50 deletions.
2 changes: 1 addition & 1 deletion code/framework/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ target_link_libraries(FrameworkServer libsig Lua54)
# Platform-dependent post processing
if(WIN32)
target_link_directories(Framework PUBLIC ${CMAKE_SOURCE_DIR}/vendors/openssl/lib)
target_link_libraries(Framework ws2_32 dbghelp crypt32 winmm iphlpapi psapi userenv)
target_link_libraries(Framework ws2_32 dbghelp crypt32 winmm iphlpapi psapi userenv shlwapi)

target_link_libraries(FrameworkClient DiscordSDK DearImGUI)

Expand Down
130 changes: 128 additions & 2 deletions code/framework/src/integrations/client/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "instance.h"

#include <networking/messages/client_connection_finalized.h>
#include <networking/messages/client_request_streamer.h>
#include <networking/messages/client_ready_assets.h>
#include <networking/messages/client_handshake.h>
#include <networking/messages/client_initialise_player.h>
#include <networking/messages/client_kick.h>
Expand All @@ -17,13 +19,48 @@

#include "../shared/modules/mod.hpp"

#include "networking/state.h"

#include <cppfs/cppfs.h>
#include <cppfs/FilePath.h>
#include <cppfs/FileHandle.h>
#include <cppfs/fs.h>

#include <logging/logger.h>

#include "utils/version.h"

#include "core_modules.h"

namespace Framework::Integrations::Client {
bool AssetDownloadFileProgress::OnFile(SLNet::FileListTransferCBInterface::OnFileStruct *onFileStruct) {
if (onFileStruct->numberOfFilesInThisSet > 0) {
auto &downloadStatus = _instance->GetAssetDownloadStatus();
downloadStatus.progress = onFileStruct->bytesDownloadedForThisSet / float(onFileStruct->byteLengthOfThisSet);
if (onFileStruct->bytesDownloadedForThisFile == onFileStruct->byteLengthOfThisFile) {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Asset downloaded ({}/{} - {}%): {}", onFileStruct->fileIndex + 1, onFileStruct->numberOfFilesInThisSet, int(downloadStatus.progress * 100.0f), onFileStruct->fileName);
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush();
}
}
return true;
}

void AssetDownloadFileProgress::OnFileProgress(SLNet::FileListTransferCBInterface::FileProgressStruct *fps) {
auto &downloadStatus = _instance->GetAssetDownloadStatus();
auto onFileStruct = fps->onFileStruct;
downloadStatus.progress = onFileStruct->byteLengthOfThisSet / float(onFileStruct->bytesDownloadedForThisSet);
}

bool AssetDownloadFileProgress::OnDownloadComplete(DownloadCompleteStruct *dcs) {
(void)dcs;

auto &downloadStatus = _instance->GetAssetDownloadStatus();
downloadStatus.progress = 1.0f;
downloadStatus.downloading = false;
_instance->OnAssetsDownloaded(true);
return false;
}

Instance::Instance() {
_networkingEngine = std::make_unique<Networking::Engine>();
_presence = std::make_unique<External::Discord::Wrapper>();
Expand Down Expand Up @@ -77,6 +114,7 @@ namespace Framework::Integrations::Client {
}

InitNetworkingMessages();
InitAssetDownloader();

PostInit();
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->info("Mod subsystems initialized");
Expand All @@ -93,6 +131,14 @@ namespace Framework::Integrations::Client {
return ClientError::CLIENT_NONE;
}

void Instance::InitAssetDownloader() {
cppfs::fs::open("cache").createDirectory();

GetNetworkingEngine()->GetNetworkClient()->SetOnAssetsDownloadFailedCallback([this]() {
this->OnAssetsDownloaded(false);
});
}

ClientError Instance::RenderInit() {
if (_renderInitialized) {
return ClientError::CLIENT_NONE;
Expand Down Expand Up @@ -201,17 +247,20 @@ namespace Framework::Integrations::Client {
PostRender();
}

void Instance::InitNetworkingMessages() const {
void Instance::InitNetworkingMessages() {
using namespace Framework::Networking::Messages;
const auto net = _networkingEngine->GetNetworkClient();
net->SetOnPlayerConnectedCallback([this, net](SLNet::Packet *packet) {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Connection accepted by server, sending handshake");

ClientHandshake msg;
msg.FromParameters(_currentState._nickname, "MY_SUPER_ID_1", "MY_SUPER_ID_2", _opts.modVersion, Utils::Version::rel, _opts.gameVersion, _opts.gameName);
msg.FromParameters(_opts.modVersion, Utils::Version::rel, _opts.gameVersion, _opts.gameName);

net->Send(msg, SLNet::UNASSIGNED_RAKNET_GUID);
});
net->RegisterMessage<ClientReadyAssets>(GameMessages::GAME_CONNECTION_READY_ASSETS, [this, net](SLNet::RakNetGUID _guid, ClientReadyAssets *msg) {
DownloadsAssetsFromConnectedServer();
});
net->RegisterMessage<ClientConnectionFinalized>(GameMessages::GAME_CONNECTION_FINALIZED, [this, net](SLNet::RakNetGUID _guid, ClientConnectionFinalized *msg) {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Connection request finalized");
_worldEngine->OnConnect(net, msg->GetServerTickRate());
Expand Down Expand Up @@ -256,6 +305,11 @@ namespace Framework::Integrations::Client {
*tr = msg->GetTransform();
});
net->SetOnPlayerDisconnectedCallback([this](SLNet::Packet *packet, uint32_t reasonId) {
// Reset initial asset download state
_initialDownloadDone = false;
_downloadStatus = {};

// Request the world engine to clean up entities
_worldEngine->OnDisconnect();

// Notify mod-level that network integration got closed
Expand All @@ -268,4 +322,76 @@ namespace Framework::Integrations::Client {

Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Game sync networking messages registered");
}

void Instance::DownloadsAssetsFromConnectedServer() {
const auto net = GetNetworkingEngine()->GetNetworkClient();

// Make sure we're connected to the server already, otherwise bail with warning
if (net->GetConnectionState() != Framework::Networking::CONNECTED) {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->warn("We can't download assets if we are not connected to the server yet!");
return;
}

Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Setting up asset downloads...");
const auto serverHash = Framework::Utils::Hashing::CalculateCRC32(_currentState._host + ":" + std::to_string(_currentState._port));
const auto cacheDir = fmt::format("cache\\{}", serverHash);
const auto streamer = net->GetAssetStreamer();
SetAssetCachePath(cacheDir);
streamer->SetApplicationDirectory(cacheDir.c_str());
auto folderHandle = cppfs::fs::open(cacheDir);

// Ensure we stop existing downloads since the server has pushed new changes already
if (_downloadStatus.downloading) {
net->GetFileListTransfer()->CancelReceive(_downloadStatus.setID);
_downloadStatus = {};
}

if (!folderHandle.exists()) {
if (folderHandle.createDirectory()) {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Client asset cache: {}", serverHash);
}
else {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->warn("Could not create folder for client asset cache: {}", cacheDir);
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->warn("Skip downloading assets.");
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush();

// Ensure we finish the download flow gracefully
OnAssetsDownloaded(false);
return;
}
}
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush();

_downloadStatus.setID = streamer->DownloadFromSubdirectory(nullptr, nullptr, true, net->GetPeer()->GetSystemAddressFromIndex(0), new AssetDownloadFileProgress(this), HIGH_PRIORITY, 2, nullptr);
}

void Instance::OnAssetsDownloaded(bool success) {
const auto net = GetNetworkingEngine()->GetNetworkClient();
if (success) {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("All the assets have been downloaded!");
}
else {
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->error("There has been an issue downloading assets!");
}
Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush();

// Send the server a request to initialise our client and assign a streamer
// but only do so the first time we connect to the server
if (!_initialDownloadDone) {
_initialDownloadDone = true;

Framework::Networking::Messages::ClientRequestStreamer req;
req.FromParameters(_currentState._nickname, "MY_SUPER_ID_1", "MY_SUPER_ID_2");
net->Send(req, SLNet::UNASSIGNED_RAKNET_GUID);
}

_downloadStatus = {};

// TODO grab client entry point from the message
// GetClientEntryPoint() and only set client scripting up if it's specified and file is present

// Let the mod-level know assets have just been finished processing
if (_onAssetsDownloadFinished)
_onAssetsDownloadFinished(success);
}
} // namespace Framework::Integrations::Client
55 changes: 54 additions & 1 deletion code/framework/src/integrations/client/instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <graphics/renderio.h>

#include "networking/engine.h"
#include <FileListTransferCBInterface.h>

#include <function2.hpp>
#include <memory>
Expand All @@ -31,6 +32,23 @@
namespace Framework::Integrations::Client {
using NetworkConnectionFinalizedCallback = fu2::function<void(flecs::entity, float) const>;
using NetworkConnectionClosedCallback = fu2::function<void() const>;
using AssetsDownloadFinishedCallback = fu2::function<void(bool success) const>;

class Instance;

class AssetDownloadFileProgress final: public SLNet::FileListTransferCBInterface {
private:
Instance *_instance = nullptr;

public:
AssetDownloadFileProgress(Instance *instance): _instance(instance) {}

bool OnFile(SLNet::FileListTransferCBInterface::OnFileStruct *onFileStruct) override;

void OnFileProgress(SLNet::FileListTransferCBInterface::FileProgressStruct *fps) override;

bool OnDownloadComplete(DownloadCompleteStruct *dcs) override;
};

struct InstanceOptions {
int64_t discordAppId = 0;
Expand All @@ -55,6 +73,12 @@ namespace Framework::Integrations::Client {
std::string _nickname;
};

struct AssetDownloadStatus {
float progress {0.0f};
bool downloading;
uint16_t setID;
};

class Instance {
private:
bool _initialized = false;
Expand All @@ -74,12 +98,20 @@ namespace Framework::Integrations::Client {
CurrentState _currentState;
NetworkConnectionFinalizedCallback _onConnectionFinalized;
NetworkConnectionClosedCallback _onConnectionClosed;
AssetsDownloadFinishedCallback _onAssetsDownloadFinished;

// Entity factories
std::unique_ptr<World::Archetypes::PlayerFactory> _playerFactory;
std::unique_ptr<World::Archetypes::StreamingFactory> _streamingFactory;

void InitNetworkingMessages() const;
// assets
AssetDownloadStatus _downloadStatus {};
std::string _assetDownloadPath;
bool _initialDownloadDone {};

void InitNetworkingMessages();
void InitAssetDownloader();
void OnAssetsDownloaded(bool success);

public:
Instance();
Expand All @@ -98,6 +130,8 @@ namespace Framework::Integrations::Client {

ClientError RenderInit();

void DownloadsAssetsFromConnectedServer();

InstanceOptions *GetOptions() {
return &_opts;
}
Expand All @@ -122,6 +156,10 @@ namespace Framework::Integrations::Client {
_onConnectionClosed = std::move(cb);
}

void SetOnAssetsDownloadFinishedCallback(AssetsDownloadFinishedCallback cb) {
_onAssetsDownloadFinished = std::move(cb);
}

Networking::Engine *GetNetworkingEngine() const {
return _networkingEngine.get();
}
Expand Down Expand Up @@ -153,5 +191,20 @@ namespace Framework::Integrations::Client {
World::Archetypes::StreamingFactory *GetStreamingFactory() const {
return _streamingFactory.get();
}

AssetDownloadStatus &GetAssetDownloadStatus() {
return _downloadStatus;
}

void SetAssetCachePath(const std::string &path) {
_assetDownloadPath = path;
}

const std::string &GetAssetCachePath() const {
return _assetDownloadPath;
}

friend class AssetDownloadFileProgress;
friend class AssetFileTransfer;
};
} // namespace Framework::Integrations::Client
Loading

0 comments on commit d051a9e

Please sign in to comment.