Skip to content
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
493 changes: 462 additions & 31 deletions relaytestapp/src/app.cpp

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions relaytestapp/src/app.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,3 +69,6 @@ void app_shockwave(const Point& pos);

// Host clears all splotches on every client
void app_clearSplotches();

// Broadcast our current relay ping to all other players (called periodically in-game)
void app_broadcastRelayPing();
35 changes: 30 additions & 5 deletions relaytestapp/src/game.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
#include "globals.h"

// C/C++ includes
#include <algorithm>
#include <imgui.h>
#include <stdio.h>
#include <chrono>
Expand All@@ -42,19 +43,37 @@ void game_update()
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize);

ImGui::Text("Player mask");
ImGui::Text("Players");
{
ImGui::Indent();
ImGui::TextDisabled("Only affect shockwaves");
if (!state.lobby.regionId.empty())
{
bool isActual = !regionFromLobbyId(state.lobby.lobbyId).empty();
ImGui::TextDisabled("%s: %s",
isActual ? "Region" : "Est. region",
state.lobby.regionId.c_str());
}
ImGui::TextDisabled("Mask = shockwave targets only");
for (auto& user : state.lobby.members)
{
auto color = COLORS[user.colorIndex % NUM_COLORS];
ImGui::PushStyleColor(ImGuiCol_Text, color);
std::string label = user.name;
if (user.cxId == state.lobby.ownerCxId) label += " [Host]";
if (user.cxId == state.user.cxId) label += " (Echo)";
if (user.cxId == state.lobby.ownerCxId) label += " [H]";
if (user.cxId == state.user.cxId) label += " (me)";
ImGui::Checkbox(label.c_str(), &user.allowSendTo);
ImGui::PopStyleColor();
ImGui::SameLine();
char pingBuf[16];
if (user.activePing < 0)
ImGui::TextDisabled("...");
else if (user.activePing >= 999)
ImGui::TextDisabled("T/O");
else
{
snprintf(pingBuf, sizeof(pingBuf), "%d ms", user.activePing);
ImGui::TextDisabled("%s", pingBuf);
}
}
ImGui::Unindent();
}
Expand DownExpand Up@@ -309,13 +328,19 @@ void game_update()
}

#if RESEND_AT_60_FPS
// Send mouse position at 60 fps
// Send mouse position at 60 fps and broadcast relay ping every 2 seconds
static auto lastTime = std::chrono::high_resolution_clock::now();
static auto lastPingBroadcastTime = std::chrono::high_resolution_clock::now();
auto now = std::chrono::high_resolution_clock::now();;
if (now - lastTime >= std::chrono::microseconds(1000000 / 60))
{
lastTime = now;
app_mouseMoved({state.mouseX, state.mouseY});
}
if (now - lastPingBroadcastTime >= std::chrono::seconds(2))
{
lastPingBroadcastTime = now;
app_broadcastRelayPing();
}
#endif
}
5 changes: 5 additions & 0 deletions relaytestapp/src/globals.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,6 +109,10 @@ bool loadConfigs()
{
settings.teamCode = value;
}
else if (strcmp(key, "usePingData") == 0)
{
settings.usePingData = std::stoi(value) != 0;
}
}
fclose(pFile);
}
Expand DownExpand Up@@ -166,6 +170,7 @@ void saveConfigs()
fprintf(pFile, "protocol = %i\n", (int)settings.protocol);
fprintf(pFile, "lobbyType = %s\n", settings.lobbyType.c_str());
fprintf(pFile, "teamCode = %s\n", settings.teamCode.c_str());
fprintf(pFile, "usePingData = %i\n", settings.usePingData ? 1 : 0);
fprintf(pFile, "autoLogin = %i\n", settings.autoLogin ? 1 : 0);
fclose(pFile);
}
Expand Down
134 changes: 129 additions & 5 deletions relaytestapp/src/globals.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@

// C/C++ includes
#include <chrono>
#include <map>
#include <string>
#include <vector>
#include <memory>
Expand DownExpand Up@@ -121,13 +122,16 @@ struct User
bool isAlive = false;
bool allowSendTo = true;
Point pos = {0, 0};
std::map<std::string, int> pings; /* Pre-game region survey latencies shared via lobby extra (ms) */
int activePing = -1; /* Live relay-server RTT broadcast during gameplay (ms); -1 = not yet received */
};

