forked from Oniryck/POSTAL-1-Open-Source
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NetInput.h
335 lines (305 loc) · 13 KB
/
NetInput.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// netinput.h
// Project: RSPiX
//
// History:
// 09/02/97 MJR Started.
//
////////////////////////////////////////////////////////////////////////////////
#ifndef NETINPUT_H
#define NETINPUT_H
#include "input.h"
////////////////////////////////////////////////////////////////////////////////
//
// CNetInput handle the buffering of inputs received from other players.
//
// The Net::SEQ values are unsigned, and last I checked were 16-bit values,
// but this same logic would work for 32-bit, too.
//
// The sequence values will increment steadly until they wrap-around. We assume
// that the amount of time that it will take for them to wrap-around is greater
// than the amount of time a network packet can live for and still manage to get
// delivered. With a 16-bit value at 30 frames per second, this allow for over
// 30 minutes -- I doubt a network packet can survive that long.
//
// This class impliments a "sliding window" of input values. The window must
// be at least 3 * Net::MaxAheadSeq in size. (A detailed explanation can be
// found in where that value is defined).
//
// Assuming Net::SEQ ranges from 0 to 65535, the window looks like this:
//
// 0-------------[ooonnnnnn]-----------------------------65535
// ^
// F
//
// The dashes represent values we don't care about -- any values that are
// NOT within the window are ignored.
//
// The "F" represents the current frame number, which determines the overall
// position of the window. This is the LOCAL PLAYER'S frame number, not the
// frame number of the player whose data we are dealing with!
//
// As the frame number increases, the window moves to the right, eventually
// wrapping around and starting over at the left side.
//
// The window contains 3 sets of Net::MaxAheadSeq values. The first set is
// to the left of the current frame number, and is represented by o's. These
// are "old" values we must keep around in case this player drops from the game
// and we are the only player that received (and used!) his input values. In
// that case, we will be asked to supply these values to the other players.
//
// The second two sets are represented by n's, and are new values we might
// have received from this player, but that we haven't yet used.
//
// Inputs may arrive out of order, which means we might get a few values,
// then get a few more values further along in the window but leaving a "hole"
// of unknown values in between. We assume that we'll eventually get those
// values, too, but in the meantime, we can't move our frame number past that
// hole.
//
// In order to properly detect which values we did and did not get, we start
// out with the entire window filled with invalid values, and whenever we move
// the window, we mark any "newly uncovered" values as invalid, too. As we
// receive inputs, we put them in the appropriate spots in the array,
// overwriting the invalid values in the process. Whenever an attempt is made
// to move the frame forward, we check if the value for that frame is valid.
// If so, we can move forward. If not, it means we haven't gotten that value
// yet, so we can't move forward yet.
//
//
// All that cool theory aside, the array is actually implimented in a somewhat
// different manner.
//
// By making it a power of 2 in size (making sure it's at least as
// large as 3 * Net::MaxAheadSeq), everything gets harder to think about, but
// very efficient. Normally, in order to impliment a sliding window buffer,
// you need to slide around the values contained in the buffer as it moves
// forward. By making it a power of 2 in size, we can use simple bit-masks
// to get all the indices into to the buffer to automatically wrap-around as
// needed, and suddenly we don't need to move the values anymore -- instead we
// are changing how we look at the buffer. We are merely moving what we think
// of as the start and end of the buffer around, rather than moving everything
// within the buffer. Kind of hard to explain, but it DOES work!
//
// Let's assume Net::SEQ is only 4-bits, so it ranges from 0 to 15. Now let's
// say Net::MaxAheadSeq is 1, so our buffer needs to 3 times that, but instead
// we go with 4 entries, which is the next power-of-2 after 3. The diagram
// below shows how the various input sequences will map into our buffer. In
// order to arrive at the index into our buffer, we simply take the sequence
// number and mark out all the bits but the bottom 2, which yields a number
// between 0 and 3, and happens to be the correct index into our buffer. The
// diagram shows how our buffer "slides along" without moving the actual
// contents -- instead, it's all just a matter of how you think of it.
//
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
// - - - - - - - - - - - - - - - -
// 0 1 2 3
// 1 2 3 0
// 2 3 0 1
// 3 0 1 2
// 0 1 2 3
//
////////////////////////////////////////////////////////////////////////////////
class CNetInput
{
//------------------------------------------------------------------------------
// Types, enums, etc.
//------------------------------------------------------------------------------
public:
enum
{
// Maximum total entries (see elsewhere for in-depth explanation)
MaxTotalEntries = 3 * Net::MaxAheadSeq,
// Maxumum new entries (see elsewhere for in-depth explanation)
MaxNewEntries = 2 * Net::MaxAheadSeq,
// Maximum old entries (see elsewhere for in-depth explanation)
MaxOldEntries = 1 * Net::MaxAheadSeq,
// The size must be a power of two, and the mask must correspond to it.
// Remember that this must be at LEAST as large as MaxTotalEntries!
Size = 256,
Mask = Size - 1,
// Invalid input value
Invalid = 0xffffffff
};
//------------------------------------------------------------------------------
// Variables
//------------------------------------------------------------------------------
protected:
UINPUT m_aInputs[Size]; // Inputs
U8 m_aFrameTimes[Size]; // Game time for the frames *SPA
Net::SEQ m_seqFrame; // Local player's current frame number
Net::SEQ m_seqOldest; // Oldest sequence we have
//------------------------------------------------------------------------------
// Functions
//------------------------------------------------------------------------------
public:
////////////////////////////////////////////////////////////////////////////////
// Constructor
////////////////////////////////////////////////////////////////////////////////
CNetInput()
{
ASSERT(Size >= MaxTotalEntries);
Reset();
}
////////////////////////////////////////////////////////////////////////////////
// Destructor
////////////////////////////////////////////////////////////////////////////////
~CNetInput()
{
Reset();
}
////////////////////////////////////////////////////////////////////////////////
// Reset to post-construction state
////////////////////////////////////////////////////////////////////////////////
void Reset(void)
{
short i = 0;
// Clear the entire window to "invalid" values
for (i = 0; i < Size; i++)
m_aInputs[i] = Invalid;
// Clear the entire window to initail values *SPA !!Eventually should input from prefs!!
for (i = 0; i < Size; i++)
m_aFrameTimes[i] = 100;
// Start the frame at 0. The oldest value always lags by a fixed distance.
m_seqFrame = 0;
m_seqOldest = m_seqFrame - MaxOldEntries;
}
////////////////////////////////////////////////////////////////////////////////
// Move the frame forward
// IT IS ASSUMED THAT THE CALLER WILL NOT BE STUPID!!!
// Don't ever move the frame forward unless the input for the current frame
// is valid, as reported by GetInput()!
////////////////////////////////////////////////////////////////////////////////
void IncFrame(void)
{
// Make sure this makes sense!
ASSERT(m_aInputs[m_seqFrame & Mask] != Invalid);
// Invalidate oldest sequence
m_aInputs[m_seqOldest & Mask] = Invalid;
// Move forward
m_seqFrame++;
m_seqOldest++;
}
////////////////////////////////////////////////////////////////////////////////
// Put a new input value.
// If the specified seq is outside of the "new value window", it is ignored.
////////////////////////////////////////////////////////////////////////////////
void Put(
Net::SEQ seq,
UINPUT input)
{
// If the seq falls within the "new values" window, we use it. Note that
// the "new values" window starts at the current frame.
// The excessive casting is to make sure the compiler does this all as
// unsigned math and comparisons.
if ((Net::SEQ)(seq - m_seqFrame) < (Net::SEQ)MaxNewEntries)
m_aInputs[seq & Mask] = input;
}
////////////////////////////////////////////////////////////////////////////////
// Put a new frame time value. *SPA
// If the specified seq is outside of the "new value window", it is ignored.
////////////////////////////////////////////////////////////////////////////////
void PutFrameTime(
Net::SEQ seq,
U8 frameTime)
{
// If the seq falls within the "new values" window, we use it. Note that
// the "new values" window starts at the current frame.
// The excessive casting is to make sure the compiler does this all as
// unsigned math and comparisons.
if ((Net::SEQ)(seq - m_seqFrame) < (Net::SEQ)MaxNewEntries)
{
m_aFrameTimes[seq & Mask] = frameTime;
}
}
////////////////////////////////////////////////////////////////////////////////
// Find first invalid value starting at the specified seq and continuing to
// the end of the "new" window.
////////////////////////////////////////////////////////////////////////////////
Net::SEQ FindFirstInvalid(
Net::SEQ seq)
{
Net::SEQ offset = (Net::SEQ)(seq - m_seqFrame);
while (offset < (Net::SEQ)MaxNewEntries)
{
if (m_aInputs[seq & Mask] == Invalid)
break;
seq++;
offset++;
}
return seq;
}
// This alternative version starts at the current frame
Net::SEQ FindFirstInvalid(void)
{
return FindFirstInvalid(m_seqFrame);
}
////////////////////////////////////////////////////////////////////////////////
// Get the specified input value.
// A return value of CNetInput::Invalid means that input was not available.
////////////////////////////////////////////////////////////////////////////////
UINPUT Get(
Net::SEQ seq)
{
// If the seq falls within the total window, we get it. Note that this
// window starts at the oldest value, which is always Net::MaxAheadSeq less
// than the current frame.
// The excessive casting is to make sure the compiler does this all as
// unsigned math and comparisons.
if ((Net::SEQ)(seq - m_seqOldest) < (Net::SEQ)MaxTotalEntries)
return m_aInputs[seq & Mask];
else
return Invalid;
}
////////////////////////////////////////////////////////////////////////////////
// Get the specified frame time value. *SPA
// A return value of CNetInput::Invalid means that input was not available.
////////////////////////////////////////////////////////////////////////////////
U8 GetFrameTime(
Net::SEQ seq)
{
// If the seq falls within the total window, we get it. Note that this
// window starts at the oldest value, which is always Net::MaxAheadSeq less
// than the current frame.
// The excessive casting is to make sure the compiler does this all as
// unsigned math and comparisons.
if ((Net::SEQ)(seq - m_seqOldest) < (Net::SEQ)MaxTotalEntries)
return m_aFrameTimes[seq & Mask];
else
return Invalid;
}
////////////////////////////////////////////////////////////////////////////////
// Get current frame seq
////////////////////////////////////////////////////////////////////////////////
Net::SEQ GetFrameSeq(void)
{
return m_seqFrame;
}
////////////////////////////////////////////////////////////////////////////////
// Get oldest frame seq
////////////////////////////////////////////////////////////////////////////////
Net::SEQ GetOldestSeq(void)
{
return m_seqOldest;
}
};
#endif //NETINPUT_H
////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////