Netcode Overhaul

Rewrote netcode to be more stable and functional. Packet-server mode has been restrategized and will now be the default netmode when playing with 3+ non-LAN players. TryRunTics has been cleaned up with older tick control behavior removed to account for the rewritten renderer. The main thread is now more consistent when playing online to prevent potential slow downs and lock ups. Load barriers are better accounted for to prevent spikes on level transition. Improvements to chat and lobby systems including a force start game button. Added a suite of new host options such as kicking and controlling who can pause the game. Max players increased from 8 to 16 since the new code can now handle it.

Note: Demo functionality is untested. This will be rewritten at a later time alongside improvements to GZDoom's playback features (e.g. freecam mode).
This commit is contained in:
Boondorl 2024-11-24 18:26:06 -05:00 committed by Rachael Alexanderson
commit 94be307225
45 changed files with 3119 additions and 2669 deletions

View file

@ -32,44 +32,42 @@
**
*/
#include "d_protocol.h"
#include "d_net.h"
#include "doomstat.h"
#include "cmdlib.h"
#include "serializer.h"
char *ReadString (uint8_t **stream)
char* ReadString(uint8_t **stream)
{
char *string = *((char **)stream);
*stream += strlen (string) + 1;
return copystring (string);
*stream += strlen(string) + 1;
return copystring(string);
}
const char *ReadStringConst(uint8_t **stream)
const char* ReadStringConst(uint8_t **stream)
{
const char *string = *((const char **)stream);
*stream += strlen (string) + 1;
*stream += strlen(string) + 1;
return string;
}
uint8_t ReadInt8 (uint8_t **stream)
uint8_t ReadInt8(uint8_t **stream)
{
uint8_t v = **stream;
*stream += 1;
return v;
}
int16_t ReadInt16 (uint8_t **stream)
int16_t ReadInt16(uint8_t **stream)
{
int16_t v = (((*stream)[0]) << 8) | (((*stream)[1]));
*stream += 2;
return v;
}
int32_t ReadInt32 (uint8_t **stream)
int32_t ReadInt32(uint8_t **stream)
{
int32_t v = (((*stream)[0]) << 24) | (((*stream)[1]) << 16) | (((*stream)[2]) << 8) | (((*stream)[3]));
*stream += 4;
@ -84,7 +82,7 @@ int64_t ReadInt64(uint8_t** stream)
return v;
}
float ReadFloat (uint8_t **stream)
float ReadFloat(uint8_t **stream)
{
union
{
@ -106,7 +104,7 @@ double ReadDouble(uint8_t** stream)
return fakeint.f;
}
void WriteString (const char *string, uint8_t **stream)
void WriteString(const char *string, uint8_t **stream)
{
char *p = *((char **)stream);
@ -118,21 +116,20 @@ void WriteString (const char *string, uint8_t **stream)
*stream = (uint8_t *)p;
}
void WriteInt8 (uint8_t v, uint8_t **stream)
void WriteInt8(uint8_t v, uint8_t **stream)
{
**stream = v;
*stream += 1;
}
void WriteInt16 (int16_t v, uint8_t **stream)
void WriteInt16(int16_t v, uint8_t **stream)
{
(*stream)[0] = v >> 8;
(*stream)[1] = v & 255;
*stream += 2;
}
void WriteInt32 (int32_t v, uint8_t **stream)
void WriteInt32(int32_t v, uint8_t **stream)
{
(*stream)[0] = v >> 24;
(*stream)[1] = (v >> 16) & 255;
@ -154,7 +151,7 @@ void WriteInt64(int64_t v, uint8_t** stream)
*stream += 8;
}
void WriteFloat (float v, uint8_t **stream)
void WriteFloat(float v, uint8_t **stream)
{
union
{
@ -176,171 +173,7 @@ void WriteDouble(double v, uint8_t** stream)
WriteInt64(fakeint.i, stream);
}
// Returns the number of bytes read
int UnpackUserCmd (usercmd_t *ucmd, const usercmd_t *basis, uint8_t **stream)
{
uint8_t *start = *stream;
uint8_t flags;
if (basis != NULL)
{
if (basis != ucmd)
{
memcpy (ucmd, basis, sizeof(usercmd_t));
}
}
else
{
memset (ucmd, 0, sizeof(usercmd_t));
}
flags = ReadInt8 (stream);
if (flags)
{
// We can support up to 29 buttons, using from 0 to 4 bytes to store them.
if (flags & UCMDF_BUTTONS)
{
uint32_t buttons = ucmd->buttons;
uint8_t in = ReadInt8(stream);
buttons = (buttons & ~0x7F) | (in & 0x7F);
if (in & 0x80)
{
in = ReadInt8(stream);
buttons = (buttons & ~(0x7F << 7)) | ((in & 0x7F) << 7);
if (in & 0x80)
{
in = ReadInt8(stream);
buttons = (buttons & ~(0x7F << 14)) | ((in & 0x7F) << 14);
if (in & 0x80)
{
in = ReadInt8(stream);
buttons = (buttons & ~(0xFF << 21)) | (in << 21);
}
}
}
ucmd->buttons = buttons;
}
if (flags & UCMDF_PITCH)
ucmd->pitch = ReadInt16 (stream);
if (flags & UCMDF_YAW)
ucmd->yaw = ReadInt16 (stream);
if (flags & UCMDF_FORWARDMOVE)
ucmd->forwardmove = ReadInt16 (stream);
if (flags & UCMDF_SIDEMOVE)
ucmd->sidemove = ReadInt16 (stream);
if (flags & UCMDF_UPMOVE)
ucmd->upmove = ReadInt16 (stream);
if (flags & UCMDF_ROLL)
ucmd->roll = ReadInt16 (stream);
}
return int(*stream - start);
}
// Returns the number of bytes written
int PackUserCmd (const usercmd_t *ucmd, const usercmd_t *basis, uint8_t **stream)
{
uint8_t flags = 0;
uint8_t *temp = *stream;
uint8_t *start = *stream;
usercmd_t blank;
uint32_t buttons_changed;
if (basis == NULL)
{
memset (&blank, 0, sizeof(blank));
basis = &blank;
}
WriteInt8 (0, stream); // Make room for the packing bits
buttons_changed = ucmd->buttons ^ basis->buttons;
if (buttons_changed != 0)
{
uint8_t bytes[4] = { uint8_t(ucmd->buttons & 0x7F),
uint8_t((ucmd->buttons >> 7) & 0x7F),
uint8_t((ucmd->buttons >> 14) & 0x7F),
uint8_t((ucmd->buttons >> 21) & 0xFF) };
flags |= UCMDF_BUTTONS;
if (buttons_changed & 0xFFFFFF80)
{
bytes[0] |= 0x80;
if (buttons_changed & 0xFFFFC000)
{
bytes[1] |= 0x80;
if (buttons_changed & 0xFFE00000)
{
bytes[2] |= 0x80;
}
}
}
WriteInt8 (bytes[0], stream);
if (bytes[0] & 0x80)
{
WriteInt8 (bytes[1], stream);
if (bytes[1] & 0x80)
{
WriteInt8 (bytes[2], stream);
if (bytes[2] & 0x80)
{
WriteInt8 (bytes[3], stream);
}
}
}
}
if (ucmd->pitch != basis->pitch)
{
flags |= UCMDF_PITCH;
WriteInt16 (ucmd->pitch, stream);
}
if (ucmd->yaw != basis->yaw)
{
flags |= UCMDF_YAW;
WriteInt16 (ucmd->yaw, stream);
}
if (ucmd->forwardmove != basis->forwardmove)
{
flags |= UCMDF_FORWARDMOVE;
WriteInt16 (ucmd->forwardmove, stream);
}
if (ucmd->sidemove != basis->sidemove)
{
flags |= UCMDF_SIDEMOVE;
WriteInt16 (ucmd->sidemove, stream);
}
if (ucmd->upmove != basis->upmove)
{
flags |= UCMDF_UPMOVE;
WriteInt16 (ucmd->upmove, stream);
}
if (ucmd->roll != basis->roll)
{
flags |= UCMDF_ROLL;
WriteInt16 (ucmd->roll, stream);
}
// Write the packing bits
WriteInt8 (flags, &temp);
return int(*stream - start);
}
FSerializer &Serialize(FSerializer &arc, const char *key, ticcmd_t &cmd, ticcmd_t *def)
{
if (arc.BeginObject(key))
{
arc("consistency", cmd.consistancy)
("ucmd", cmd.ucmd)
.EndObject();
}
return arc;
}
FSerializer &Serialize(FSerializer &arc, const char *key, usercmd_t &cmd, usercmd_t *def)
FSerializer& Serialize(FSerializer& arc, const char* key, usercmd_t& cmd, usercmd_t* def)
{
// This used packed data with the old serializer but that's totally counterproductive when
// having a text format that is human-readable. So this compression has been undone here.
@ -360,192 +193,308 @@ FSerializer &Serialize(FSerializer &arc, const char *key, usercmd_t &cmd, usercm
return arc;
}
int WriteUserCmdMessage (usercmd_t *ucmd, const usercmd_t *basis, uint8_t **stream)
// Returns the number of bytes read
int UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
{
if (basis == NULL)
const uint8_t* start = stream;
if (basis != nullptr)
{
if (ucmd->buttons != 0 ||
ucmd->pitch != 0 ||
ucmd->yaw != 0 ||
ucmd->forwardmove != 0 ||
ucmd->sidemove != 0 ||
ucmd->upmove != 0 ||
ucmd->roll != 0)
{
WriteInt8 (DEM_USERCMD, stream);
return PackUserCmd (ucmd, basis, stream) + 1;
}
if (basis != &cmd)
memcpy(&cmd, basis, sizeof(usercmd_t));
}
else
if (ucmd->buttons != basis->buttons ||
ucmd->pitch != basis->pitch ||
ucmd->yaw != basis->yaw ||
ucmd->forwardmove != basis->forwardmove ||
ucmd->sidemove != basis->sidemove ||
ucmd->upmove != basis->upmove ||
ucmd->roll != basis->roll)
{
WriteInt8 (DEM_USERCMD, stream);
return PackUserCmd (ucmd, basis, stream) + 1;
memset(&cmd, 0, sizeof(usercmd_t));
}
WriteInt8 (DEM_EMPTYUSERCMD, stream);
uint8_t flags = ReadInt8(&stream);
if (flags)
{
// We can support up to 29 buttons using 1 to 4 bytes to store them. The most
// significant bit of each button byte is a flag to indicate whether or not more buttons
// should be read in excluding the last one which supports all 8 bits.
if (flags & UCMDF_BUTTONS)
{
uint8_t in = ReadInt8(&stream);
uint32_t buttons = (cmd.buttons & ~0x7F) | (in & 0x7F);
if (in & MoreButtons)
{
in = ReadInt8(&stream);
buttons = (buttons & ~(0x7F << 7)) | ((in & 0x7F) << 7);
if (in & MoreButtons)
{
in = ReadInt8(&stream);
buttons = (buttons & ~(0x7F << 14)) | ((in & 0x7F) << 14);
if (in & MoreButtons)
{
in = ReadInt8(&stream);
buttons = (buttons & ~(0xFF << 21)) | (in << 21);
}
}
}
cmd.buttons = buttons;
}
if (flags & UCMDF_PITCH)
cmd.pitch = ReadInt16(&stream);
if (flags & UCMDF_YAW)
cmd.yaw = ReadInt16(&stream);
if (flags & UCMDF_FORWARDMOVE)
cmd.forwardmove = ReadInt16(&stream);
if (flags & UCMDF_SIDEMOVE)
cmd.sidemove = ReadInt16(&stream);
if (flags & UCMDF_UPMOVE)
cmd.upmove = ReadInt16(&stream);
if (flags & UCMDF_ROLL)
cmd.roll = ReadInt16(&stream);
}
return int(stream - start);
}
int PackUserCmd(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
{
uint8_t flags = 0;
uint8_t* start = stream;
usercmd_t blank;
if (basis == nullptr)
{
memset(&blank, 0, sizeof(blank));
basis = &blank;
}
++stream; // Make room for the flags.
uint32_t buttons_changed = cmd.buttons ^ basis->buttons;
if (buttons_changed != 0)
{
uint8_t bytes[4] = { uint8_t(cmd.buttons & 0x7F),
uint8_t((cmd.buttons >> 7) & 0x7F),
uint8_t((cmd.buttons >> 14) & 0x7F),
uint8_t((cmd.buttons >> 21) & 0xFF) };
flags |= UCMDF_BUTTONS;
if (buttons_changed & 0xFFFFFF80)
{
bytes[0] |= MoreButtons;
if (buttons_changed & 0xFFFFC000)
{
bytes[1] |= MoreButtons;
if (buttons_changed & 0xFFE00000)
bytes[2] |= MoreButtons;
}
}
WriteInt8(bytes[0], &stream);
if (bytes[0] & MoreButtons)
{
WriteInt8(bytes[1], &stream);
if (bytes[1] & MoreButtons)
{
WriteInt8(bytes[2], &stream);
if (bytes[2] & MoreButtons)
WriteInt8(bytes[3], &stream);
}
}
}
if (cmd.pitch != basis->pitch)
{
flags |= UCMDF_PITCH;
WriteInt16(cmd.pitch, &stream);
}
if (cmd.yaw != basis->yaw)
{
flags |= UCMDF_YAW;
WriteInt16 (cmd.yaw, &stream);
}
if (cmd.forwardmove != basis->forwardmove)
{
flags |= UCMDF_FORWARDMOVE;
WriteInt16 (cmd.forwardmove, &stream);
}
if (cmd.sidemove != basis->sidemove)
{
flags |= UCMDF_SIDEMOVE;
WriteInt16(cmd.sidemove, &stream);
}
if (cmd.upmove != basis->upmove)
{
flags |= UCMDF_UPMOVE;
WriteInt16(cmd.upmove, &stream);
}
if (cmd.roll != basis->roll)
{
flags |= UCMDF_ROLL;
WriteInt16(cmd.roll, &stream);
}
// Write the packing bits
WriteInt8(flags, &start);
return int(stream - start);
}
int WriteUserCmdMessage(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
{
if (basis == nullptr)
{
if (cmd.buttons
|| cmd.pitch || cmd.yaw || cmd.roll
|| cmd.forwardmove || cmd.sidemove || cmd.upmove)
{
WriteInt8(DEM_USERCMD, &stream);
return PackUserCmd(cmd, basis, stream) + 1;
}
}
else if (cmd.buttons != basis->buttons
|| cmd.yaw != basis->yaw || cmd.pitch != basis->pitch || cmd.roll != basis->roll
|| cmd.forwardmove != basis->forwardmove || cmd.sidemove != basis->sidemove || cmd.upmove != basis->upmove)
{
WriteInt8(DEM_USERCMD, &stream);
return PackUserCmd(cmd, basis, stream) + 1;
}
WriteInt8(DEM_EMPTYUSERCMD, &stream);
return 1;
}
int SkipTicCmd (uint8_t **stream, int count)
// Reads through the user command without actually setting any of its info. Used to get the size
// of the command when getting the length of the stream.
int SkipUserCmdMessage(uint8_t*& stream)
{
int i, skip;
uint8_t *flow = *stream;
const uint8_t* start = stream;
for (i = count; i > 0; i--)
while (true)
{
bool moreticdata = true;
flow += 2; // Skip consistancy marker
while (moreticdata)
const uint8_t type = *stream++;
if (type == DEM_USERCMD)
{
uint8_t type = *flow++;
if (type == DEM_USERCMD)
int skip = 1;
if (*stream & UCMDF_PITCH)
skip += 2;
if (*stream & UCMDF_YAW)
skip += 2;
if (*stream & UCMDF_FORWARDMOVE)
skip += 2;
if (*stream & UCMDF_SIDEMOVE)
skip += 2;
if (*stream & UCMDF_UPMOVE)
skip += 2;
if (*stream & UCMDF_ROLL)
skip += 2;
if (*stream & UCMDF_BUTTONS)
{
moreticdata = false;
skip = 1;
if (*flow & UCMDF_PITCH) skip += 2;
if (*flow & UCMDF_YAW) skip += 2;
if (*flow & UCMDF_FORWARDMOVE) skip += 2;
if (*flow & UCMDF_SIDEMOVE) skip += 2;
if (*flow & UCMDF_UPMOVE) skip += 2;
if (*flow & UCMDF_ROLL) skip += 2;
if (*flow & UCMDF_BUTTONS)
if (*++stream & MoreButtons)
{
if (*++flow & 0x80)
if (*++stream & MoreButtons)
{
if (*++flow & 0x80)
{
if (*++flow & 0x80)
{
++flow;
}
}
if (*++stream & MoreButtons)
++stream;
}
}
flow += skip;
}
else if (type == DEM_EMPTYUSERCMD)
{
moreticdata = false;
}
else
{
Net_SkipCommand (type, &flow);
}
stream += skip;
break;
}
}
skip = int(flow - *stream);
*stream = flow;
return skip;
}
extern short consistancy[MAXPLAYERS][BACKUPTICS];
void ReadTicCmd (uint8_t **stream, int player, int tic)
{
int type;
uint8_t *start;
ticcmd_t *tcmd;
int ticmod = tic % BACKUPTICS;
tcmd = &netcmds[player][ticmod];
tcmd->consistancy = ReadInt16 (stream);
start = *stream;
while ((type = ReadInt8 (stream)) != DEM_USERCMD && type != DEM_EMPTYUSERCMD)
Net_SkipCommand (type, stream);
NetSpecs[player][ticmod].SetData (start, int(*stream - start - 1));
if (type == DEM_USERCMD)
{
UnpackUserCmd (&tcmd->ucmd,
tic ? &netcmds[player][(tic-1)%BACKUPTICS].ucmd : NULL, stream);
}
else
{
if (tic)
else if (type == DEM_EMPTYUSERCMD)
{
memcpy (&tcmd->ucmd, &netcmds[player][(tic-1)%BACKUPTICS].ucmd, sizeof(tcmd->ucmd));
break;
}
else
{
memset (&tcmd->ucmd, 0, sizeof(tcmd->ucmd));
Net_SkipCommand(type, &stream);
}
}
if (player==consoleplayer&&tic>BACKUPTICS)
assert(consistancy[player][ticmod] == tcmd->consistancy);
return int(stream - start);
}
void RunNetSpecs (int player, int buf)
int ReadUserCmdMessage(uint8_t*& stream, int player, int tic)
{
uint8_t *stream;
int len;
const int ticMod = tic % BACKUPTICS;
if (gametic % ticdup == 0)
auto& curTic = ClientStates[player].Tics[ticMod];
usercmd_t& ticCmd = curTic.Command;
const uint8_t* start = stream;
// Skip until we reach the player command. Event data will get read off once the
// tick is actually executed.
int type;
while ((type = ReadInt8(&stream)) != DEM_USERCMD && type != DEM_EMPTYUSERCMD)
Net_SkipCommand(type, &stream);
// Subtract a byte to account for the fact the stream head is now sitting on the
// user command.
curTic.Data.SetData(start, int(stream - start - 1));
if (type == DEM_USERCMD)
{
stream = NetSpecs[player][buf].GetData (&len);
if (stream)
{
uint8_t *end = stream + len;
while (stream < end)
{
int type = ReadInt8 (&stream);
Net_DoCommand (type, &stream, player);
}
if (!demorecording)
NetSpecs[player][buf].SetData (NULL, 0);
}
UnpackUserCmd(ticCmd,
tic > 0 ? &ClientStates[player].Tics[(tic - 1) % BACKUPTICS].Command : nullptr, stream);
}
else
{
if (tic > 0)
memcpy(&ticCmd, &ClientStates[player].Tics[(tic - 1) % BACKUPTICS].Command, sizeof(ticCmd));
else
memset(&ticCmd, 0, sizeof(ticCmd));
}
return int(stream - start);
}
void RunPlayerCommands(int player, int tic)
{
// We don't have the full command yet, so don't run it.
if (gametic % doomcom.ticdup)
return;
int len;
auto& data = ClientStates[player].Tics[tic % BACKUPTICS].Data;
uint8_t* stream = data.GetData(&len);
if (stream != nullptr)
{
uint8_t* end = stream + len;
while (stream < end)
Net_DoCommand(ReadInt8(&stream), &stream, player);
if (!demorecording)
data.SetData(nullptr, 0);
}
}
uint8_t *lenspot;
// Demo related functionality
uint8_t* streamPos = nullptr;
// Write the header of an IFF chunk and leave space
// for the length field.
void StartChunk (int id, uint8_t **stream)
void StartChunk(int id, uint8_t **stream)
{
WriteInt32 (id, stream);
lenspot = *stream;
WriteInt32(id, stream);
streamPos = *stream;
*stream += 4;
}
// Write the length field for the chunk and insert
// pad byte if the chunk is odd-sized.
void FinishChunk (uint8_t **stream)
void FinishChunk(uint8_t **stream)
{
int len;
if (!lenspot)
if (streamPos == nullptr)
return;
len = int(*stream - lenspot - 4);
WriteInt32 (len, &lenspot);
int len = int(*stream - streamPos - 4);
WriteInt32(len, &streamPos);
if (len & 1)
WriteInt8 (0, stream);
WriteInt8(0, stream);
lenspot = NULL;
streamPos = nullptr;
}
// Skip past an unknown chunk. *stream should be
// pointing to the chunk's length field.
void SkipChunk (uint8_t **stream)
void SkipChunk(uint8_t **stream)
{
int len;
len = ReadInt32 (stream);
int len = ReadInt32(stream);
*stream += len + (len & 1);
}