Skip to content
Merged
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
27 changes: 27 additions & 0 deletions Generals/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@
#include "Common/version.h"
#include "Lib/PathUtil.h"

// GeneralsX @feature fbraz3 04/10/2026 Upload replay to NGMP storage on game end
#if defined(SAGE_USE_NGMP) && __has_include("GameNetwork/GeneralsOnline/OnlineServices_Manager.h")
#if defined(min)
#undef min
#endif
#if defined(max)
#undef max
#endif
#include "GameNetwork/GeneralsOnline/OnlineServices_Manager.h"
class NGMPGame;
extern NGMPGame* TheNGMPGame;
#endif

constexpr const char s_genrep[] = "GENREP";
constexpr const UnsignedInt replayBufferBytes = 8192;

Expand Down Expand Up @@ -675,6 +688,20 @@ void RecorderClass::stopRecording() {

if (m_archiveReplays)
archiveReplay(m_fileName);

// GeneralsX @feature fbraz3 04/10/2026 Upload replay to NGMP storage on game end
#if defined(SAGE_USE_NGMP) && __has_include("GameNetwork/GeneralsOnline/OnlineServices_Manager.h")
if (TheNGMPGame != nullptr)
{
AsciiString absoluteReplayPath = getReplayDir();
absoluteReplayPath.concat(m_fileName);
if (!absoluteReplayPath.endsWith(getReplayExtention())) {
absoluteReplayPath.concat(getReplayExtention());
}

NGMP_OnlineServicesManager::getInstance().commitReplay(absoluteReplayPath.str());
}
#endif
}
m_fileName.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@

#include "GameNetwork/GameInfo.h"
#include "GameNetwork/GeneralsOnline/NGMP_types.h"

#if defined(min)
#undef min
#endif
#if defined(max)
#undef max
#endif

#include <chrono>

struct LobbyEntry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <thread>
#include <atomic>
Expand Down Expand Up @@ -251,6 +252,10 @@ class NGMP_OnlineServicesManager {
static NetworkMesh* GetNetworkMesh();
NetworkMesh* getNetworkMesh() { return m_pNetworkMesh.get(); }

// GeneralsX @feature fbraz3 04/10/2026 Replay upload routines for Cloudflare R2
void commitReplay(const std::string& absoluteReplayPath);
void setReplayUploadUrl(uint64_t matchId, const std::string& uploadUrl);

// Internal thread-safe event poster (called from worker threads)
void postEvent(const NGMPEvent& event);

Expand Down Expand Up @@ -324,10 +329,32 @@ class NGMP_OnlineServicesManager {
// Chat WebSocket session
std::unique_ptr<NGMP::NGMPWebSocket> m_chatSession;

// GeneralsX @feature fbraz3 03/10/2026 Periodic match progress timer
// GeneralsX @feature fbraz3 04/10/2026 Periodic match progress timer
std::chrono::steady_clock::time_point m_lastMatchProgressTime{};
uint64_t m_lastMatchProgressMatchId{0};

// GeneralsX @feature fbraz3 04/10/2026 Replay upload queue & worker state
struct ReplayUploadTask {
uint64_t matchId{0};
std::vector<uint8_t> bytes;
std::string uploadUrl;
};
void dispatchReplayUpload(uint64_t matchId, std::vector<uint8_t> bytes, std::string uploadUrl);
void replayWorkerLoop();
void stopReplayWorker();

struct PendingReplayUpload {
std::vector<uint8_t> bytes;
std::string uploadUrl;
};
mutable std::mutex m_replayMutex;
std::unordered_map<uint64_t, PendingReplayUpload> m_pendingReplayUploads;
std::queue<ReplayUploadTask> m_replayQueue;
std::condition_variable m_replayQueueCv;
std::thread m_replayWorkerThread;
std::atomic<bool> m_replayWorkerStopping{false};
std::atomic<bool> m_shuttingDown{false};

mutable std::mutex m_eventMutex;
std::queue<NGMPEvent> m_eventQueue;
std::queue<NGMPEvent> m_uiEventQueue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include <functional>
#include <cstdint>
#include <thread>
#include <mutex>
#include "GameNetwork/RankPointValue.h"
#include "GameNetwork/GameSpy/PersistentStorageThread.h"

Expand All @@ -26,10 +28,16 @@ class NGMP_OnlineServices_StatsInterface
{
public:
NGMP_OnlineServices_StatsInterface();
~NGMP_OnlineServices_StatsInterface();

void findPlayerStatsByID(int64_t userID, std::function<void(bool, PSPlayerStats)> callback, EStatsRequestPolicy policy = EStatsRequestPolicy::RESPECT_CACHE_ALLOW_REQUEST);
void CommitMyOutcome(ScoreKeeper* pScoreKeeper, bool bWon);
void quiesceOutcome();
// GeneralsX @feature fbraz3 03/10/2026 Send initial or periodic match progress telemetry to server
void SendMatchProgress(bool isInitial = false);
void UpdateMyStats(PSPlayerStats stats) {}

private:
std::thread m_outcomeThread;
std::mutex m_outcomeMutex;
};
27 changes: 27 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@
#include "Common/version.h"
#include "Lib/PathUtil.h"

// GeneralsX @feature fbraz3 04/10/2026 Upload replay to NGMP storage on game end
#if defined(SAGE_USE_NGMP)
#if defined(min)
#undef min
#endif
#if defined(max)
#undef max
#endif
#include "GameNetwork/GeneralsOnline/OnlineServices_Manager.h"
class NGMPGame;
extern NGMPGame* TheNGMPGame;
#endif


constexpr const char s_genrep[] = "GENREP";
constexpr const UnsignedInt replayBufferBytes = 8192;
Expand Down Expand Up @@ -678,6 +691,20 @@ void RecorderClass::stopRecording() {

if (m_archiveReplays)
archiveReplay(m_fileName);

// GeneralsX @feature fbraz3 04/10/2026 Upload replay to NGMP storage on game end
#if defined(SAGE_USE_NGMP)
if (TheNGMPGame != nullptr)
{
AsciiString absoluteReplayPath = getReplayDir();
absoluteReplayPath.concat(m_fileName);
if (!absoluteReplayPath.endsWith(getReplayExtention())) {
absoluteReplayPath.concat(getReplayExtention());
}

NGMP_OnlineServicesManager::getInstance().commitReplay(absoluteReplayPath.str());
}
#endif
}
m_fileName.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ bool NGMP_OnlineServicesManager::init() {
TheGameSpyPSMessageQueue = new DummyGameSpyPSMessageQueue();
}

{
std::lock_guard<std::mutex> lock(m_replayMutex);
m_shuttingDown = false;
m_replayWorkerStopping = false;
}

m_initialized = true;
return true;
}
Expand All @@ -114,6 +120,8 @@ void NGMP_OnlineServicesManager::shutdown() {
return;
}

m_shuttingDown = true;

fprintf(stderr, "[NGMP] Shutting down NGMP Online Services\n");
fflush(stderr);

Expand Down Expand Up @@ -145,8 +153,20 @@ void NGMP_OnlineServicesManager::shutdown() {
m_playlistsThread.join();
}

// GeneralsX @bugfix fbraz3 05/10/2026 Quiesce outcome worker before stopping replay upload worker so no subsequent upload can be started
if (m_pStatsInterface) {
m_pStatsInterface->quiesceOutcome();
}

stopReplayWorker();

logout();

{
std::lock_guard<std::mutex> lock(m_replayMutex);
m_pendingReplayUploads.clear();
}

if (TheNGMPGame) {
delete TheNGMPGame;
TheNGMPGame = nullptr;
Expand Down
Loading
Loading