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)
{
int16_t 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
////////////////////////////////////////////////////////////////////////////////
|