99using Backdash . Synchronizing . Input ;
1010using Backdash . Synchronizing . Input . Confirmed ;
1111using Backdash . Synchronizing . Random ;
12+ using Backdash . Synchronizing . State ;
1213
1314namespace Backdash . Backends ;
1415
@@ -24,7 +25,6 @@ sealed class SpectatorBackend<TInput> :
2425 readonly NetcodeOptions options ;
2526 readonly IBackgroundJobManager backgroundJobManager ;
2627 readonly IClock clock ;
27- readonly IDeterministicRandom deterministicRandom ;
2828 readonly ConnectionsState localConnections = new ( 0 ) ;
2929 readonly GameInput < ConfirmedInputs < TInput > > [ ] inputs ;
3030 readonly PeerConnection < ConfirmedInputs < TInput > > host ;
@@ -38,6 +38,8 @@ sealed class SpectatorBackend<TInput> :
3838 SynchronizedInput < TInput > [ ] syncInputBuffer = [ ] ;
3939 TInput [ ] inputBuffer = [ ] ;
4040 bool closed ;
41+ readonly IStateStore stateStore ;
42+ readonly IChecksumProvider checksumProvider ;
4143
4244 public SpectatorBackend ( int port ,
4345 IPEndPoint hostEndpoint ,
@@ -53,9 +55,11 @@ public SpectatorBackend(int port,
5355 this . hostEndpoint = hostEndpoint ;
5456 this . options = options ;
5557 backgroundJobManager = services . JobManager ;
56- deterministicRandom = services . DeterministicRandom ;
58+ Random = services . DeterministicRandom ;
5759 logger = services . Logger ;
5860 clock = services . Clock ;
61+ stateStore = services . StateStore ;
62+ checksumProvider = services . ChecksumProvider ;
5963 NumberOfPlayers = numberOfPlayers ;
6064 fakePlayers = Enumerable . Range ( 0 , numberOfPlayers )
6165 . Select ( x => new PlayerHandle ( PlayerType . Remote , x + 1 , x ) ) . ToArray ( ) ;
@@ -71,17 +75,19 @@ public SpectatorBackend(int port,
7175
7276 PeerConnectionFactory peerConnectionFactory = new (
7377 this , clock , services . Random , logger , udp ,
74- options . Protocol , options . TimeSync , services . StateStore
78+ options . Protocol , options . TimeSync , stateStore
7579 ) ;
7680
7781 ProtocolState protocolState =
7882 new ( new ( PlayerType . Remote , 0 ) , hostEndpoint , localConnections , magicNumber ) ;
7983
8084 var inputGroupComparer = ConfirmedInputComparer < TInput > . Create ( services . InputComparer ) ;
8185 host = peerConnectionFactory . Create ( protocolState , inputGroupSerializer , this , inputGroupComparer ) ;
86+
8287 peerObservers . Add ( host . GetUdpObserver ( ) ) ;
8388 host . Synchronize ( ) ;
8489 isSynchronizing = true ;
90+ stateStore . Initialize ( options . TotalPredictionFrames ) ;
8591 }
8692
8793 public void Dispose ( )
@@ -106,11 +112,14 @@ public void Close()
106112 public Frame CurrentFrame { get ; private set ; } = Frame . Zero ;
107113 public FrameSpan RollbackFrames => FrameSpan . Zero ;
108114 public FrameSpan FramesBehind => FrameSpan . Zero ;
115+ public SavedFrame CurrentSavedFrame => stateStore . GetCurrent ( ) ;
116+
109117 public int NumberOfPlayers { get ; private set ; }
110118 public int NumberOfSpectators => 0 ;
111119
112- public IDeterministicRandom Random => deterministicRandom ;
120+ public IDeterministicRandom Random { get ; }
113121 public SessionMode Mode => SessionMode . Spectating ;
122+
114123 public void DisconnectPlayer ( in PlayerHandle player ) { }
115124 public ResultCode AddLocalInput ( PlayerHandle player , in TInput localInput ) => ResultCode . Ok ;
116125 public IReadOnlyCollection < PlayerHandle > GetPlayers ( ) => fakePlayers ;
@@ -124,8 +133,8 @@ public void BeginFrame()
124133 if ( isSynchronizing )
125134 return ;
126135
127- if ( lastReceivedInputTime > 0
128- && clock . GetElapsedTime ( lastReceivedInputTime ) > options . Protocol . DisconnectTimeout )
136+ if ( lastReceivedInputTime > 0 &&
137+ clock . GetElapsedTime ( lastReceivedInputTime ) > options . Protocol . DisconnectTimeout )
129138 Close ( ) ;
130139 }
131140
@@ -243,12 +252,27 @@ public ResultCode SynchronizeInputs()
243252 }
244253
245254 var inputPopCount = options . UseInputSeedForRandom ? Mem . PopCount < TInput > ( inputBuffer . AsSpan ( ) ) : 0 ;
246- deterministicRandom . UpdateSeed ( CurrentFrame . Number , inputPopCount ) ;
255+ Random . UpdateSeed ( CurrentFrame . Number , inputPopCount ) ;
247256
248257 CurrentFrame ++ ;
258+ SaveCurrentFrame ( ) ;
249259 return ResultCode . Ok ;
250260 }
251261
262+ void SaveCurrentFrame ( )
263+ {
264+ var currentFrame = CurrentFrame ;
265+ ref var nextState = ref stateStore . GetCurrent ( ) ;
266+
267+ BinaryBufferWriter writer = new ( nextState . GameState ) ;
268+ callbacks . SaveState ( in currentFrame , in writer ) ;
269+ nextState . Frame = currentFrame ;
270+ nextState . Checksum = checksumProvider . Compute ( nextState . GameState . WrittenSpan ) ;
271+
272+ stateStore . Advance ( ) ;
273+ logger . Write ( LogLevel . Trace , $ "spectator: saved frame { nextState . Frame } (checksum: { nextState . Checksum } ).") ;
274+ }
275+
252276 public ref readonly SynchronizedInput < TInput > GetInput ( int index ) =>
253277 ref syncInputBuffer [ index ] ;
254278
@@ -257,12 +281,12 @@ public ref readonly SynchronizedInput<TInput> GetInput(in PlayerHandle player) =
257281
258282 public void GetInputs ( Span < SynchronizedInput < TInput > > buffer ) => syncInputBuffer . CopyTo ( buffer ) ;
259283
260- void IProtocolInputEventPublisher < ConfirmedInputs < TInput > > . Publish ( in GameInputEvent < ConfirmedInputs < TInput > > evt )
284+ bool IProtocolInputEventPublisher < ConfirmedInputs < TInput > > . Publish ( in GameInputEvent < ConfirmedInputs < TInput > > evt )
261285 {
262286 lastReceivedInputTime = clock . GetTimeStamp ( ) ;
263287 var ( _, input ) = evt ;
264288 inputs [ input . Frame . Number % inputs . Length ] = input ;
265289 host . SetLocalFrameNumber ( input . Frame , options . FramesPerSecond ) ;
266- host . SendInputAck ( ) ;
290+ return host . SendInputAck ( ) ;
267291 }
268292}
0 commit comments