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
18 changes: 11 additions & 7 deletions CS2MultiplayerMod.Steam/SteamRelayConnections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using CS2MultiplayerMod.Core.Diagnostics;
using Steamworks;

namespace CS2MultiplayerMod.Core.Networking.Steam
Expand Down Expand Up @@ -63,8 +64,8 @@ private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t
if (endpoint != null && !endpoint.Announced)
{
endpoint.Announced = true;
_log.Info("Steam relay connection " + endpoint.Id + " established with " +
endpoint.RemoteAddress + ".");
_log.Detail(LogTopic.Transport, "Steam relay connection " + endpoint.Id +
" established with " + endpoint.RemoteAddress + ".");
Enqueue(TransportEvent.Connected(endpoint.Id));
}
break;
Expand All @@ -89,15 +90,17 @@ private void AcceptIncoming(SteamNetConnectionStatusChangedCallback_t evt)
lock (_gate) { open = _byId.Count; }
if (open >= MaxConnections)
{
_log.Warn("Refused Steam relay connection from " + steamId + ": too many open connections.");
_log.Warn(LogTopic.Transport, "Refused Steam relay connection from " + steamId +
": too many open connections.");
SteamNetworkingSockets.CloseConnection(evt.m_hConn, 0, "too many connections", false);
return;
}

EResult accepted = SteamNetworkingSockets.AcceptConnection(evt.m_hConn);
if (accepted != EResult.k_EResultOK)
{
_log.Warn("Could not accept Steam relay connection from " + steamId + ": " + accepted + ".");
_log.Warn(LogTopic.Transport, "Could not accept Steam relay connection from " +
steamId + ": " + accepted + ".");
SteamNetworkingSockets.CloseConnection(evt.m_hConn, 0, "accept failed", false);
return;
}
Expand All @@ -108,13 +111,14 @@ private void AcceptIncoming(SteamNetConnectionStatusChangedCallback_t evt)
{
// Outside the poll group this connection is deaf; better to refuse it than
// to leave a peer that handshakes and then goes quiet forever.
_log.Warn("Could not add Steam relay connection " + id + " from " + steamId +
" to the poll group; refusing it.");
_log.Warn(LogTopic.Transport, "Could not add Steam relay connection " + id +
" from " + steamId + " to the poll group; refusing it.");
Close(endpoint, "poll group rejected the connection", linger: false);
return;
}

_log.Info("Accepted Steam relay connection " + id + " from " + steamId + ".");
_log.Detail(LogTopic.Transport, "Accepted Steam relay connection " + id + " from " +
steamId + ".");
// Connected is announced on the Connected state, so the session never talks to
// a connection that is still negotiating.
}
Expand Down
26 changes: 14 additions & 12 deletions CS2MultiplayerMod.Steam/SteamRelayGovernor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using CS2MultiplayerMod.Core.Diagnostics;
using Steamworks;

namespace CS2MultiplayerMod.Core.Networking.Steam
Expand Down Expand Up @@ -48,13 +49,15 @@ private bool SetInt32(ESteamNetworkingConfigValue setting, ESteamNetworkingConfi
ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32,
pin.AddrOfPinnedObject());
if (!ok)
_log.Warn("Steam refused the relay " + description + " setting; transfers may be slow.");
_log.Warn(LogTopic.Transport, "Steam refused the relay " + description +
" setting; transfers may be slow.");
return ok;
}
catch (Exception ex)
{
// Non-fatal: the transfer still completes, just slower.
_log.Warn("Could not set the relay " + description + " (" + ex.Message + ").");
_log.Warn(LogTopic.Transport, "Could not set the relay " + description + " (" +
ex.Message + ").");
return false;
}
finally
Expand Down Expand Up @@ -157,8 +160,8 @@ private void Govern()
{
string finished = endpoint.FinishBulk();
if (finished != null)
_log.Info("[relay] " + endpoint.Id + " " + finished + " over a " +
RouteOf(endpoint) + " route.");
_log.Detail(LogTopic.Transport, "Relay " + endpoint.Id + " " + finished +
" over a " + RouteOf(endpoint) + " route.");

int idle = Math.Min(SendRateStartBytesPerSecond, endpoint.SafeRate);
if (endpoint.SendRate != idle) ApplySendRate(endpoint, idle);
Expand Down Expand Up @@ -219,14 +222,13 @@ private void Govern()
}