// Lobby
struct Lobby
{
std::string lobbyId;
std::string ownerCxId;
std::string regionId; /* Region extracted from lobbyId prefix (e.g. "na-east") */
std::vector<User> members;
};

Expand DownExpand Up@@ -170,16 +174,23 @@ struct State
std::vector<Shockwave> shockwaves; /* Players' created shockwaves */
std::vector<Splotch> splotches; /* Persistent splotches left by shockwaves */
std::vector<std::string> appLobbies; /* Lobby types fetched from AllLobbyTypes global property */
std::map<std::string, int> pingData; /* Our measured region latencies (ms), preserved across sessions */
std::vector<std::string> expectedPingRegions; /* Regions currently being pinged; empty when not in ping phase */
std::vector<std::string> geoTestedRegions; /* EdgeGap regions already launched into during geo test cycling */
std::map<std::string, int> geoTestResults; /* region -> relay RTT (ms) observed during geo test soak; -1 = not captured */
int mouseX = 0;
int mouseY = 0;
long long gameStartTime = 0; /* ms since epoch when current round started (0 = not in game) */
int roundNumber = 0; /* Increments each relay round within the same lobby session */
bool pendingEndMatch = false; /* Deferred END_MATCH disconnect (cannot call disconnect inside relay callback) */
int splotchDurationSec = -1; /* -1 = forever; from SplotchDuration global property */
long long gameStartTime = 0; /* ms since epoch when current round started (0 = not in game) */
int roundNumber = 0; /* Increments each relay round within the same lobby session */
bool pendingEndMatch = false; /* Deferred END_MATCH disconnect (cannot call disconnect inside relay callback) */
bool pendingGeoTestDisconnect = false; /* Deferred auto-geo-test disconnect after relay connect confirmed */
std::chrono::steady_clock::time_point geoTestLobbyArrivalTime; /* When we entered Lobby state during a geo test (for 1.5s auto-start delay) */
std::chrono::steady_clock::time_point geoTestRelayConnectTime; /* When relay connected during a geo test (for 2.5s soak before disconnect) */
int splotchDurationSec = -1; /* -1 = forever; from SplotchDuration global property */
};

// Change this one line to switch the default lobby type everywhere.
static const std::string DEFAULT_LOBBY_TYPE = "CursorPartyGameLift";
static const std::string DEFAULT_LOBBY_TYPE = "CursorPartyCursorPartyV2";

struct Settings
{
Expand All@@ -194,6 +205,8 @@ struct Settings
bool multiInstance = false; /* true when launched with instance/count args */
bool autoJoin = false;
bool autoLogin = true;
bool usePingData = false;
bool autoGeoTest = false; /* Automatically cycle through all EdgeGap regions; disconnects after each relay connect */
BrainCloud::eRelayConnectionType protocol = BrainCloud::eRelayConnectionType::UDP;
std::string lobbyType = DEFAULT_LOBBY_TYPE;
std::string teamCode = "all"; /* "all" for non-team lobbies, "alpha"/"beta" for team lobbies */
Expand All@@ -220,6 +233,117 @@ inline int maxLobbyMembers(const std::string &lobbyType)
return isCursorPartyLobby(lobbyType) ? 40 : 8;
}

// Extracts the region prefix from a brainCloud lobbyId (format: "region:LobbyType:N").
// Returns empty string if the lobbyId doesn't follow that convention.
inline std::string regionFromLobbyId(const std::string &lobbyId)
{
auto pos = lobbyId.find(':');
return (pos != std::string::npos && pos > 0) ? lobbyId.substr(0, pos) : "";
}

// True when the lobby type is the generic EdgeGap umbrella type.
// Selecting this type causes the app to ping EdgeGap beacon regions and then
// route to the best region-specific CP_E_* lobby type automatically.
inline bool isEdgeGapLobby(const std::string &lobbyType)
{
return lobbyType == "CursorPartyEdgeGap";
}

