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
6 changes: 3 additions & 3 deletions samples/ConsoleGame/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ public void TimeSync(FrameSpan framesAhead)
Thread.Sleep(framesAhead.Duration());
}

public void OnPeerEvent(NetcodePlayer player, PeerEventInfo evt)
public void OnPeerEvent(NetcodePlayer player, in PeerEventInfo evt)
{
Log($"PEER EVENT: {evt} from {player}");
if (player.IsSpectator())
return;
if (player.IsSpectator()) return;

switch (evt.Type)
{
case PeerEvent.Connected:
Expand Down
5 changes: 3 additions & 2 deletions samples/SpaceWar.Lobby/Game1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ protected override void Initialize()
protected override void LoadContent()
{
SpriteBatch = new(GraphicsDevice);
SceneManager = new(this, startScene: new ChooseLobbyScene());

Services.AddService(SpriteBatch);
Services.AddService(SceneManager);
Services.AddService(settings);
Services.AddService(new GameAssets(Content, GraphicsDevice));
Services.AddService(new LobbyHttpClient(settings));

SceneManager = new(this, startScene: new ChooseLobbyScene());
Services.AddService(SceneManager);
}

protected override void Update(GameTime gameTime)
Expand Down
3 changes: 1 addition & 2 deletions samples/SpaceWar.Lobby/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@

var settings = AppSettings.LoadFromJson("appsettings.json");
settings.ParseArgs(args);

using var game = new Game1(settings);
using Game1 game = new(settings);
game.Run();
5 changes: 4 additions & 1 deletion samples/SpaceWar.Shared/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ void UpdateStats()
nonGameState.StateSize = saved.Size;
}

public void OnPeerEvent(NetcodePlayer player, PeerEventInfo evt)
public void OnPeerEvent(NetcodePlayer player, in PeerEventInfo evt)
{
Log($"=> PEER EVENT: {evt} from {player}");
if (player.IsSpectator()) return;
Expand Down Expand Up @@ -155,6 +155,9 @@ public void OnPeerEvent(NetcodePlayer player, PeerEventInfo evt)
case PeerEvent.Disconnected:
nonGameState.SetConnectState(player, PlayerConnectState.Disconnected);
break;
case PeerEvent.ChecksumMismatch:
Log($"=> CHECKSUM MISMATCH: {evt.ChecksumMismatch}");
break;
}
}

Expand Down
20 changes: 8 additions & 12 deletions src/Backdash/Network/PeerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ sealed class PeerConnection<TInput> : IDisposable where TInput : unmanaged
readonly ProtocolInbox<TInput> inbox;
readonly ProtocolOutbox outbox;
readonly ProtocolInputBuffer<TInput> inputBuffer;
readonly IStateStore stateStore;
readonly ChecksumStore checksumStore;