if (!report) continue;
_log.Info("[relay] " + endpoint.Id + " sending: " + (outstanding / 1024) +
" KB left at " + (goodput / 1024) + " KB/s (paced " +
(endpoint.SendRate / 1024) + " KB/s, held " +
(endpoint.SafeRate / 1024) + " KB/s, wire " +
((int)status.m_flOutBytesPerSec / 1024) + " KB/s), ping " +
status.m_nPing + " of " + pingBudget + " ms, peer received " +
(quality < 0f ? "?" : ((int)(quality * 100)).ToString()) + "%, " +
RouteOf(endpoint) + " route.");
_log.Detail(LogTopic.Transport, "Relay " + endpoint.Id + " sending: " +
(outstanding / 1024) + " KB left at " + (goodput / 1024) + " KB/s (paced " +
(endpoint.SendRate / 1024) + " KB/s, held " + (endpoint.SafeRate / 1024) +
" KB/s, wire " + ((int)status.m_flOutBytesPerSec / 1024) + " KB/s), ping " +
status.m_nPing + " of " + pingBudget + " ms, peer received " +
(quality < 0f ? "?" : ((int)(quality * 100)).ToString()) + "%, " +
RouteOf(endpoint) + " route.");
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions CS2MultiplayerMod.Steam/SteamRelayIo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using CS2MultiplayerMod.Core.Diagnostics;
using CS2MultiplayerMod.Core.Protocol;
using Steamworks;

Expand Down Expand Up @@ -111,14 +112,15 @@ private SendOutcome SendFrame(Endpoint endpoint, byte[] frame)
// rejected, and a half-delivered payload can never be completed.
if (result == EResult.k_EResultLimitExceeded) return SendOutcome.Backpressure;

_log.Warn("Steam relay send to " + endpoint.Id + " failed (" + result + "); dropping the connection. " +
DescribeConnection(endpoint));
_log.Warn(LogTopic.Transport, "Steam relay send to " + endpoint.Id + " failed (" +
result + "); dropping the connection. " + DescribeConnection(endpoint));
Close(endpoint, "relay send failed: " + result, linger: false);
return SendOutcome.Failed;
}
catch (Exception ex)
{
_log.Warn("Steam relay send to " + endpoint.Id + " threw (" + ex.Message + "); dropping the connection.");
_log.Warn(LogTopic.Transport, "Steam relay send to " + endpoint.Id + " threw (" +
ex.Message + "); dropping the connection.");
Close(endpoint, "relay send error", linger: false);
return SendOutcome.Failed;
}
Expand Down Expand Up @@ -164,7 +166,7 @@ private void Receive()
}
catch (Exception ex)
{
_log.Warn("Steam relay receive failed: " + ex.Message);
_log.Warn(LogTopic.Transport, "Steam relay receive failed: " + ex.Message);
return;
}

Expand Down
7 changes: 4 additions & 3 deletions CS2MultiplayerMod.Steam/SteamRelayLifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using CS2MultiplayerMod.Core.Diagnostics;
using Steamworks;

namespace CS2MultiplayerMod.Core.Networking.Steam
Expand Down Expand Up @@ -92,7 +93,7 @@ public void Shutdown()
_statusCallback = null;
}

_log.Info("Steam relay transport stopped.");
_log.Detail(LogTopic.Transport, "Steam relay transport stopped.");
}

public void ShutdownAfterFlush(int timeoutMs)
Expand Down Expand Up @@ -124,8 +125,8 @@ public void ShutdownAfterFlush(int timeoutMs)

