rollback-netcode 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/debug.d.ts +29 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +56 -0
- package/dist/debug.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/encoding.d.ts +80 -0
- package/dist/protocol/encoding.d.ts.map +1 -0
- package/dist/protocol/encoding.js +992 -0
- package/dist/protocol/encoding.js.map +1 -0
- package/dist/protocol/messages.d.ts +271 -0
- package/dist/protocol/messages.d.ts.map +1 -0
- package/dist/protocol/messages.js +114 -0
- package/dist/protocol/messages.js.map +1 -0
- package/dist/rollback/engine.d.ts +261 -0
- package/dist/rollback/engine.d.ts.map +1 -0
- package/dist/rollback/engine.js +543 -0
- package/dist/rollback/engine.js.map +1 -0
- package/dist/rollback/input-buffer.d.ts +225 -0
- package/dist/rollback/input-buffer.d.ts.map +1 -0
- package/dist/rollback/input-buffer.js +483 -0
- package/dist/rollback/input-buffer.js.map +1 -0
- package/dist/rollback/snapshot-buffer.d.ts +119 -0
- package/dist/rollback/snapshot-buffer.d.ts.map +1 -0
- package/dist/rollback/snapshot-buffer.js +256 -0
- package/dist/rollback/snapshot-buffer.js.map +1 -0
- package/dist/session/desync-manager.d.ts +106 -0
- package/dist/session/desync-manager.d.ts.map +1 -0
- package/dist/session/desync-manager.js +136 -0
- package/dist/session/desync-manager.js.map +1 -0
- package/dist/session/lag-monitor.d.ts +69 -0
- package/dist/session/lag-monitor.d.ts.map +1 -0
- package/dist/session/lag-monitor.js +74 -0
- package/dist/session/lag-monitor.js.map +1 -0
- package/dist/session/message-builders.d.ts +86 -0
- package/dist/session/message-builders.d.ts.map +1 -0
- package/dist/session/message-builders.js +199 -0
- package/dist/session/message-builders.js.map +1 -0
- package/dist/session/message-router.d.ts +61 -0
- package/dist/session/message-router.d.ts.map +1 -0
- package/dist/session/message-router.js +105 -0
- package/dist/session/message-router.js.map +1 -0
- package/dist/session/player-manager.d.ts +100 -0
- package/dist/session/player-manager.d.ts.map +1 -0
- package/dist/session/player-manager.js +160 -0
- package/dist/session/player-manager.js.map +1 -0
- package/dist/session/session.d.ts +379 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +1294 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/topology.d.ts +66 -0
- package/dist/session/topology.d.ts.map +1 -0
- package/dist/session/topology.js +72 -0
- package/dist/session/topology.js.map +1 -0
- package/dist/transport/adapter.d.ts +99 -0
- package/dist/transport/adapter.d.ts.map +1 -0
- package/dist/transport/adapter.js +8 -0
- package/dist/transport/adapter.js.map +1 -0
- package/dist/transport/local.d.ts +192 -0
- package/dist/transport/local.d.ts.map +1 -0
- package/dist/transport/local.js +435 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/transforming.d.ts +177 -0
- package/dist/transport/transforming.d.ts.map +1 -0
- package/dist/transport/transforming.js +407 -0
- package/dist/transport/transforming.js.map +1 -0
- package/dist/transport/webrtc.d.ts +285 -0
- package/dist/transport/webrtc.d.ts.map +1 -0
- package/dist/transport/webrtc.js +734 -0
- package/dist/transport/webrtc.js.map +1 -0
- package/dist/types.d.ts +394 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +256 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +59 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +93 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-player input tracking with support for dynamic join/leave.
|
|
3
|
+
*
|
|
4
|
+
* Tracks received inputs, confirmed ticks, and used (predicted) inputs
|
|
5
|
+
* to enable misprediction detection during rollback.
|
|
6
|
+
*/
|
|
7
|
+
import { asTick } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Compares two Uint8Arrays for equality.
|
|
10
|
+
*/
|
|
11
|
+
function inputsEqual(a, b) {
|
|
12
|
+
if (a.length !== b.length)
|
|
13
|
+
return false;
|
|
14
|
+
for (let i = 0; i < a.length; i++) {
|
|
15
|
+
if (a[i] !== b[i])
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Buffer for tracking inputs from all players.
|
|
22
|
+
*
|
|
23
|
+
* Supports:
|
|
24
|
+
* - Dynamic player join/leave
|
|
25
|
+
* - Out-of-order input reception
|
|
26
|
+
* - Misprediction detection
|
|
27
|
+
* - Confirmed tick tracking
|
|
28
|
+
*/
|
|
29
|
+
export class InputBuffer {
|
|
30
|
+
players = new Map();
|
|
31
|
+
/** Index of players by their join tick for O(1) lookup */
|
|
32
|
+
joinsByTick = new Map();
|
|
33
|
+
/** Index of players by their leave tick for O(1) lookup */
|
|
34
|
+
leavesByTick = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* Add a player to the buffer.
|
|
37
|
+
*
|
|
38
|
+
* @param playerId - The player's ID
|
|
39
|
+
* @param joinTick - The tick at which the player joins
|
|
40
|
+
*/
|
|
41
|
+
addPlayer(playerId, joinTick) {
|
|
42
|
+
const existing = this.players.get(playerId);
|
|
43
|
+
if (existing) {
|
|
44
|
+
// If player is rejoining, update join tick and clear leave tick
|
|
45
|
+
if (existing.leaveTick !== null) {
|
|
46
|
+
// Remove from old join tick index
|
|
47
|
+
this.removeFromTickIndex(this.joinsByTick, existing.joinTick, playerId);
|
|
48
|
+
// Remove from leave tick index
|
|
49
|
+
this.removeFromTickIndex(this.leavesByTick, existing.leaveTick, playerId);
|
|
50
|
+
existing.joinTick = joinTick;
|
|
51
|
+
existing.leaveTick = null;
|
|
52
|
+
existing.confirmedTick = asTick(joinTick - 1);
|
|
53
|
+
existing.received.clear();
|
|
54
|
+
existing.usedInputs.clear();
|
|
55
|
+
// Add to new join tick index
|
|
56
|
+
this.addToTickIndex(this.joinsByTick, joinTick, playerId);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.players.set(playerId, {
|
|
61
|
+
joinTick,
|
|
62
|
+
leaveTick: null,
|
|
63
|
+
received: new Map(),
|
|
64
|
+
confirmedTick: asTick(joinTick - 1), // No inputs confirmed yet
|
|
65
|
+
usedInputs: new Map(),
|
|
66
|
+
});
|
|
67
|
+
// Add to join tick index
|
|
68
|
+
this.addToTickIndex(this.joinsByTick, joinTick, playerId);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Mark a player as having left.
|
|
72
|
+
*
|
|
73
|
+
* @param playerId - The player's ID
|
|
74
|
+
* @param leaveTick - The tick at which the player leaves
|
|
75
|
+
*/
|
|
76
|
+
removePlayer(playerId, leaveTick) {
|
|
77
|
+
const player = this.players.get(playerId);
|
|
78
|
+
if (player) {
|
|
79
|
+
// Remove from old leave tick index if they had one
|
|
80
|
+
if (player.leaveTick !== null) {
|
|
81
|
+
this.removeFromTickIndex(this.leavesByTick, player.leaveTick, playerId);
|
|
82
|
+
}
|
|
83
|
+
player.leaveTick = leaveTick;
|
|
84
|
+
// Add to leave tick index
|
|
85
|
+
this.addToTickIndex(this.leavesByTick, leaveTick, playerId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Set the confirmed tick for all active players to a specific value.
|
|
90
|
+
* Used after a state sync to indicate that all inputs up to that tick
|
|
91
|
+
* are implicitly confirmed by the synced state.
|
|
92
|
+
*
|
|
93
|
+
* @param tick - The tick to set as confirmed for all active players
|
|
94
|
+
*/
|
|
95
|
+
setConfirmedTickForSync(tick) {
|
|
96
|
+
// Guard against underflow when tick is 0 or negative
|
|
97
|
+
if (tick <= 0) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const confirmedTick = asTick(tick - 1);
|
|
101
|
+
for (const player of this.players.values()) {
|
|
102
|
+
// Only update if the player was active at or before this tick
|
|
103
|
+
if (player.leaveTick === null || player.leaveTick > tick) {
|
|
104
|
+
// Set confirmed tick to tick-1 (the state represents confirmed state)
|
|
105
|
+
if (player.confirmedTick < confirmedTick) {
|
|
106
|
+
player.confirmedTick = confirmedTick;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if a player is active at a given tick.
|
|
113
|
+
*
|
|
114
|
+
* @param playerId - The player's ID
|
|
115
|
+
* @param tick - The tick to check
|
|
116
|
+
* @returns true if the player is active at that tick
|
|
117
|
+
*/
|
|
118
|
+
isPlayerActive(playerId, tick) {
|
|
119
|
+
const player = this.players.get(playerId);
|
|
120
|
+
if (!player)
|
|
121
|
+
return false;
|
|
122
|
+
if (tick < player.joinTick)
|
|
123
|
+
return false;
|
|
124
|
+
if (player.leaveTick !== null && tick >= player.leaveTick)
|
|
125
|
+
return false;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get all players that are active at a given tick.
|
|
130
|
+
*
|
|
131
|
+
* @param tick - The tick to check
|
|
132
|
+
* @returns Array of active player IDs
|
|
133
|
+
*/
|
|
134
|
+
getActivePlayers(tick) {
|
|
135
|
+
const active = [];
|
|
136
|
+
for (const [playerId] of this.players) {
|
|
137
|
+
if (this.isPlayerActive(playerId, tick)) {
|
|
138
|
+
active.push(playerId);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return active;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get all player IDs (active or not).
|
|
145
|
+
*
|
|
146
|
+
* @returns Array of all player IDs
|
|
147
|
+
*/
|
|
148
|
+
getAllPlayers() {
|
|
149
|
+
return Array.from(this.players.keys());
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Receive an input from a player.
|
|
153
|
+
* The input is copied to prevent external mutation.
|
|
154
|
+
*
|
|
155
|
+
* @param playerId - The player's ID
|
|
156
|
+
* @param tick - The tick the input is for
|
|
157
|
+
* @param input - The input data
|
|
158
|
+
*/
|
|
159
|
+
receiveInput(playerId, tick, input) {
|
|
160
|
+
const player = this.players.get(playerId);
|
|
161
|
+
if (!player)
|
|
162
|
+
return;
|
|
163
|
+
// Don't accept inputs for ticks before the player joined
|
|
164
|
+
if (tick < player.joinTick)
|
|
165
|
+
return;
|
|
166
|
+
// Don't accept inputs for ticks after the player left
|
|
167
|
+
if (player.leaveTick !== null && tick >= player.leaveTick)
|
|
168
|
+
return;
|
|
169
|
+
// Copy input to prevent external mutation
|
|
170
|
+
const inputCopy = new Uint8Array(input.length);
|
|
171
|
+
inputCopy.set(input);
|
|
172
|
+
player.received.set(tick, inputCopy);
|
|
173
|
+
// Update confirmed tick (highest consecutive tick with input)
|
|
174
|
+
this.updateConfirmedTick(player);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Update the confirmed tick for a player.
|
|
178
|
+
* Confirmed tick is the highest tick where all ticks from joinTick
|
|
179
|
+
* to that tick have received inputs.
|
|
180
|
+
*/
|
|
181
|
+
updateConfirmedTick(player) {
|
|
182
|
+
let tick = player.confirmedTick + 1;
|
|
183
|
+
while (player.received.has(asTick(tick))) {
|
|
184
|
+
tick++;
|
|
185
|
+
}
|
|
186
|
+
player.confirmedTick = asTick(tick - 1);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get the received input for a player at a specific tick.
|
|
190
|
+
*
|
|
191
|
+
* @param playerId - The player's ID
|
|
192
|
+
* @param tick - The tick to get input for
|
|
193
|
+
* @returns The input, or undefined if not received
|
|
194
|
+
*/
|
|
195
|
+
getInput(playerId, tick) {
|
|
196
|
+
const player = this.players.get(playerId);
|
|
197
|
+
if (!player)
|
|
198
|
+
return undefined;
|
|
199
|
+
return player.received.get(tick);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get the confirmed tick for a player.
|
|
203
|
+
* This is the highest consecutive tick for which we have received input.
|
|
204
|
+
*
|
|
205
|
+
* @param playerId - The player's ID
|
|
206
|
+
* @returns The confirmed tick, or undefined if player not found
|
|
207
|
+
*/
|
|
208
|
+
getConfirmedTick(playerId) {
|
|
209
|
+
const player = this.players.get(playerId);
|
|
210
|
+
if (!player)
|
|
211
|
+
return undefined;
|
|
212
|
+
return player.confirmedTick;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get the join tick for a player.
|
|
216
|
+
*
|
|
217
|
+
* @param playerId - The player's ID
|
|
218
|
+
* @returns The join tick, or undefined if player not found
|
|
219
|
+
*/
|
|
220
|
+
getJoinTick(playerId) {
|
|
221
|
+
const player = this.players.get(playerId);
|
|
222
|
+
return player?.joinTick;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get the leave tick for a player.
|
|
226
|
+
*
|
|
227
|
+
* @param playerId - The player's ID
|
|
228
|
+
* @returns The leave tick, or null/undefined if player hasn't left or not found
|
|
229
|
+
*/
|
|
230
|
+
getLeaveTick(playerId) {
|
|
231
|
+
const player = this.players.get(playerId);
|
|
232
|
+
return player?.leaveTick;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Record the input that was actually used for a player at a tick.
|
|
236
|
+
* This may be the real input or a predicted input.
|
|
237
|
+
*
|
|
238
|
+
* @param playerId - The player's ID
|
|
239
|
+
* @param tick - The tick
|
|
240
|
+
* @param input - The input that was used
|
|
241
|
+
*/
|
|
242
|
+
recordUsedInput(playerId, tick, input) {
|
|
243
|
+
const player = this.players.get(playerId);
|
|
244
|
+
if (!player)
|
|
245
|
+
return;
|
|
246
|
+
// Copy input
|
|
247
|
+
const inputCopy = new Uint8Array(input.length);
|
|
248
|
+
inputCopy.set(input);
|
|
249
|
+
player.usedInputs.set(tick, inputCopy);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get the input that was used for a player at a tick.
|
|
253
|
+
*
|
|
254
|
+
* @param playerId - The player's ID
|
|
255
|
+
* @param tick - The tick
|
|
256
|
+
* @returns The used input, or undefined if not recorded
|
|
257
|
+
*/
|
|
258
|
+
getUsedInput(playerId, tick) {
|
|
259
|
+
const player = this.players.get(playerId);
|
|
260
|
+
if (!player)
|
|
261
|
+
return undefined;
|
|
262
|
+
return player.usedInputs.get(tick);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Find the first tick where a misprediction occurred for a player.
|
|
266
|
+
* A misprediction is when we used a predicted input that differs
|
|
267
|
+
* from the actual received input.
|
|
268
|
+
*
|
|
269
|
+
* @param playerId - The player's ID
|
|
270
|
+
* @param fromTick - Start searching from this tick
|
|
271
|
+
* @returns The tick of the first misprediction, or undefined if none found
|
|
272
|
+
*/
|
|
273
|
+
findMisprediction(playerId, fromTick) {
|
|
274
|
+
const player = this.players.get(playerId);
|
|
275
|
+
if (!player)
|
|
276
|
+
return undefined;
|
|
277
|
+
// Check each tick from fromTick up to confirmedTick
|
|
278
|
+
for (let tick = fromTick; tick <= player.confirmedTick; tick++) {
|
|
279
|
+
const received = player.received.get(asTick(tick));
|
|
280
|
+
const used = player.usedInputs.get(asTick(tick));
|
|
281
|
+
// If we have both received and used inputs, compare them
|
|
282
|
+
if (received !== undefined && used !== undefined) {
|
|
283
|
+
if (!inputsEqual(received, used)) {
|
|
284
|
+
return asTick(tick);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Check if there are any mispredictions for a player in a tick range.
|
|
292
|
+
*
|
|
293
|
+
* @param playerId - The player's ID
|
|
294
|
+
* @param fromTick - Start of range (inclusive)
|
|
295
|
+
* @param toTick - End of range (inclusive)
|
|
296
|
+
* @returns true if any misprediction found
|
|
297
|
+
*/
|
|
298
|
+
hasMisprediction(playerId, fromTick, toTick) {
|
|
299
|
+
const player = this.players.get(playerId);
|
|
300
|
+
if (!player)
|
|
301
|
+
return false;
|
|
302
|
+
for (let tick = fromTick; tick <= toTick; tick++) {
|
|
303
|
+
const received = player.received.get(asTick(tick));
|
|
304
|
+
const used = player.usedInputs.get(asTick(tick));
|
|
305
|
+
if (received !== undefined && used !== undefined) {
|
|
306
|
+
if (!inputsEqual(received, used)) {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get the last confirmed input for a player.
|
|
315
|
+
* Useful for input prediction.
|
|
316
|
+
*
|
|
317
|
+
* @param playerId - The player's ID
|
|
318
|
+
* @returns The last confirmed input, or undefined if none
|
|
319
|
+
*/
|
|
320
|
+
getLastConfirmedInput(playerId) {
|
|
321
|
+
const player = this.players.get(playerId);
|
|
322
|
+
if (!player)
|
|
323
|
+
return undefined;
|
|
324
|
+
// Return input at confirmedTick, not just any received input
|
|
325
|
+
// confirmedTick is the highest consecutive tick with input
|
|
326
|
+
if (player.confirmedTick >= player.joinTick) {
|
|
327
|
+
return player.received.get(player.confirmedTick);
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Remove all data before a given tick.
|
|
333
|
+
* Used to clean up old inputs that are no longer needed.
|
|
334
|
+
*
|
|
335
|
+
* @param tick - Remove all data for ticks < this value
|
|
336
|
+
*/
|
|
337
|
+
pruneBeforeTick(tick) {
|
|
338
|
+
for (const player of this.players.values()) {
|
|
339
|
+
// Remove old received inputs
|
|
340
|
+
for (const t of player.received.keys()) {
|
|
341
|
+
if (t < tick) {
|
|
342
|
+
player.received.delete(t);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Remove old used inputs
|
|
346
|
+
for (const t of player.usedInputs.keys()) {
|
|
347
|
+
if (t < tick) {
|
|
348
|
+
player.usedInputs.delete(t);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Clear used inputs for a player from a given tick onwards.
|
|
355
|
+
* Called during rollback to allow re-recording inputs.
|
|
356
|
+
*
|
|
357
|
+
* @param playerId - The player's ID
|
|
358
|
+
* @param fromTick - Clear from this tick onwards
|
|
359
|
+
*/
|
|
360
|
+
clearUsedInputsFrom(playerId, fromTick) {
|
|
361
|
+
const player = this.players.get(playerId);
|
|
362
|
+
if (!player)
|
|
363
|
+
return;
|
|
364
|
+
for (const tick of player.usedInputs.keys()) {
|
|
365
|
+
if (tick >= fromTick) {
|
|
366
|
+
player.usedInputs.delete(tick);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Clear all used inputs from a given tick onwards for all players.
|
|
372
|
+
*
|
|
373
|
+
* @param fromTick - Clear from this tick onwards
|
|
374
|
+
*/
|
|
375
|
+
clearAllUsedInputsFrom(fromTick) {
|
|
376
|
+
for (const playerId of this.players.keys()) {
|
|
377
|
+
this.clearUsedInputsFrom(playerId, fromTick);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get the minimum confirmed tick across all active players at a given tick.
|
|
382
|
+
*
|
|
383
|
+
* @param tick - The reference tick for determining active players
|
|
384
|
+
* @returns The minimum confirmed tick, or undefined if no active players
|
|
385
|
+
*/
|
|
386
|
+
getMinConfirmedTick(tick) {
|
|
387
|
+
let minTick;
|
|
388
|
+
for (const [playerId, player] of this.players) {
|
|
389
|
+
if (this.isPlayerActive(playerId, tick)) {
|
|
390
|
+
if (minTick === undefined || player.confirmedTick < minTick) {
|
|
391
|
+
minTick = player.confirmedTick;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return minTick;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Check if we have all inputs for a given tick from all active players.
|
|
399
|
+
*
|
|
400
|
+
* @param tick - The tick to check
|
|
401
|
+
* @returns true if all inputs are available
|
|
402
|
+
*/
|
|
403
|
+
hasAllInputsForTick(tick) {
|
|
404
|
+
for (const [playerId] of this.players) {
|
|
405
|
+
if (this.isPlayerActive(playerId, tick)) {
|
|
406
|
+
if (!this.getInput(playerId, tick)) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Clear all data for a player.
|
|
415
|
+
*
|
|
416
|
+
* @param playerId - The player's ID
|
|
417
|
+
*/
|
|
418
|
+
clearPlayer(playerId) {
|
|
419
|
+
const player = this.players.get(playerId);
|
|
420
|
+
if (player) {
|
|
421
|
+
// Remove from tick indexes
|
|
422
|
+
this.removeFromTickIndex(this.joinsByTick, player.joinTick, playerId);
|
|
423
|
+
if (player.leaveTick !== null) {
|
|
424
|
+
this.removeFromTickIndex(this.leavesByTick, player.leaveTick, playerId);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
this.players.delete(playerId);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Clear all players and data.
|
|
431
|
+
*/
|
|
432
|
+
clear() {
|
|
433
|
+
this.players.clear();
|
|
434
|
+
this.joinsByTick.clear();
|
|
435
|
+
this.leavesByTick.clear();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Get all players that join at a specific tick.
|
|
439
|
+
* O(1) lookup using tick-indexed map.
|
|
440
|
+
*
|
|
441
|
+
* @param tick - The tick to check
|
|
442
|
+
* @returns Array of player IDs joining at this tick
|
|
443
|
+
*/
|
|
444
|
+
getPlayersJoiningAtTick(tick) {
|
|
445
|
+
const players = this.joinsByTick.get(tick);
|
|
446
|
+
return players ? Array.from(players) : [];
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get all players that leave at a specific tick.
|
|
450
|
+
* O(1) lookup using tick-indexed map.
|
|
451
|
+
*
|
|
452
|
+
* @param tick - The tick to check
|
|
453
|
+
* @returns Array of player IDs leaving at this tick
|
|
454
|
+
*/
|
|
455
|
+
getPlayersLeavingAtTick(tick) {
|
|
456
|
+
const players = this.leavesByTick.get(tick);
|
|
457
|
+
return players ? Array.from(players) : [];
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Add a player to a tick index.
|
|
461
|
+
*/
|
|
462
|
+
addToTickIndex(index, tick, playerId) {
|
|
463
|
+
let players = index.get(tick);
|
|
464
|
+
if (!players) {
|
|
465
|
+
players = new Set();
|
|
466
|
+
index.set(tick, players);
|
|
467
|
+
}
|
|
468
|
+
players.add(playerId);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Remove a player from a tick index.
|
|
472
|
+
*/
|
|
473
|
+
removeFromTickIndex(index, tick, playerId) {
|
|
474
|
+
const players = index.get(tick);
|
|
475
|
+
if (players) {
|
|
476
|
+
players.delete(playerId);
|
|
477
|
+
if (players.size === 0) {
|
|
478
|
+
index.delete(tick);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
//# sourceMappingURL=input-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-buffer.js","sourceRoot":"","sources":["../../src/rollback/input-buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ring buffer for storing game state snapshots.
|
|
3
|
+
*
|
|
4
|
+
* Used for rollback: when a misprediction is detected, the game state
|
|
5
|
+
* is restored from a snapshot and resimulated forward.
|
|
6
|
+
*/
|
|
7
|
+
import type { Snapshot, Tick } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* A ring buffer that stores game state snapshots for rollback.
|
|
10
|
+
*
|
|
11
|
+
* Snapshots are stored at specific ticks and can be retrieved by tick.
|
|
12
|
+
* When the buffer is full, the oldest snapshot is evicted.
|
|
13
|
+
* Uses a tick-to-index map for O(1) lookup performance.
|
|
14
|
+
*
|
|
15
|
+
* INVARIANT: Snapshots must be saved in ascending tick order. This is required
|
|
16
|
+
* for O(log n) binary search in getAtOrBefore(). The invariant is maintained by:
|
|
17
|
+
* - Normal simulation: ticks advance sequentially (0, 1, 2, ...)
|
|
18
|
+
* - Resimulation: existing ticks are updated in-place, preserving order
|
|
19
|
+
*
|
|
20
|
+
* Saving a non-existing tick out of order will corrupt the sort order and
|
|
21
|
+
* cause getAtOrBefore() to return incorrect results.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SnapshotBuffer {
|
|
24
|
+
readonly capacity: number;
|
|
25
|
+
private readonly buffer;
|
|
26
|
+
private readonly tickToIndex;
|
|
27
|
+
private head;
|
|
28
|
+
private count;
|
|
29
|
+
private _oldestTick;
|
|
30
|
+
private _newestTick;
|
|
31
|
+
/**
|
|
32
|
+
* Create a new snapshot buffer.
|
|
33
|
+
*
|
|
34
|
+
* @param capacity - Maximum number of snapshots to store
|
|
35
|
+
*/
|
|
36
|
+
constructor(capacity: number);
|
|
37
|
+
/**
|
|
38
|
+
* Number of snapshots currently stored.
|
|
39
|
+
*/
|
|
40
|
+
get size(): number;
|
|
41
|
+
/**
|
|
42
|
+
* The oldest tick in the buffer, or undefined if empty.
|
|
43
|
+
*/
|
|
44
|
+
get oldestTick(): Tick | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The newest tick in the buffer, or undefined if empty.
|
|
47
|
+
*/
|
|
48
|
+
get newestTick(): Tick | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Save a snapshot at a specific tick.
|
|
51
|
+
*
|
|
52
|
+
* If the tick already exists, updates in-place to maintain sort order.
|
|
53
|
+
* If the buffer is full, the oldest snapshot is evicted.
|
|
54
|
+
* The state is copied to prevent external mutation.
|
|
55
|
+
*
|
|
56
|
+
* PRECONDITION: New ticks (not already in buffer) must be >= all existing ticks.
|
|
57
|
+
* Saving an out-of-order new tick corrupts sort order. See class invariant.
|
|
58
|
+
*
|
|
59
|
+
* @param tick - The tick this snapshot was taken at
|
|
60
|
+
* @param state - The serialized game state
|
|
61
|
+
* @param hash - The hash of the game state
|
|
62
|
+
*/
|
|
63
|
+
save(tick: Tick, state: Uint8Array, hash: number): void;
|
|
64
|
+
/**
|
|
65
|
+
* Get a snapshot at a specific tick.
|
|
66
|
+
* Uses O(1) map lookup for performance.
|
|
67
|
+
*
|
|
68
|
+
* @param tick - The tick to retrieve
|
|
69
|
+
* @returns The snapshot at that tick, or undefined if not found
|
|
70
|
+
*/
|
|
71
|
+
get(tick: Tick): Snapshot | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Get the oldest snapshot in the buffer.
|
|
74
|
+
*
|
|
75
|
+
* @returns The oldest snapshot, or undefined if empty
|
|
76
|
+
*/
|
|
77
|
+
getOldest(): Snapshot | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Get the newest snapshot in the buffer.
|
|
80
|
+
*
|
|
81
|
+
* @returns The newest snapshot, or undefined if empty
|
|
82
|
+
*/
|
|
83
|
+
getNewest(): Snapshot | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Clear all snapshots from the buffer.
|
|
86
|
+
*/
|
|
87
|
+
clear(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Get the snapshot at or before a given tick.
|
|
90
|
+
* Useful for finding the closest snapshot for rollback.
|
|
91
|
+
*
|
|
92
|
+
* O(log n) binary search. Snapshots are stored in ascending tick order.
|
|
93
|
+
*
|
|
94
|
+
* @param tick - The target tick
|
|
95
|
+
* @returns The snapshot at or before that tick, or undefined if none exists
|
|
96
|
+
*/
|
|
97
|
+
getAtOrBefore(tick: Tick): Snapshot | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Remove all snapshots before a given tick.
|
|
100
|
+
* Used to clean up old snapshots that are no longer needed.
|
|
101
|
+
*
|
|
102
|
+
* @param tick - Remove all snapshots with tick < this value
|
|
103
|
+
*/
|
|
104
|
+
pruneBeforeTick(tick: Tick): void;
|
|
105
|
+
/**
|
|
106
|
+
* Check if a snapshot exists at a given tick.
|
|
107
|
+
*
|
|
108
|
+
* @param tick - The tick to check
|
|
109
|
+
* @returns true if a snapshot exists at that tick
|
|
110
|
+
*/
|
|
111
|
+
has(tick: Tick): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Get all ticks that have snapshots, in order from oldest to newest.
|
|
114
|
+
*
|
|
115
|
+
* @returns Array of ticks with snapshots
|
|
116
|
+
*/
|
|
117
|
+
getTicks(): Tick[];
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=snapshot-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-buffer.d.ts","sourceRoot":"","sources":["../../src/rollback/snapshot-buffer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGlD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,cAAc;aAaE,QAAQ,EAAE,MAAM;IAZ5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgC;IAC5D,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,WAAW,CAAmB;IAEtC;;;;OAIG;gBACyB,QAAQ,EAAE,MAAM;IAO5C;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,IAAI,GAAG,SAAS,CAEjC;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,IAAI,GAAG,SAAS,CAEjC;IAED;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAsDvD;;;;;;OAMG;IACH,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS;IAQrC;;;;OAIG;IACH,SAAS,IAAI,QAAQ,GAAG,SAAS;IAOjC;;;;OAIG;IACH,SAAS,IAAI,QAAQ,GAAG,SAAS;IAQjC;;OAEG;IACH,KAAK,IAAI,IAAI;IASb;;;;;;;;OAQG;IACH,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS;IA6B/C;;;;;OAKG;IACH,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAqBjC;;;;;OAKG;IACH,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAIxB;;;;OAIG;IACH,QAAQ,IAAI,IAAI,EAAE;CAWlB"}
|