// Maps an EdgeGap beacon region name to its corresponding specific lobby type.
// Returns an empty string if the region is unknown (caller should fall back to
// the original lobby type in that case).
inline std::string edgeGapRegionToLobbyType(const std::string &region)
{
static const std::map<std::string, std::string> kMap = {
{"asia-east", "CursorPartyEdgeGap_AsiaEast"},
{"asia-south", "CursorPartyEdgeGap_AsiaSouth"},
{"europe-central", "CursorPartyEdgeGap_Europe_Central"},
{"na-east", "CursorPartyEdgeGap_NorthAmerica_East"},
{"na-west", "CursorPartyEdgeGap_NorthAmerica_West"},
{"sa-central", "CursorPartyEdgeGap_SouthAmerica_Central"},
{"us-south", "CursorPartyEdgeGap_UnitedStates_South"},
};
auto it = kMap.find(region);
return it != kMap.end() ? it->second : "";
}

// True when the lobby type is the generic GameLift umbrella type.
// Selecting this type causes the app to ping GameLift regions and then
// route to the best region-specific CursorPartyGameLift_* lobby type.
inline bool isGameLiftLobby(const std::string &lobbyType)
{
return lobbyType == "CursorPartyGameLift";
}

// Maps a GameLift ping-region name to its corresponding specific lobby type.
// Returns an empty string if the region is unknown.
// NOTE: verify these region key strings against actual pingRegions() data for GameLift.
inline std::string gameLiftRegionToLobbyType(const std::string &region)
{
static const std::map<std::string, std::string> kMap = {
{"ca-central-1", "CursorPartyGameLift_canada"},
{"eu-central-1", "CursorPartyGameLift_frankfurt"},
{"eu-west-1", "CursorPartyGameLift_ireland"},
{"us-west-2", "CursorPartyGameLift_oregon"},
};
auto it = kMap.find(region);
return it != kMap.end() ? it->second : "";
}

// True when the lobby type is the generic CursorPartyV2 umbrella type.
// Selecting this type causes the app to ping V2 regions and then
// route to the best region-specific CursorPartyV2_* lobby type.
inline bool isV2RegionalLobby(const std::string &lobbyType)
{
return lobbyType == "CursorPartyV2";
}

// Maps a V2 ping-region name to its corresponding specific lobby type.
// Returns an empty string if the region is unknown.
// NOTE: verify these region key strings against actual pingRegions() data for CursorPartyV2.
inline std::string v2RegionToLobbyType(const std::string &region)
{
static const std::map<std::string, std::string> kMap = {
// prod regions
{"us-west-1", "CursorPartyV2_california"},
{"ca-central-1", "CursorPartyV2_canada"},
{"eu-central-1", "CursorPartyV2_frankfurt"},
{"eu-south-1", "CursorPartyV2_milan"},
{"ap-south-1", "CursorPartyV2_mumbai"},
{"us-east-2", "CursorPartyV2_ohio"},
{"eu-west-3", "CursorPartyV2_paris"},
{"sa-east-1", "CursorPartyV2_south_america"},
{"eu-south-2", "CursorPartyV2_spain"},
{"eu-north-1", "CursorPartyV2_stockholm"},
{"ap-southeast-2","CursorPartyV2_sydney"},
{"ap-northeast-1","CursorPartyV2_tokyo"},
// internal regions (absent on prod — skipped automatically when not returned by pingRegions)
{"eu-west-1", "CursorPartyV2_ireland"},
{"us-west-2", "CursorPartyV2_oregon"},
};
auto it = kMap.find(region);
return it != kMap.end() ? it->second : "";
}

// True when the lobby type is a regional-cycling umbrella (EdgeGap, GameLift, or V2).
// These types use ping-based region cycling in the auto geo test.
// All other lobby types use passive recording (server-chosen region).
inline bool isRegionalCyclingLobby(const std::string &lobbyType)
{
return isEdgeGapLobby(lobbyType) || isGameLiftLobby(lobbyType) || isV2RegionalLobby(lobbyType);
}

// Maps a ping region to the specific lobby type for the given umbrella lobby.
// Dispatches to the correct regional map based on the umbrella type.
// Returns empty string if the region has no defined specific lobby.
inline std::string regionToSpecificLobbyType(const std::string &umbrellaLobby, const std::string &region)
{
if (isEdgeGapLobby(umbrellaLobby)) return edgeGapRegionToLobbyType(region);
if (isGameLiftLobby(umbrellaLobby)) return gameLiftRegionToLobbyType(region);
if (isV2RegionalLobby(umbrellaLobby)) return v2RegionToLobbyType(region);
return "";
}

// Main application state instance
extern State state;

Expand Down
Loading