long left = PendingSendBytes;
if (left > 0)
_log.Warn("Steam relay stopping with " + left + " byte(s) still queued after " +
timeoutMs + " ms; closing anyway.");
_log.Warn(LogTopic.Transport, "Steam relay stopping with " + left +
" byte(s) still queued after " + timeoutMs + " ms; closing anyway.");

// Linger lets Steam make a final attempt after the handles leave our maps.
lock (_gate)
Expand Down
7 changes: 4 additions & 3 deletions CS2MultiplayerMod.Steam/SteamRelayTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ public static SteamRelayTransport StartHost(IModLogger log, int virtualPort)
"Steam refused to create a relay poll group. Restart Steam and try again.");
}

transport._log.Info("Hosting over the Steam relay on virtual port " + virtualPort +
transport._log.Detail(LogTopic.Transport,
"Hosting over the Steam relay on virtual port " + virtualPort +
"; join code " + SteamRelayProvider.LocalSteamId() + ".");
return transport;
}
Expand Down Expand Up @@ -238,7 +239,7 @@ public static SteamRelayTransport Connect(IModLogger log, string joinCode, int v
// The client's single connection is the session's well-known Server id, bound
// before any callback can fire so the first status change already resolves.
transport.Bind(ConnectionId.Server, connection, steamId);
transport._log.Info("Connecting to " + steamId + " over the Steam relay.");
transport._log.Detail(LogTopic.Transport, "Connecting to " + steamId + " over the Steam relay.");
return transport;
}

Expand All @@ -247,7 +248,7 @@ private void Begin()
// Warming the relay network here means the first connection does not also pay
// for fetching the relay topology.
try { SteamNetworkingUtils.InitRelayNetworkAccess(); }
catch (Exception ex) { _log.Warn("Could not pre-warm the Steam relay network: " + ex.Message); }
catch (Exception ex) { _log.Warn(LogTopic.Transport, "Could not pre-warm the Steam relay network: " + ex.Message); }

ConfigureForBulkTransfer();

