From bfcda4629dce195aa4577ba41b8c6c7850195ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Thu, 20 Feb 2025 23:52:12 +0100 Subject: [PATCH 1/6] add: asset streamer Initial implementation of client assets streaming. Client-side scripting implementation is still pending. This feature only takes care of downloading promoted files from the server. TODO: * Implement a simple download dialog in web app to display progress and potential errors * Implement client-side scripting and ensure Lua files are sent pre-compiled and bundled --- code/framework/CMakeLists.txt | 2 +- .../src/integrations/client/instance.cpp | 103 +++++++++++++++++- .../src/integrations/client/instance.h | 46 +++++++- .../src/integrations/server/instance.cpp | 66 ++++++++--- .../src/integrations/server/instance.h | 1 + .../networking/messages/client_handshake.h | 26 +---- .../networking/messages/client_ready_assets.h | 42 +++++++ .../messages/client_request_streamer.h | 56 ++++++++++ .../src/networking/messages/messages.h | 3 + .../src/networking/network_client.cpp | 17 ++- .../framework/src/networking/network_client.h | 21 +++- code/framework/src/networking/network_peer.h | 7 ++ .../src/networking/network_server.cpp | 7 +- .../framework/src/networking/network_server.h | 4 +- code/framework/src/scripting/module.h | 4 + .../include/slikenet/DirectoryDeltaTransfer.h | 9 +- .../Source/src/DirectoryDeltaTransfer.cpp | 4 + 17 files changed, 368 insertions(+), 50 deletions(-) create mode 100644 code/framework/src/networking/messages/client_ready_assets.h create mode 100644 code/framework/src/networking/messages/client_request_streamer.h diff --git a/code/framework/CMakeLists.txt b/code/framework/CMakeLists.txt index a3a72cb63..dc5921812 100644 --- a/code/framework/CMakeLists.txt +++ b/code/framework/CMakeLists.txt @@ -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) diff --git a/code/framework/src/integrations/client/instance.cpp b/code/framework/src/integrations/client/instance.cpp index 780bec5ad..311f0dc6f 100644 --- a/code/framework/src/integrations/client/instance.cpp +++ b/code/framework/src/integrations/client/instance.cpp @@ -9,6 +9,8 @@ #include "instance.h" #include +#include +#include #include #include #include @@ -17,6 +19,11 @@ #include "../shared/modules/mod.hpp" +#include +#include +#include +#include + #include #include "utils/version.h" @@ -24,6 +31,34 @@ #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(); + return false; + } + Instance::Instance() { _networkingEngine = std::make_unique(); _presence = std::make_unique(); @@ -77,6 +112,7 @@ namespace Framework::Integrations::Client { } InitNetworkingMessages(); + InitAssetDownloader(); PostInit(); Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->info("Mod subsystems initialized"); @@ -93,6 +129,14 @@ namespace Framework::Integrations::Client { return ClientError::CLIENT_NONE; } + void Instance::InitAssetDownloader() { + cppfs::fs::open("cache").createDirectory(); + + GetNetworkingEngine()->GetNetworkClient()->SetOnAssetsDownloadFailedCallback([this]() { + this->OnAssetsDownloadFailed(); + }); + } + ClientError Instance::RenderInit() { if (_renderInitialized) { return ClientError::CLIENT_NONE; @@ -201,17 +245,48 @@ 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(GameMessages::GAME_CONNECTION_READY_ASSETS, [this, net](SLNet::RakNetGUID _guid, ClientReadyAssets *msg) { + 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); + + // TODO grab client entry point from the message + // GetClientEntryPoint() and only set client scripting up if it's specified and file is present + + 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(); + + // assume assets are already downloaded + _downloadStatus.progress = 1.0f; + _downloadStatus.downloading = false; + OnAssetsDownloaded(); + } + } + Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); + + streamer->DownloadFromSubdirectory(nullptr, "/", true, net->GetPeer()->GetSystemAddressFromIndex(0), new AssetDownloadFileProgress(this), HIGH_PRIORITY, 0, nullptr); + }); net->RegisterMessage(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()); @@ -268,4 +343,28 @@ namespace Framework::Integrations::Client { Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Game sync networking messages registered"); } + + void Instance::OnAssetsDownloaded() { + const auto net = GetNetworkingEngine()->GetNetworkClient(); + if (_downloadStatus.downloading == false && _downloadStatus.progress == 1.0f) { + Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("All the assets have been downloaded!"); + Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); + + 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 = {}; + } + } + + void Instance::OnAssetsDownloadFailed() { + Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->error("There has been an issue downloading assets!"); + Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); + + // assume assets are already downloaded + _downloadStatus.progress = 1.0f; + _downloadStatus.downloading = false; + OnAssetsDownloaded(); + } } // namespace Framework::Integrations::Client diff --git a/code/framework/src/integrations/client/instance.h b/code/framework/src/integrations/client/instance.h index 0f41813fc..56a2d8f32 100644 --- a/code/framework/src/integrations/client/instance.h +++ b/code/framework/src/integrations/client/instance.h @@ -17,6 +17,7 @@ #include #include "networking/engine.h" +#include #include #include @@ -32,6 +33,22 @@ namespace Framework::Integrations::Client { using NetworkConnectionFinalizedCallback = fu2::function; using NetworkConnectionClosedCallback = fu2::function; + 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; bool usePresence = true; @@ -55,6 +72,11 @@ namespace Framework::Integrations::Client { std::string _nickname; }; + struct AssetDownloadStatus { + float progress {0.0f}; + bool downloading; + }; + class Instance { private: bool _initialized = false; @@ -79,7 +101,14 @@ namespace Framework::Integrations::Client { std::unique_ptr _playerFactory; std::unique_ptr _streamingFactory; - void InitNetworkingMessages() const; + // assets + AssetDownloadStatus _downloadStatus {}; + std::string _assetDownloadPath; + + void InitNetworkingMessages(); + void InitAssetDownloader(); + void OnAssetsDownloaded(); + void OnAssetsDownloadFailed(); public: Instance(); @@ -153,5 +182,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 diff --git a/code/framework/src/integrations/server/instance.cpp b/code/framework/src/integrations/server/instance.cpp index 3bdd3e225..ac2e8bd69 100644 --- a/code/framework/src/integrations/server/instance.cpp +++ b/code/framework/src/integrations/server/instance.cpp @@ -13,12 +13,15 @@ #include "networking/messages/client_connection_finalized.h" #include "networking/messages/client_handshake.h" #include "networking/messages/client_initialise_player.h" +#include "networking/messages/client_request_streamer.h" +#include "networking/messages/client_ready_assets.h" #include "networking/messages/client_kick.h" #include "networking/messages/messages.h" #include "../shared/modules/mod.hpp" #include "utils/version.h" +#include "utils/path.h" #include "cxxopts.hpp" @@ -145,6 +148,9 @@ namespace Framework::Integrations::Server { return ServerError::SERVER_SCRIPTING_INIT_FAILED; } + // Initialize asset streamer + InitAssetStreamer(); + // Load the gamemode _scriptingEngine->GetServerEngine()->LoadScript(); @@ -224,7 +230,7 @@ namespace Framework::Integrations::Server { using namespace Framework::Networking::Messages; const auto net = _networkingEngine->GetNetworkServer(); net->RegisterMessage(Framework::Networking::Messages::GameMessages::GAME_CONNECTION_HANDSHAKE, [this, net](SLNet::RakNetGUID guid, ClientHandshake *msg) { - Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Received handshake message for player {}", msg->GetPlayerName()); + Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Received handshake message for incoming player guid {}", guid.g); // Make sure handshake payload was correctly formatted if (!msg->Valid()) { @@ -275,6 +281,28 @@ namespace Framework::Integrations::Server { return; } + // Let the client know they can ask for client-side assets now. + ClientReadyAssets readyMsg; + net->Send(readyMsg, guid); + }); + + net->SetOnPlayerDisconnectCallback([this, net](SLNet::Packet *packet, uint32_t reason) { + const auto guid = packet->guid; + Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Disconnecting peer {}, reason: {}", guid.g, reason); + + const auto e = _worldEngine->GetEntityByGUID(guid.g); + if (e.is_valid()) { + if (_onPlayerDisconnectCallback) + _onPlayerDisconnectCallback(e, guid.g); + + _worldEngine->RemoveEntity(e); + } + + net->GetPeer()->CloseConnection(guid, true); + }); + + + net->RegisterMessage(GameMessages::GAME_CONNECTION_REQUEST_STREAMER, [this, net](SLNet::RakNetGUID guid, ClientRequestStreamer *msg) { // Create player entity and add on world const auto newPlayer = _worldEngine->CreateEntity(); _streamingFactory->SetupServer(newPlayer, guid.g); @@ -294,21 +322,6 @@ namespace Framework::Integrations::Server { net->Send(answer, guid); }); - net->SetOnPlayerDisconnectCallback([this, net](SLNet::Packet *packet, uint32_t reason) { - const auto guid = packet->guid; - Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Disconnecting peer {}, reason: {}", guid.g, reason); - - const auto e = _worldEngine->GetEntityByGUID(guid.g); - if (e.is_valid()) { - if (_onPlayerDisconnectCallback) - _onPlayerDisconnectCallback(e, guid.g); - - _worldEngine->RemoveEntity(e); - } - - net->GetPeer()->CloseConnection(guid, true); - }); - net->RegisterMessage(Framework::Networking::Messages::GameMessages::GAME_INIT_PLAYER, [this, net](SLNet::RakNetGUID guid, ClientInitPlayer *stub) { const auto e = _worldEngine->GetEntityByGUID(guid.g); if (_onPlayerConnectCallback && e.is_valid() && e.is_alive()) @@ -320,6 +333,27 @@ namespace Framework::Integrations::Server { Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Game sync networking messages registered"); } + void Instance::InitAssetStreamer() { + Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Setting up asset streamer..."); + const auto net = GetNetworkingEngine()->GetNetworkServer(); + const auto streamer = net->GetAssetStreamer(); + + const auto scripting = GetScriptingEngine(); + const auto gamemodePath = scripting->GetMainPath(); + const auto clientPath = fmt::format("{}\\client", gamemodePath); + const auto clientFiles = scripting->GetClientFiles(); + const std::string assetsPath = Framework::Utils::GetAbsolutePathA(clientPath); + Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Client assets directory: {}", assetsPath); + Logging::GetLogger(FRAMEWORK_INNER_SERVER)->flush(); + + streamer->SetApplicationDirectory(assetsPath.c_str()); + + for (const auto& fileName : clientFiles) { + streamer->AddFile(fmt::format("{}\\{}", assetsPath, fileName).c_str(), fileName.c_str()); + Logging::GetLogger(FRAMEWORK_INNER_SERVER)->debug("Added client asset: {}", fileName); + } + } + ServerError Instance::Shutdown() { if (_shuttingDown) { return ServerError::SERVER_NONE; diff --git a/code/framework/src/integrations/server/instance.h b/code/framework/src/integrations/server/instance.h index 41e595fad..49dc8a3f5 100644 --- a/code/framework/src/integrations/server/instance.h +++ b/code/framework/src/integrations/server/instance.h @@ -92,6 +92,7 @@ namespace Framework::Integrations::Server { void InitEndpoints(); void InitModules() const; void InitNetworkingMessages() const; + void InitAssetStreamer(); bool LoadConfigFromJSON(); void RegisterScriptingBuiltins(Framework::Scripting::ServerEngine *); diff --git a/code/framework/src/networking/messages/client_handshake.h b/code/framework/src/networking/messages/client_handshake.h index 91c09c018..ec880a90e 100644 --- a/code/framework/src/networking/messages/client_handshake.h +++ b/code/framework/src/networking/messages/client_handshake.h @@ -15,9 +15,6 @@ namespace Framework::Networking::Messages { class ClientHandshake final: public IMessage { private: - SLNet::RakString _playerName = ""; - SLNet::RakString _playerSteamId = ""; - SLNet::RakString _playerDiscordId = ""; SLNet::RakString _clientVersion = ""; SLNet::RakString _fwVersion = ""; SLNet::RakString _gameVersion = ""; @@ -28,11 +25,7 @@ namespace Framework::Networking::Messages { return GAME_CONNECTION_HANDSHAKE; } - void FromParameters(const std::string &playerName, const std::string &playerSteamId, const std::string &playerDiscordId, const std::string &clientVersion, const std::string &fwVersion, const std::string &gameVersion, const std::string &gameName) { - Framework::Logging::GetLogger("dbg")->debug(playerName); - _playerName = SLNet::RakString(playerName.c_str()); - _playerSteamId = SLNet::RakString(playerSteamId.c_str()); - _playerDiscordId = SLNet::RakString(playerDiscordId.c_str()); + void FromParameters(const std::string &clientVersion, const std::string &fwVersion, const std::string &gameVersion, const std::string &gameName) { _fwVersion = SLNet::RakString(fwVersion.c_str()); _clientVersion = SLNet::RakString(clientVersion.c_str()); _gameVersion = SLNet::RakString(gameVersion.c_str()); @@ -40,9 +33,6 @@ namespace Framework::Networking::Messages { } void Serialize(SLNet::BitStream *bs, bool write) override { - bs->Serialize(write, _playerName); - bs->Serialize(write, _playerSteamId); - bs->Serialize(write, _playerDiscordId); bs->Serialize(write, _fwVersion); bs->Serialize(write, _clientVersion); bs->Serialize(write, _gameVersion); @@ -50,19 +40,7 @@ namespace Framework::Networking::Messages { } bool Valid() const override { - return _playerName.GetLength() > 0 && (_playerSteamId.GetLength() > 0 || _playerDiscordId.GetLength() > 0) && _fwVersion.GetLength() > 0 && _clientVersion.GetLength() > 0 && _gameVersion.GetLength() > 0 && _gameName.GetLength() > 0; - } - - std::string GetPlayerName() const { - return _playerName.C_String(); - } - - std::string GetPlayerSteamID() const { - return _playerSteamId.C_String(); - } - - std::string GetPlayerDiscordID() const { - return _playerDiscordId.C_String(); + return _fwVersion.GetLength() > 0 && _clientVersion.GetLength() > 0 && _gameVersion.GetLength() > 0 && _gameName.GetLength() > 0; } std::string GetFWVersion() const { diff --git a/code/framework/src/networking/messages/client_ready_assets.h b/code/framework/src/networking/messages/client_ready_assets.h new file mode 100644 index 000000000..3356617bf --- /dev/null +++ b/code/framework/src/networking/messages/client_ready_assets.h @@ -0,0 +1,42 @@ +/* + * MafiaHub OSS license + * Copyright (c) 2021-2023, MafiaHub. All rights reserved. + * + * This file comes from MafiaHub, hosted at https://github.com/MafiaHub/Framework. + * See LICENSE file in the source repository for information regarding licensing. + */ + +#pragma once + +#include "messages.h" + +#include +#include + +namespace Framework::Networking::Messages { + class ClientReadyAssets final: public IMessage { + private: + SLNet::RakString _clientEntryPoint; + + public: + uint8_t GetMessageID() const override { + return GAME_CONNECTION_READY_ASSETS; + } + + void FromParameters(const std::string& clientEntryPoint) { + _clientEntryPoint = clientEntryPoint.c_str(); + } + + void Serialize(SLNet::BitStream *bs, bool write) override { + bs->Serialize(write, _clientEntryPoint); + } + + const std::string GetClientEntryPoint() const { + return _clientEntryPoint.C_String(); + } + + bool Valid() const override { + return true; + } + }; +} // namespace Framework::Networking::Messages diff --git a/code/framework/src/networking/messages/client_request_streamer.h b/code/framework/src/networking/messages/client_request_streamer.h new file mode 100644 index 000000000..515442b6b --- /dev/null +++ b/code/framework/src/networking/messages/client_request_streamer.h @@ -0,0 +1,56 @@ +/* + * MafiaHub OSS license + * Copyright (c) 2021-2023, MafiaHub. All rights reserved. + * + * This file comes from MafiaHub, hosted at https://github.com/MafiaHub/Framework. + * See LICENSE file in the source repository for information regarding licensing. + */ + +#pragma once + +#include "messages.h" + +#include + +namespace Framework::Networking::Messages { + class ClientRequestStreamer final: public IMessage { + private: + SLNet::RakString _playerName = ""; + SLNet::RakString _playerSteamId = ""; + SLNet::RakString _playerDiscordId = ""; + + public: + uint8_t GetMessageID() const override { + return GAME_CONNECTION_REQUEST_STREAMER; + } + + void FromParameters(const std::string &playerName, const std::string &playerSteamId, const std::string &playerDiscordId) { + Framework::Logging::GetLogger("dbg")->debug(playerName); + _playerName = SLNet::RakString(playerName.c_str()); + _playerSteamId = SLNet::RakString(playerSteamId.c_str()); + _playerDiscordId = SLNet::RakString(playerDiscordId.c_str()); + } + + void Serialize(SLNet::BitStream *bs, bool write) override { + bs->Serialize(write, _playerName); + bs->Serialize(write, _playerSteamId); + bs->Serialize(write, _playerDiscordId); + } + + bool Valid() const override { + return _playerName.GetLength() > 0 && (_playerSteamId.GetLength() > 0 || _playerDiscordId.GetLength() > 0); + } + + std::string GetPlayerName() const { + return _playerName.C_String(); + } + + std::string GetPlayerSteamID() const { + return _playerSteamId.C_String(); + } + + std::string GetPlayerDiscordID() const { + return _playerDiscordId.C_String(); + } + }; +} // namespace Framework::Networking::Messages diff --git a/code/framework/src/networking/messages/messages.h b/code/framework/src/networking/messages/messages.h index f97ac73f7..35d0f88c1 100644 --- a/code/framework/src/networking/messages/messages.h +++ b/code/framework/src/networking/messages/messages.h @@ -42,6 +42,9 @@ namespace Framework::Networking::Messages { enum GameMessages : uint8_t { // Game messages handling common client connection flow GAME_CONNECTION_HANDSHAKE = INTERNAL_NEXT_MESSAGE_ID, + GAME_CONNECTION_ACKNOWLEDGE_CLIENT, + GAME_CONNECTION_READY_ASSETS, + GAME_CONNECTION_REQUEST_STREAMER, GAME_CONNECTION_FINALIZED, GAME_CONNECTION_KICKED, GAME_INIT_PLAYER, diff --git a/code/framework/src/networking/network_client.cpp b/code/framework/src/networking/network_client.cpp index 04fce203f..18dc9b478 100644 --- a/code/framework/src/networking/network_client.cpp +++ b/code/framework/src/networking/network_client.cpp @@ -17,13 +17,18 @@ namespace Framework::Networking { Shutdown(); } - ClientError NetworkClient::Init() const { + ClientError NetworkClient::Init() { SLNet::SocketDescriptor sd {}; const SLNet::StartupResult result = _peer->Startup(1, &sd, 1); if (result != SLNet::RAKNET_STARTED && result != SLNet::RAKNET_ALREADY_STARTED) { Logging::GetLogger(FRAMEWORK_INNER_NETWORKING)->critical("Failed to init the networking peer. Reason: {}", GetStartupResultString(result)); return CLIENT_PEER_FAILED; } + + _assetStreamer.SetFileListTransferPlugin(&_fileListTransfer); + _peer->AttachPlugin(&_fileListTransfer); + _peer->AttachPlugin(&_assetStreamer); + return CLIENT_NONE; } @@ -161,4 +166,14 @@ namespace Framework::Networking { return _peer->GetAveragePing(_peer->GetSystemAddressFromIndex(0)); } + + void AssetFileTransfer::OnClosedConnection(const SLNet::SystemAddress &systemAddress, SLNet::RakNetGUID rakNetGUID, SLNet::PI2_LostConnectionReason lostConnectionReason) { + if (_cb) { + (*_cb)(); + } + } + + void AssetFileTransfer::SetCallback(OnAssetsDownloadFailedCallback *cb) { + _cb = cb; + } } // namespace Framework::Networking diff --git a/code/framework/src/networking/network_client.h b/code/framework/src/networking/network_client.h index f5d2755eb..9d3c1ae63 100644 --- a/code/framework/src/networking/network_client.h +++ b/code/framework/src/networking/network_client.h @@ -20,19 +20,32 @@ #include namespace Framework::Networking { + using OnAssetsDownloadFailedCallback = fu2::function; + + class AssetFileTransfer final: public SLNet::FileListTransfer { + private: + OnAssetsDownloadFailedCallback *_cb = nullptr; + + public: + void SetCallback(OnAssetsDownloadFailedCallback *cb); + void OnClosedConnection(const SLNet::SystemAddress &systemAddress, SLNet::RakNetGUID rakNetGUID, SLNet::PI2_LostConnectionReason lostConnectionReason) override; + }; class NetworkClient: public NetworkPeer { private: + PeerState _state; Messages::PacketCallback _onPlayerConnectedCallback; Messages::DisconnectPacketCallback _onPlayerDisconnectedCallback; - + OnAssetsDownloadFailedCallback _onAssetsDownloadFailedCallback; + AssetFileTransfer _fileListTransfer; public: + NetworkClient(); ~NetworkClient(); - ClientError Init() const; + ClientError Init(); ClientError Shutdown(); void Update() override; @@ -56,6 +69,10 @@ namespace Framework::Networking { _onPlayerDisconnectedCallback = std::move(callback); } + void SetOnAssetsDownloadFailedCallback(OnAssetsDownloadFailedCallback callback) { + _onAssetsDownloadFailedCallback = callback; + } + template bool SendGameRPC(T &rpc, SLNet::RakNetGUID guid = SLNet::UNASSIGNED_RAKNET_GUID, PacketPriority priority = HIGH_PRIORITY, PacketReliability reliability = RELIABLE_ORDERED) { SLNet::BitStream bs; diff --git a/code/framework/src/networking/network_peer.h b/code/framework/src/networking/network_peer.h index 00a92fc6a..c7a0a13e6 100644 --- a/code/framework/src/networking/network_peer.h +++ b/code/framework/src/networking/network_peer.h @@ -12,6 +12,8 @@ #include #include +#include +#include #include #include #include @@ -26,6 +28,7 @@ namespace Framework::Networking { std::unordered_map> _registeredRPCs; std::unordered_map _registeredMessageCallbacks; Messages::PacketCallback _onUnknownPacketCallback; + SLNet::DirectoryDeltaTransfer _assetStreamer; public: NetworkPeer(); @@ -146,6 +149,10 @@ namespace Framework::Networking { static const char *GetStartupResultString(uint8_t id); static const char *GetConnectionAttemptString(uint8_t id); + SLNet::DirectoryDeltaTransfer* GetAssetStreamer() { + return &_assetStreamer; + } + static inline NetworkPeer *_networkRef = nullptr; }; } // namespace Framework::Networking diff --git a/code/framework/src/networking/network_server.cpp b/code/framework/src/networking/network_server.cpp index 4e3380dbc..54a8a1452 100644 --- a/code/framework/src/networking/network_server.cpp +++ b/code/framework/src/networking/network_server.cpp @@ -13,7 +13,7 @@ #include namespace Framework::Networking { - ServerError NetworkServer::Init(int32_t port, const std::string &host, int32_t maxPlayers, const std::string &password) const { + ServerError NetworkServer::Init(int32_t port, const std::string &host, int32_t maxPlayers, const std::string &password) { auto newSocketSd = SLNet::SocketDescriptor((uint16_t)port, host.c_str()); const SLNet::StartupResult result = _peer->Startup(maxPlayers, &newSocketSd, 1); if (result != SLNet::RAKNET_STARTED) { @@ -27,6 +27,11 @@ namespace Framework::Networking { } _peer->SetMaximumIncomingConnections((uint16_t)maxPlayers); + + _assetStreamer.SetFileListTransferPlugin(&_fileListTransfer); + _peer->AttachPlugin(&_fileListTransfer); + _peer->AttachPlugin(&_assetStreamer); + return SERVER_NONE; } diff --git a/code/framework/src/networking/network_server.h b/code/framework/src/networking/network_server.h index 7b88f9c02..d4b0e800d 100644 --- a/code/framework/src/networking/network_server.h +++ b/code/framework/src/networking/network_server.h @@ -26,13 +26,15 @@ namespace Framework::Networking { private: Messages::PacketCallback _onPlayerConnectCallback; Messages::DisconnectPacketCallback _onPlayerDisconnectCallback; + SLNet::FileListTransfer _fileListTransfer; + bool SendGameRPCInternal(SLNet::BitStream &bs, Framework::World::ServerEngine *world, flecs::entity_t ent, SLNet::RakNetGUID guid = SLNet::UNASSIGNED_RAKNET_GUID, SLNet::RakNetGUID excludeGUID = SLNet::UNASSIGNED_RAKNET_GUID, PacketPriority priority = HIGH_PRIORITY, PacketReliability reliability = RELIABLE_ORDERED) const; public: NetworkServer(): NetworkPeer() {} - ServerError Init(int32_t port, const std::string &host, int32_t maxPlayers, const std::string &password = "") const; + ServerError Init(int32_t port, const std::string &host, int32_t maxPlayers, const std::string &password = ""); ServerError Shutdown() const; bool HandlePacket(uint8_t packetID, SLNet::Packet *packet) override; diff --git a/code/framework/src/scripting/module.h b/code/framework/src/scripting/module.h index 928065958..6150c13cd 100644 --- a/code/framework/src/scripting/module.h +++ b/code/framework/src/scripting/module.h @@ -56,6 +56,10 @@ namespace Framework::Scripting { return _serverFiles; } + const std::string GetMainPath() const { + return _mainPath; + } + void SetMainPath(const std::string &mainPath) { _mainPath = mainPath; } diff --git a/vendors/slikenet/Source/include/slikenet/DirectoryDeltaTransfer.h b/vendors/slikenet/Source/include/slikenet/DirectoryDeltaTransfer.h index 01f426201..6bee185e9 100644 --- a/vendors/slikenet/Source/include/slikenet/DirectoryDeltaTransfer.h +++ b/vendors/slikenet/Source/include/slikenet/DirectoryDeltaTransfer.h @@ -97,7 +97,14 @@ class RAK_DLL_EXPORT DirectoryDeltaTransfer : public PluginInterface2 /// All files in the resultant directory and subdirectories are then hashed so that users can download them. /// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin /// \param[in] subdir Concatenated with pathToApplication to form the final path from which to allow uploads. - void AddUploadsFromSubdirectory(const char *subdir); + void AddUploadsFromSubdirectory(const char *subdir); + + /// + /// Add a specific file to the file list + /// + /// relative path to the file + /// name of the particular file, without path + void AddFile(const char *filePath, const char *fileName); /// \brief Downloads files from the matching parameter \a subdir in AddUploadsFromSubdirectory. /// \details \a subdir must contain all starting characters in \a subdir in AddUploadsFromSubdirectory diff --git a/vendors/slikenet/Source/src/DirectoryDeltaTransfer.cpp b/vendors/slikenet/Source/src/DirectoryDeltaTransfer.cpp index e87b656f4..6ac532059 100644 --- a/vendors/slikenet/Source/src/DirectoryDeltaTransfer.cpp +++ b/vendors/slikenet/Source/src/DirectoryDeltaTransfer.cpp @@ -134,6 +134,10 @@ void DirectoryDeltaTransfer::SetUploadSendParameters(PacketPriority _priority, c { priority=_priority; orderingChannel=_orderingChannel; +} +void DirectoryDeltaTransfer::AddFile(const char* filePath, const char* fileName) +{ + availableUploads->AddFile(filePath, fileName, FileListNodeContext(0, 0, 0, 0)); } void DirectoryDeltaTransfer::AddUploadsFromSubdirectory(const char *subdir) { From 130c75aaa55b80fbff8db429115cc08873842358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Thu, 20 Feb 2025 23:59:47 +0100 Subject: [PATCH 2/6] fix: change ordering channel to 2 Avoid using the same channel we use for general sync. --- code/framework/src/integrations/client/instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/framework/src/integrations/client/instance.cpp b/code/framework/src/integrations/client/instance.cpp index 311f0dc6f..6a0f91eb6 100644 --- a/code/framework/src/integrations/client/instance.cpp +++ b/code/framework/src/integrations/client/instance.cpp @@ -285,7 +285,7 @@ namespace Framework::Integrations::Client { } Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); - streamer->DownloadFromSubdirectory(nullptr, "/", true, net->GetPeer()->GetSystemAddressFromIndex(0), new AssetDownloadFileProgress(this), HIGH_PRIORITY, 0, nullptr); + streamer->DownloadFromSubdirectory(nullptr, nullptr, true, net->GetPeer()->GetSystemAddressFromIndex(0), new AssetDownloadFileProgress(this), HIGH_PRIORITY, 2, nullptr); }); net->RegisterMessage(GameMessages::GAME_CONNECTION_FINALIZED, [this, net](SLNet::RakNetGUID _guid, ClientConnectionFinalized *msg) { Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Connection request finalized"); From 62969960c182fcc92a14e57979cd0ddf0f0046bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 21 Feb 2025 08:54:30 +0100 Subject: [PATCH 3/6] fix: improve asset download flow * handle re-downloads for hot reloading * provide a download finished/failed callback * stop pending download if server asks us to download assets again or the client demands it (hot reloading purposes) --- .../src/integrations/client/instance.cpp | 115 +++++++++++------- .../src/integrations/client/instance.h | 13 +- .../src/networking/network_client.cpp | 6 +- .../framework/src/networking/network_client.h | 10 +- 4 files changed, 92 insertions(+), 52 deletions(-) diff --git a/code/framework/src/integrations/client/instance.cpp b/code/framework/src/integrations/client/instance.cpp index 6a0f91eb6..da9065174 100644 --- a/code/framework/src/integrations/client/instance.cpp +++ b/code/framework/src/integrations/client/instance.cpp @@ -19,6 +19,8 @@ #include "../shared/modules/mod.hpp" +#include "networking/state.h" + #include #include #include @@ -55,7 +57,7 @@ namespace Framework::Integrations::Client { auto &downloadStatus = _instance->GetAssetDownloadStatus(); downloadStatus.progress = 1.0f; downloadStatus.downloading = false; - _instance->OnAssetsDownloaded(); + _instance->OnAssetsDownloaded(true); return false; } @@ -133,7 +135,7 @@ namespace Framework::Integrations::Client { cppfs::fs::open("cache").createDirectory(); GetNetworkingEngine()->GetNetworkClient()->SetOnAssetsDownloadFailedCallback([this]() { - this->OnAssetsDownloadFailed(); + this->OnAssetsDownloaded(false); }); } @@ -257,35 +259,7 @@ namespace Framework::Integrations::Client { net->Send(msg, SLNet::UNASSIGNED_RAKNET_GUID); }); net->RegisterMessage(GameMessages::GAME_CONNECTION_READY_ASSETS, [this, net](SLNet::RakNetGUID _guid, ClientReadyAssets *msg) { - 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); - - // TODO grab client entry point from the message - // GetClientEntryPoint() and only set client scripting up if it's specified and file is present - - 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(); - - // assume assets are already downloaded - _downloadStatus.progress = 1.0f; - _downloadStatus.downloading = false; - OnAssetsDownloaded(); - } - } - Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); - - streamer->DownloadFromSubdirectory(nullptr, nullptr, true, net->GetPeer()->GetSystemAddressFromIndex(0), new AssetDownloadFileProgress(this), HIGH_PRIORITY, 2, nullptr); + DownloadsAssetsFromConnectedServer(); }); net->RegisterMessage(GameMessages::GAME_CONNECTION_FINALIZED, [this, net](SLNet::RakNetGUID _guid, ClientConnectionFinalized *msg) { Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Connection request finalized"); @@ -331,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 @@ -344,27 +323,75 @@ namespace Framework::Integrations::Client { Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("Game sync networking messages registered"); } - void Instance::OnAssetsDownloaded() { + void Instance::DownloadsAssetsFromConnectedServer() { const auto net = GetNetworkingEngine()->GetNetworkClient(); - if (_downloadStatus.downloading == false && _downloadStatus.progress == 1.0f) { - Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->debug("All the assets have been downloaded!"); - Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); - Framework::Networking::Messages::ClientRequestStreamer req; - req.FromParameters(_currentState._nickname, "MY_SUPER_ID_1", "MY_SUPER_ID_2"); - net->Send(req, SLNet::UNASSIGNED_RAKNET_GUID); + // 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(); + + // Assume assets are already downloaded + _downloadStatus.progress = 1.0f; + _downloadStatus.downloading = false; + OnAssetsDownloaded(false); + } + } + 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::OnAssetsDownloadFailed() { - Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->error("There has been an issue downloading assets!"); + 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(); - // assume assets are already downloaded - _downloadStatus.progress = 1.0f; - _downloadStatus.downloading = false; - OnAssetsDownloaded(); + // 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 = {}; + + if (_onAssetsDownloadFinished) + _onAssetsDownloadFinished(success); + + // TODO grab client entry point from the message + // GetClientEntryPoint() and only set client scripting up if it's specified and file is present } } // namespace Framework::Integrations::Client diff --git a/code/framework/src/integrations/client/instance.h b/code/framework/src/integrations/client/instance.h index 56a2d8f32..3e8674be0 100644 --- a/code/framework/src/integrations/client/instance.h +++ b/code/framework/src/integrations/client/instance.h @@ -32,6 +32,7 @@ namespace Framework::Integrations::Client { using NetworkConnectionFinalizedCallback = fu2::function; using NetworkConnectionClosedCallback = fu2::function; + using AssetsDownloadFinishedCallback = fu2::function; class Instance; @@ -75,6 +76,7 @@ namespace Framework::Integrations::Client { struct AssetDownloadStatus { float progress {0.0f}; bool downloading; + uint16_t setID; }; class Instance { @@ -96,6 +98,7 @@ namespace Framework::Integrations::Client { CurrentState _currentState; NetworkConnectionFinalizedCallback _onConnectionFinalized; NetworkConnectionClosedCallback _onConnectionClosed; + AssetsDownloadFinishedCallback _onAssetsDownloadFinished; // Entity factories std::unique_ptr _playerFactory; @@ -104,11 +107,11 @@ namespace Framework::Integrations::Client { // assets AssetDownloadStatus _downloadStatus {}; std::string _assetDownloadPath; + bool _initialDownloadDone {}; void InitNetworkingMessages(); void InitAssetDownloader(); - void OnAssetsDownloaded(); - void OnAssetsDownloadFailed(); + void OnAssetsDownloaded(bool success); public: Instance(); @@ -127,6 +130,8 @@ namespace Framework::Integrations::Client { ClientError RenderInit(); + void DownloadsAssetsFromConnectedServer(); + InstanceOptions *GetOptions() { return &_opts; } @@ -151,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(); } diff --git a/code/framework/src/networking/network_client.cpp b/code/framework/src/networking/network_client.cpp index 18dc9b478..4745cbb5e 100644 --- a/code/framework/src/networking/network_client.cpp +++ b/code/framework/src/networking/network_client.cpp @@ -169,11 +169,11 @@ namespace Framework::Networking { void AssetFileTransfer::OnClosedConnection(const SLNet::SystemAddress &systemAddress, SLNet::RakNetGUID rakNetGUID, SLNet::PI2_LostConnectionReason lostConnectionReason) { if (_cb) { - (*_cb)(); + _cb(); } } - void AssetFileTransfer::SetCallback(OnAssetsDownloadFailedCallback *cb) { - _cb = cb; + void AssetFileTransfer::SetCallback(OnAssetsDownloadFailedCallback cb) { + _cb = std::move(cb); } } // namespace Framework::Networking diff --git a/code/framework/src/networking/network_client.h b/code/framework/src/networking/network_client.h index 9d3c1ae63..a493eba4a 100644 --- a/code/framework/src/networking/network_client.h +++ b/code/framework/src/networking/network_client.h @@ -24,10 +24,10 @@ namespace Framework::Networking { class AssetFileTransfer final: public SLNet::FileListTransfer { private: - OnAssetsDownloadFailedCallback *_cb = nullptr; + OnAssetsDownloadFailedCallback _cb {}; public: - void SetCallback(OnAssetsDownloadFailedCallback *cb); + void SetCallback(OnAssetsDownloadFailedCallback cb); void OnClosedConnection(const SLNet::SystemAddress &systemAddress, SLNet::RakNetGUID rakNetGUID, SLNet::PI2_LostConnectionReason lostConnectionReason) override; }; class NetworkClient: public NetworkPeer { @@ -61,6 +61,10 @@ namespace Framework::Networking { return _state; } + AssetFileTransfer* GetFileListTransfer() { + return &_fileListTransfer; + } + void SetOnPlayerConnectedCallback(Messages::PacketCallback callback) { _onPlayerConnectedCallback = std::move(callback); } @@ -70,7 +74,7 @@ namespace Framework::Networking { } void SetOnAssetsDownloadFailedCallback(OnAssetsDownloadFailedCallback callback) { - _onAssetsDownloadFailedCallback = callback; + _onAssetsDownloadFailedCallback = std::move(callback); } template From 196ed0a9a51510f681222f8f10aaa321e351f1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 21 Feb 2025 09:02:19 +0100 Subject: [PATCH 4/6] fix: improve download failed flow --- code/framework/src/integrations/client/instance.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/code/framework/src/integrations/client/instance.cpp b/code/framework/src/integrations/client/instance.cpp index da9065174..a44475405 100644 --- a/code/framework/src/integrations/client/instance.cpp +++ b/code/framework/src/integrations/client/instance.cpp @@ -355,9 +355,7 @@ namespace Framework::Integrations::Client { Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->warn("Skip downloading assets."); Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush(); - // Assume assets are already downloaded - _downloadStatus.progress = 1.0f; - _downloadStatus.downloading = false; + // Ensure we finish the download flow gracefully OnAssetsDownloaded(false); } } @@ -388,10 +386,11 @@ namespace Framework::Integrations::Client { _downloadStatus = {}; - if (_onAssetsDownloadFinished) - _onAssetsDownloadFinished(success); - // 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 From 9ac248be33b606244dd07794d4b8d71d69457a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 21 Feb 2025 11:22:28 +0100 Subject: [PATCH 5/6] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 943f9cbc4..227cea215 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.1 +2.0.0 From 590191b576a94110b3c05a77b2b6767a287262e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 21 Feb 2025 11:37:24 +0100 Subject: [PATCH 6/6] Update instance.cpp --- code/framework/src/integrations/client/instance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/code/framework/src/integrations/client/instance.cpp b/code/framework/src/integrations/client/instance.cpp index a44475405..3193a295d 100644 --- a/code/framework/src/integrations/client/instance.cpp +++ b/code/framework/src/integrations/client/instance.cpp @@ -357,6 +357,7 @@ namespace Framework::Integrations::Client { // Ensure we finish the download flow gracefully OnAssetsDownloaded(false); + return; } } Logging::GetLogger(FRAMEWORK_INNER_CLIENT)->flush();