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
2 changes: 1 addition & 1 deletion src/input/GameInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Linq;
using System.Diagnostics;

namespace PleaseResync.input
namespace PleaseResync.Input
{
internal class GameInput
{
Expand Down
7 changes: 3 additions & 4 deletions src/input/InputQueue.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics;

namespace PleaseResync.input
namespace PleaseResync.Input
{
internal class InputQueue
{
Expand Down Expand Up @@ -73,4 +72,4 @@ public void ResetPrediction(int frame)

private int PreviousFrame(int offset) => offset == 0 ? QueueSize - 1 : offset - 1;
}
}
}
2 changes: 1 addition & 1 deletion src/session/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Generic;
using System;

namespace PleaseResync.session
namespace PleaseResync.Session
{
public class Device
{
Expand Down
2 changes: 1 addition & 1 deletion src/session/DeviceMessage.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using MessagePack;

namespace PleaseResync.session
namespace PleaseResync.Session
{
[Union(0, typeof(DeviceSyncMessage))]
[Union(1, typeof(DeviceSyncConfirmMessage))]
Expand Down
13 changes: 8 additions & 5 deletions src/session/Session.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System.Diagnostics;
using System.Collections.Generic;
using System.Net;
using System;

namespace PleaseResync.session
namespace PleaseResync.Session
{
/// <summary>
/// Session is responsible for managing a pool of devices wanting to play your game together.
Expand Down Expand Up @@ -59,8 +57,11 @@ public abstract class Session
/// <param name="inputSize">The size in bits of the input for one player.</param>
/// <param name="deviceCount">The number of devices taking part in this session.</param>
/// <param name="totalPlayerCount">The total number of players accross all devices taking part in this session.</param>
public Session(uint inputSize, uint deviceCount, uint totalPlayerCount, bool offline)
public Session(uint inputSize, uint deviceCount, uint totalPlayerCount, bool offline, bool replay = false)
{
// we dont care about any of this when we are replaying
if (replay) return;

Debug.Assert(inputSize > 0);
Debug.Assert(inputSize <= LIMIT_INPUT_SIZE);
Debug.Assert(deviceCount >= 1);
Expand Down Expand Up @@ -118,5 +119,7 @@ public Session(uint inputSize, uint deviceCount, uint totalPlayerCount, bool off
public abstract uint RollbackFrames();
public abstract uint AverageRollbackFrames();
public abstract int State();

public abstract void SaveToReplayFile();
}
}
}
4 changes: 2 additions & 2 deletions src/session/SessionAction.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Diagnostics;
using PleaseResync.synchronization;
using PleaseResync.Synchronization;

namespace PleaseResync.session
namespace PleaseResync.Session
{
/// <summary>
/// SessionAction is an action you must fulfill to give a chance to the Session to synchronize with other sessions.
Expand Down
2 changes: 1 addition & 1 deletion src/session/SessionAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections.Generic;

namespace PleaseResync.session
namespace PleaseResync.Session
{
/// <summary>
/// SessionAdapter is the interface used to implement a way for the Session to communicate with remote devices.
Expand Down
2 changes: 1 addition & 1 deletion src/session/SessionEvent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace PleaseResync.session
namespace PleaseResync.Session
{
public abstract class SessionEvent
{
Expand Down
2 changes: 1 addition & 1 deletion src/session/adapters/LiteNetLibSessionAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Linq;
using MessagePack;

namespace PleaseResync.session.adapters
namespace PleaseResync.Session.Adapters
{
public class LiteNetLibSessionAdapter : SessionAdapter, INetEventListener
{
Expand Down
7 changes: 6 additions & 1 deletion src/session/backends/Peer2PeerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
using System.Diagnostics;
using System.Collections.Generic;
using PleaseResync.synchronization;
using PleaseResync.Session.Backends.Utility;

namespace PleaseResync.session.backends
namespace PleaseResync.Session.Backends
{
/// <summary>
/// Peer2PeerSession implements a session for devices wanting to play your game together via network.
Expand Down Expand Up @@ -126,6 +127,8 @@ public override List<SessionAction> AdvanceFrame(byte[] localInput)
Debug.Assert(IsRunning(), "Session must be running before calling AdvanceFrame");
Debug.Assert(localInput != null);

if (Frame() == 1000) SaveToReplayFile();

Poll();
return _sync.AdvanceSync(_localDevice.Id, localInput);
}
Expand Down Expand Up @@ -168,5 +171,7 @@ internal protected override void AddRemoteInput(uint deviceId, DeviceInputMessag
public override uint RollbackFrames() => _sync.RollbackFrames();
public override uint AverageRollbackFrames() => _sync.AverageRollbackFrames();
public override int State() => (int)_sync.State();

public override void SaveToReplayFile() => _sync.SaveToReplayFile();
}
}
208 changes: 208 additions & 0 deletions src/session/backends/ReplaySession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using PleaseResync.Session.Backends.Utility;
using PleaseResync.Synchronization;

namespace PleaseResync.Session.Backends
{
public sealed class ReplaySession : Session
{
private enum PlaybackState
{
NoFile,
FileLoaded,
Running,
Paused
}

private readonly Queue<Action<List<SessionAction>>> _commandQueue = new();
private readonly BroadcastStream _broadcastStream;
private readonly StateStorage _stateStorage;

private PlaybackState _currentState = PlaybackState.NoFile;
private int _currentFrame;
private int _targetFrame;

public ReplaySession(): base(0, 0, 0, false, true)
{
_broadcastStream = new BroadcastStream();
_stateStorage = new StateStorage(0);
_currentFrame = 0;
_targetFrame = -1;
}

public void LoadFile(string filePath)
{
Enqueue(actions =>
{
_broadcastStream.LoadReplayFile(filePath);
var initialState = _broadcastStream.GetInitialState();
_stateStorage.SaveFrame(0, initialState);

_currentFrame = 0;
_targetFrame = -1;
_currentState = PlaybackState.FileLoaded;

// Immediately emit a load-game action so the engine knows to load state 0
actions.Add(new SessionLoadGameAction(0, _stateStorage));
});
}

public void Restart()
{
Enqueue(actions =>
{
if (_currentState == PlaybackState.NoFile) return;

_currentFrame = 0;
_broadcastStream.SetCurrentFrame(0);
_currentState = PlaybackState.Running;

actions.Add(new SessionLoadGameAction(0, _stateStorage));
});
}

public void Pause()
{
Enqueue(actions =>
{
if (_currentState != PlaybackState.NoFile)
_currentState = PlaybackState.Paused;
});
}

public void Resume()
{
Enqueue(actions =>
{
if (_currentState == PlaybackState.NoFile) return;
_currentState = PlaybackState.Running;
});
}

public void Step()
{
Enqueue(actions =>
{
if (_currentState == PlaybackState.NoFile) return;

if (_broadcastStream.GetFrameInput(out var frame, out var input))
{
_currentFrame = frame;
actions.Add(new SessionAdvanceFrameAction(frame, input));
}

_currentState = PlaybackState.Paused;
});
}

public void GoToFrame(int frame)
{
Enqueue(actions =>
{
if (_currentState == PlaybackState.NoFile) return;

_targetFrame = frame;
if (_currentFrame > _targetFrame)
{
actions.Add(new SessionLoadGameAction(0, _stateStorage));
_currentFrame = 0;
}

_broadcastStream.SetCurrentFrame(_currentFrame);

while (_currentFrame < _targetFrame)
{
if (!_broadcastStream.GetFrameInput(out var nextFrame, out var nextInput))
break;

_currentFrame = nextFrame;
actions.Add(new SessionAdvanceFrameAction(nextFrame, nextInput));
}

_currentState = PlaybackState.Paused;
_targetFrame = -1;
});
}

private void Enqueue(Action<List<SessionAction>> command) =>
_commandQueue.Enqueue(command);

public override List<SessionAction> AdvanceFrame(byte[] localInput = null)
{
var actions = new List<SessionAction>();

while (_commandQueue.Count > 0)
{
var cmd = _commandQueue.Dequeue();
cmd.Invoke(actions);
}

if (_currentState == PlaybackState.Running)
{
if (_broadcastStream.GetFrameInput(out var frame, out var input))
{
_currentFrame = frame;
actions.Add(new SessionAdvanceFrameAction(frame, input));
}
}

return actions;
}

protected internal override Device LocalDevice =>
throw new NotImplementedException();

protected internal override Device[] AllDevices =>
throw new NotImplementedException();

public override void AddRemoteDevice(uint deviceId, uint playerCount, object remoteConfiguration) =>
throw new NotImplementedException();

public override void AddSpectatorDevice(object remoteConfiguration) =>
throw new NotImplementedException();

public override uint AverageRollbackFrames() =>
throw new NotImplementedException();

public override int Frame() => _currentFrame;

public override int FrameAdvantage() =>
throw new NotImplementedException();

public override int FrameAdvantageDifference() =>
throw new NotImplementedException();

public override bool IsRunning() =>
_currentState != PlaybackState.NoFile;

public override void Poll()
{
// No-op for replay
}

public override int RemoteFrame() =>
throw new NotImplementedException();

public override int RemoteFrameAdvantage() =>
throw new NotImplementedException();

public override uint RollbackFrames() =>
throw new NotImplementedException();

public override void SetLocalDevice(uint deviceId, uint playerCount, uint frameDelay) =>
throw new NotImplementedException();

public override int State() =>
throw new NotImplementedException();

protected internal override void AddRemoteInput(uint deviceId, DeviceInputMessage message) =>
throw new NotImplementedException();

protected internal override uint SendMessageTo(uint deviceId, DeviceMessage message) =>
throw new NotImplementedException();

public override void SaveToReplayFile() =>
throw new NotImplementedException();
}
}
13 changes: 5 additions & 8 deletions src/session/backends/SpectatorSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using PleaseResync.session.backends.utility;
using PleaseResync.Session.Backends.Utility;

namespace PleaseResync.session.backends
namespace PleaseResync.Session.Backends
{
public class SpectatorSession : Session
{
Expand Down Expand Up @@ -46,11 +46,6 @@ public override List<SessionAction> AdvanceFrame(byte[] localInput = null)
{
_currentFrame = frame;
actions.Add(new SessionAdvanceFrameAction(frame, input));

if (frame == 1000|| frame == 5000 || frame == 10000)
{
_broadcastStream.SaveToFile();
}
}
return actions;
}
Expand Down Expand Up @@ -130,7 +125,7 @@ protected internal override void AddRemoteInput(uint deviceId, DeviceInputMessag
{
if (deviceId != _broadcastDevice.Id) return; // discard messages from other devices
var count = message.EndFrame - message.StartFrame + 1;
var inpSize = (int)_broadcastStream.InputSize;
var inpSize = (int)_broadcastStream.InputSize();

for (var i = 0; i < count; i++)
{
Expand All @@ -150,5 +145,7 @@ public override void AddSpectatorDevice(object remoteConfiguration)
{
throw new NotImplementedException();
}

public override void SaveToReplayFile() => _broadcastStream.SaveReplayFile();
}
}
1 change: 1 addition & 0 deletions src/session/backends/SyncTestSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Todo In the future.
Loading