Expand Down
46 changes: 38 additions & 8 deletions CS2MultiplayerMod/Core/Diagnostics/IModLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,46 @@ namespace CS2MultiplayerMod.Core.Diagnostics
/// <summary>
/// Logging abstraction for the multiplayer core.
///
/// The core deliberately does not reference Colossal.Logging (or any game
/// assembly) so it stays portable and unit-testable. The game layer supplies
/// a concrete adapter; tests can pass <see cref="NullModLogger"/>.
/// The core deliberately does not reference Colossal.Logging (or any game assembly) so it
/// stays portable and unit-testable. The game layer supplies a concrete adapter; tests can
/// pass <see cref="NullModLogger"/>.
///
/// The shape mirrors the game layer's logger exactly, so there is one vocabulary across the
/// whole mod: every line names a <see cref="LogTopic"/>, and the severity decides whether the
/// topic's switch is consulted at all. <see cref="Detail"/> is troubleshooting chatter and is
/// gated; <see cref="Trace"/> is kept in the crash log either way; and everything from
/// <see cref="Event"/> upwards is written to both logs whatever the switches say, because a
/// player cannot be expected to have turned on the right switch before the thing they are
/// reporting happened.
/// </summary>
public interface IModLogger
{
void Debug(string message);
void Info(string message);
void Warn(string message);
void Error(string message);
void Error(string message, System.Exception exception);
/// <summary>
/// Whether a <see cref="Detail"/> line on this topic would be written. Ask before
/// <i>computing</i> a diagnostic, not only before logging one: a counter nobody reads
/// must not cost a frame.
/// </summary>
bool IsEnabled(LogTopic topic);

/// <summary>Troubleshooting detail. Written only while the topic is switched on.</summary>
void Detail(LogTopic topic, string message);

/// <summary>
/// A short breadcrumb: always kept in the crash log, shown in the readable log only while
/// the topic is switched on.
/// </summary>
void Trace(LogTopic topic, string message);

/// <summary>A milestone worth having in every player's log. Never gated.</summary>
void Event(LogTopic topic, string message);

/// <summary>Something went wrong but the mod worked around it. Never gated.</summary>
void Warn(LogTopic topic, string message);

/// <summary>Something went wrong that the mod could not work around. Never gated.</summary>
void Error(LogTopic topic, string message);

/// <summary>As <see cref="Error(LogTopic,string)"/>, with the exception that caused it.</summary>
void Error(LogTopic topic, string message, System.Exception exception);
}
}
78 changes: 78 additions & 0 deletions CS2MultiplayerMod/Core/Diagnostics/LogTopic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace CS2MultiplayerMod.Core.Diagnostics
{
/// <summary>
/// What a log line is about.
///
/// Every line the mod writes names one of these, and each one can be switched on by itself.
/// That is the whole point: a player chasing "roads do not appear on my partner's screen"
/// turns on <see cref="Nets"/> and gets a log about roads, instead of a general "debug"
/// switch that buries the one interesting line under twenty thousand others. A log nobody
/// can read is a log nobody reads.
///
/// The topics are named after the thing that went wrong from the player's side, not after
/// the class that noticed it - "my transit lines are missing" is <see cref="Routes"/>, and
/// the reporter may be a channel, a system or the pipeline.
///
/// This lives in the portable core so the networking and session code can name the same
/// topics as the game layer without referencing a game assembly.
/// </summary>
public enum LogTopic
{
/// <summary>
/// The mod itself: load, settings, system registration, compatibility and DLC checks.
/// Deliberately first, so an unattributed line lands somewhere honest.
/// </summary>
Startup = 0,

/// <summary>Connecting, disconnecting, the handshake, peers joining and leaving, kicks and bans.</summary>
Session,

/// <summary>The wire underneath a session: sockets, the Steam relay, port forwarding, framing, rates.</summary>
Transport,

/// <summary>Sending, receiving, staging and loading the world a joining player downloads.</summary>
WorldTransfer,

/// <summary>Divergence: what was detected, what the arbiter decided, and what the repair did.</summary>
Resync,

/// <summary>The command pipeline: inbox, observers, authority holds, definition gates, realization.</summary>
Pipeline,

/// <summary>Roads, tracks, pipes and wires - placement, upgrades, replacement, topology.</summary>
Nets,

/// <summary>Placed objects: buildings, props and trees - placement, move, upgrade, delete.</summary>
Buildings,

/// <summary>The map itself: zoning, areas and districts, terrain, tile purchases.</summary>
Land,

/// <summary>City-wide state: names, policies, money, milestones, the development tree, statistics.</summary>
City,

/// <summary>Transit: lines, stops, vehicles and fares.</summary>
Routes,

/// <summary>Households, residents and their homes.</summary>
Residential,

/// <summary>Shops: tenancy, figures and stock.</summary>
Commercial,

/// <summary>Factories and extractors: tenancy, figures and stock.</summary>
Industrial,

/// <summary>Offices: tenancy, figures and stock.</summary>
Office,

/// <summary>The other players: their cursors, markers, map pings and chat.</summary>
Players,

/// <summary>The mod's own screens: the main-menu button, the join dialog, the options page.</summary>
Ui,

/// <summary>Frame times and the mod's own main-thread cost, including the per-zone split.</summary>
Performance,
}
}
12 changes: 7 additions & 5 deletions CS2MultiplayerMod/Core/Diagnostics/NullModLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ public sealed class NullModLogger : IModLogger

private NullModLogger() { }

public void Debug(string message) { }
public void Info(string message) { }
public void Warn(string message) { }
public void Error(string message) { }
public void Error(string message, Exception exception) { }
public bool IsEnabled(LogTopic topic) { return false; }
public void Detail(LogTopic topic, string message) { }
public void Trace(LogTopic topic, string message) { }
public void Event(LogTopic topic, string message) { }
public void Warn(LogTopic topic, string message) { }
public void Error(LogTopic topic, string message) { }
public void Error(LogTopic topic, string message, Exception exception) { }
}
}
Loading