readonly Timer qualityReportTimer;
readonly Timer networkStatsTimer;
Expand All @@ -45,7 +45,7 @@ public PeerConnection(
ProtocolInbox<TInput> inbox,
ProtocolOutbox outbox,
ProtocolInputBuffer<TInput> inputBuffer,
IStateStore stateStore
ChecksumStore checksumStore
)
{
this.options = options;
Expand All @@ -57,7 +57,7 @@ IStateStore stateStore
this.inbox = inbox;
this.outbox = outbox;
this.inputBuffer = inputBuffer;
this.stateStore = stateStore;
this.checksumStore = checksumStore;
disconnectCheckEnabled = options.IsDisconnectTimeoutEnabled();

keepAliveTimer = new(options.KeepAliveInterval);
Expand Down Expand Up @@ -269,9 +269,9 @@ bool DispatchInterruptedEvent(TimeSpan timeout)
if (state.Connection is not { DisconnectNotifySent: false, DisconnectEventSent: false })
return false;

networkEventHandler.OnNetworkEvent(new(ProtocolEvent.NetworkInterrupted, state.Player)
networkEventHandler.OnNetworkEvent(state.Player, new(PeerEvent.ConnectionInterrupted)
{
NetworkInterrupted = new()
ConnectionInterrupted = new()
{
DisconnectTimeout = timeout,
},
Expand All @@ -291,7 +291,7 @@ bool DispatchDisconnectEvent()
return false;

state.Connection.DisconnectEventSent = true;
networkEventHandler.OnNetworkEvent(ProtocolEvent.Disconnected, state.Player);
networkEventHandler.OnNetworkEvent(PeerEvent.Disconnected, state.Player);

return true;
}
Expand Down Expand Up @@ -371,23 +371,19 @@ void UpdateStats(ref ProtocolState.PackagesStats stats)
void OnConsistencyCheck(object? sender, ElapsedEventArgs e)
{
if (state.CurrentStatus is not ProtocolStatus.Running) return;

var lastReceivedFrame = inbox.LastReceivedInput.Frame;
var checkFrame = lastReceivedFrame.Number - options.ConsistencyCheckDistance;
if (checkFrame <= 1) return;

state.Consistency.AskedFrame = new(checkFrame);
state.Consistency.AskedChecksum = stateStore.GetChecksum(state.Consistency.AskedFrame);
state.Consistency.AskedChecksum = checksumStore.Get(state.Consistency.AskedFrame);

if (state.Consistency.AskedFrame.IsNull || state.Consistency.AskedChecksum is 0)
return;

if (state.Consistency.LastCheck is 0)
state.Consistency.LastCheck = Stopwatch.GetTimestamp();

logger.Write(LogLevel.Trace,
$"Start consistency check for frame {state.Consistency.AskedFrame} #{state.Consistency.AskedChecksum:x8}");

var elapsed = Stopwatch.GetElapsedTime(state.Consistency.LastCheck);
if (options.ConsistencyCheckTimeout > TimeSpan.Zero && elapsed > options.ConsistencyCheckTimeout)
{
Expand All @@ -398,7 +394,7 @@ void OnConsistencyCheck(object? sender, ElapsedEventArgs e)
}

logger.Write(LogLevel.Debug,
$"Send consistency request for frame {state.Consistency.AskedFrame.Number} #{state.Consistency.AskedChecksum:x8}");
$"Begin consistency-check request for frame {state.Consistency.AskedFrame.Number} #{state.Consistency.AskedChecksum:x8}");

outbox
.SendMessage(new(MessageType.ConsistencyCheckRequest)
Expand Down
6 changes: 3 additions & 3 deletions src/Backdash/Network/PeerConnectionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ sealed class PeerConnectionFactory(
PeerClient<ProtocolMessage> peer,
ProtocolOptions options,
TimeSyncOptions timeSyncOptions,
IStateStore stateStore
ChecksumStore checksumStore
)
{
public PeerConnection<TInput> Create<TInput>(
Expand All @@ -31,13 +31,13 @@ public PeerConnection<TInput> Create<TInput>(
var outbox = new ProtocolOutbox(state, peer, logger);
var syncManager = new ProtocolSynchronizer(logger, random, state, options, outbox, networkEventHandler);
var inbox = new ProtocolInbox<TInput>(options, inputSerializer, state, syncManager, outbox,
networkEventHandler, inputEventQueue, stateStore, logger);
networkEventHandler, inputEventQueue, checksumStore, logger);
var inputBuffer =
new ProtocolInputBuffer<TInput>(options, inputSerializer, state, logger, timeSync, outbox, inbox);

PeerConnection<TInput> connection = new(
options, state, logger, timeSync, networkEventHandler,
syncManager, inbox, outbox, inputBuffer, stateStore
syncManager, inbox, outbox, inputBuffer, checksumStore
);

state.StoppingToken.Register(() => connection.Disconnect());
Expand Down
41 changes: 25 additions & 16 deletions src/Backdash/Network/Protocol/Comm/ProtocolInbox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ sealed class ProtocolInbox<TInput>(
IMessageSender messageSender,
IProtocolNetworkEventHandler networkEvents,
IProtocolInputEventPublisher<TInput> inputEvents,
IStateStore stateStore,
ChecksumStore checksumStore,
Logger logger
) : IProtocolInbox<TInput> where TInput : unmanaged
{
Expand All @@ -54,7 +54,7 @@ public void OnPeerMessage(ref readonly ProtocolMessage message, in SocketAddress
{
if (state.CurrentStatus is not ProtocolStatus.Running)
{
logger.Write(LogLevel.Debug, $"recv skip (not ready): {message} on {state.Player}");
logger.Write(LogLevel.Trace, $"recv skip (not ready): {message} on {state.Player}");
return;
}

Expand Down Expand Up @@ -85,7 +85,7 @@ public void OnPeerMessage(ref readonly ProtocolMessage message, in SocketAddress
state.Stats.Received.TotalBytes += (ByteSize)bytesReceived;
if (state.Connection.DisconnectNotifySent && state.CurrentStatus is ProtocolStatus.Running)
{
networkEvents.OnNetworkEvent(ProtocolEvent.NetworkResumed, state.Player);
networkEvents.OnNetworkEvent(PeerEvent.ConnectionResumed, state.Player);
state.Connection.DisconnectNotifySent = false;
}
}
Expand Down Expand Up @@ -124,7 +124,7 @@ bool OnInput(in InputMessage msg)
if (state.CurrentStatus is not ProtocolStatus.Disconnected && !state.Connection.DisconnectEventSent)
{
logger.Write(LogLevel.Information, "Disconnecting endpoint on remote request");
networkEvents.OnNetworkEvent(ProtocolEvent.Disconnected, state.Player);
networkEvents.OnNetworkEvent(PeerEvent.Disconnected, state.Player);
state.Connection.DisconnectEventSent = true;
}
}
Expand Down Expand Up @@ -186,7 +186,7 @@ bool OnInput(in InputMessage msg)
lastReceivedInput.Frame = currentFrame;
state.Stats.LastReceivedInputTime = Stopwatch.GetTimestamp();
currentFrame++;
logger.Write(LogLevel.Debug,
logger.Write(LogLevel.Trace,
$"Received input: frame {lastReceivedInput.Frame}, sending to emulator queue {state.Player} (ack: {LastAckedFrame})");
inputEvents.Publish(new(state.Player, lastReceivedInput));
}
Expand Down Expand Up @@ -236,7 +236,7 @@ bool OnSyncReply(ref readonly ProtocolMessage msg, ref ProtocolMessage replyMsg)

if (!state.Connection.IsConnected)
{
networkEvents.OnNetworkEvent(ProtocolEvent.Connected, state.Player);
networkEvents.OnNetworkEvent(PeerEvent.Connected, state.Player);
state.Connection.IsConnected = true;
}

Expand All @@ -254,21 +254,20 @@ bool OnSyncReply(ref readonly ProtocolMessage msg, ref ProtocolMessage replyMsg)
state.Stats.RoundTripTime = ping;
lastReceivedInput.ResetFrame();
state.RemoteSyncNumber = msg.Header.SyncNumber;
networkEvents.OnNetworkEvent(new(ProtocolEvent.Synchronized, state.Player)
networkEvents.OnNetworkEvent(state.Player, new(PeerEvent.Synchronized)
{
Synchronized = new(ping),
});
}
else
{
networkEvents.OnNetworkEvent(
new(ProtocolEvent.Synchronizing, state.Player)
{
Synchronizing = new(
networkEvents.OnNetworkEvent(state.Player, new(PeerEvent.Synchronizing)
{
Synchronizing = new(
TotalSteps: options.NumberOfSyncRoundTrips,
CurrentStep: options.NumberOfSyncRoundTrips - state.Sync.RemainingRoundTrips
),
}
}
);
sync.CreateRequestMessage(ref replyMsg);
}
Expand Down Expand Up @@ -296,7 +295,7 @@ bool OnConsistencyCheckReply(ref readonly ProtocolMessage message)
var checksum = message.ConsistencyCheckReply.Checksum;
var localChecksum = state.Consistency.AskedChecksum;

logger.Write(LogLevel.Debug, $"Received consistency request reply for: {checkFrame} #{checksum:x8}");
logger.Write(LogLevel.Debug, $"Reply consistency-check for {checkFrame} #{checksum:x8}");

if (state.Consistency.AskedFrame != checkFrame || localChecksum is 0 || checksum is 0)
{
Expand All @@ -308,11 +307,21 @@ bool OnConsistencyCheckReply(ref readonly ProtocolMessage message)
{
logger.Write(LogLevel.Error,
$"Invalid remote checksum on frame {checkFrame}, {localChecksum:x8} != {checksum:x8}");
state.StoppingTokenSource.Cancel();

networkEvents.OnNetworkEvent(state.Player, new(PeerEvent.ChecksumMismatch)
{
ChecksumMismatch = new(
MismatchFrame: checkFrame,
LocalChecksum: localChecksum,
RemoteChecksum: checksum
),
}
);

return false;
}

logger.Write(LogLevel.Debug, $"Consistency request check for: {checkFrame} OK({checksum:x8})");
logger.Write(LogLevel.Debug, $"Finish consistency-check request check for {checkFrame} #{checksum:x8}");
state.Consistency.LastCheck = Stopwatch.GetTimestamp();
state.Consistency.AskedFrame = Frame.Null;
state.Consistency.AskedChecksum = 0;
Expand All @@ -323,7 +332,7 @@ bool OnConsistencyCheckReply(ref readonly ProtocolMessage message)
bool OnConsistencyCheckRequest(ref readonly ProtocolMessage message, ref ProtocolMessage replyMsg)
{
var checkFrame = message.ConsistencyCheckRequest.Frame;
var checksum = stateStore.GetChecksum(checkFrame);
var checksum = checksumStore.Get(checkFrame);

logger.Write(LogLevel.Debug, $"Received consistency request check for: {checkFrame} (reply {checksum:x8})");

Expand Down
2 changes: 1 addition & 1 deletion src/Backdash/Network/Protocol/Comm/ProtocolSynchronizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void Update()
active = false;
logger.Write(LogLevel.Warning,
$"Fail to sync {state.Player} after {retryCounter} retries");
eventHandler.OnNetworkEvent(ProtocolEvent.SyncFailure, state.Player);
eventHandler.OnNetworkEvent(PeerEvent.SynchronizationFailure, state.Player);
return;
}

Expand Down
42 changes: 7 additions & 35 deletions src/Backdash/Network/Protocol/ProtocolEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,11 @@

namespace Backdash.Network.Protocol;

enum ProtocolEvent : byte
struct ProtocolEventInfo(NetcodePlayer player, PeerEventInfo eventInfo) : IUtf8SpanFormattable
{
Connected,
Synchronizing,
Synchronized,
SyncFailure,
Disconnected,
NetworkInterrupted,
NetworkResumed,
}

struct ProtocolEventInfo(ProtocolEvent type, NetcodePlayer player) : IUtf8SpanFormattable
{
public readonly ProtocolEvent Type = type;
public NetcodePlayer Player = player;
public SynchronizingEventInfo Synchronizing = default;
public SynchronizedEventInfo Synchronized = default;
public ConnectionInterruptedEventInfo NetworkInterrupted = default;
public readonly NetcodePlayer Player = player;
public PeerEventInfo EventInfo = eventInfo;
public readonly PeerEvent Type => EventInfo.Type;

public readonly bool TryFormat(
Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format,
Expand All @@ -29,23 +16,8 @@ public readonly bool TryFormat(
Utf8StringBuilder writer = new(in utf8Destination, ref bytesWritten);
if (!writer.Write("P"u8)) return false;
if (!writer.Write(Player.Index)) return false;
if (!writer.Write(" ProtoEvt "u8)) return false;
if (!writer.WriteEnum(Type)) return false;
if (!writer.Write(":"u8)) return false;
switch (Type)
{
case ProtocolEvent.NetworkInterrupted:
return writer.Write("Timeout: "u8)
&& writer.Write(NetworkInterrupted.DisconnectTimeout);
case ProtocolEvent.Synchronizing when !writer.Write(' '):
return false;
case ProtocolEvent.Synchronizing:
return writer.Write(Synchronizing.CurrentStep)
&& writer.Write('/')
&&
writer.Write(Synchronizing.TotalSteps);
default:
return writer.Write("{}"u8);
}
if (!writer.Write(" Info: "u8)) return false;
if (!writer.Write(EventInfo)) return false;
return true;
}
}
3 changes: 2 additions & 1 deletion src/Backdash/Network/ProtocolNetworkEventQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace Backdash.Network;
interface IProtocolNetworkEventHandler : IDisposable
{
void OnNetworkEvent(in ProtocolEventInfo evt);
void OnNetworkEvent(in ProtocolEvent evt, NetcodePlayer player) => OnNetworkEvent(new(evt, player));
void OnNetworkEvent(NetcodePlayer player, PeerEventInfo evt) => OnNetworkEvent(new(player, evt));
void OnNetworkEvent(PeerEvent evt, NetcodePlayer player) => OnNetworkEvent(player, new(evt));
}

sealed class ProtocolNetworkEventQueue : IProtocolNetworkEventHandler
Expand Down
15 changes: 11 additions & 4 deletions src/Backdash/Options/ProtocolOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ internal bool IsDisconnectTimeoutEnabled() =>
/// <seealso cref="PeerNetworkStats.Send"/>
/// <seealso cref="PeerNetworkStats.Received"/>
/// <value>Defaults to <see lanword="true" /></value>
public bool NetworkPackageStatsEnabled { get; set; } = false;
public bool NetworkPackageStatsEnabled { get; set; }

internal bool IsNetworkPackageStatsEnabled() =>
NetworkPackageStatsEnabled && NetworkPackageStatsInterval > TimeSpan.Zero;
Expand All @@ -165,7 +165,7 @@ internal bool IsNetworkPackageStatsEnabled() =>

/// <summary>
/// Offset to be applied to frame on checksum consistency check.
/// The frame sent is (<c>LastReceivedFrame - ConsistencyCheckOffset</c>).
/// The frame sent is (<c>LastAckedFrame - ConsistencyCheckOffset</c>).
/// </summary>
/// <value>Defaults to <c>8</c></value>
/// <seealso cref="ConsistencyCheckTimeout" />
Expand All @@ -180,8 +180,15 @@ internal bool IsNetworkPackageStatsEnabled() =>
/// <value>Defaults to <c>3_000</c> milliseconds</value>
/// <seealso cref="ConsistencyCheckDistance" />
/// <seealso cref="ConsistencyCheckTimeout" />
public TimeSpan ConsistencyCheckInterval { get; set; } =
TimeSpan.FromMilliseconds(3_000);
public TimeSpan ConsistencyCheckInterval { get; set; } = TimeSpan.FromSeconds(3);

/// <summary>
/// The number of checksum frames that will be keep for consistency checks.
/// </summary>
/// <value>Defaults to <c>180</c></value>
/// <seealso cref="ConsistencyCheckTimeout" />
/// <seealso cref="ConsistencyCheckInterval" />
public int ConsistencyCheckStoreSize { get; set; } = 250;

/// <summary>
/// Enable/Disable consistency check.
Expand Down
Loading
Loading