Experimental: Improve level load processing

- P_PreTicker ("defrosting") is dead. Levels now actually start on tic 0 instead of tic 2.
- Netxcmds are ran before G_Ticker, instead of after.
- All netxcmds are required to be processed before the level will finish loading (up to 5 gametics, to prevent any possible lock-up from malicious clients).
This commit is contained in:
Sally Coolatta 2023-03-22 10:59:29 -04:00
parent cd7d4f23c7
commit ffb65a3583
9 changed files with 122 additions and 152 deletions

View file

@ -383,11 +383,13 @@ static UINT8* D_GetTextcmd(tic_t tic, INT32 playernum)
return textcmdplayer->cmd;
}
static void ExtraDataTicker(void)
static boolean ExtraDataTicker(void)
{
boolean anyNetCmd = false;
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] || i == 0)
{
UINT8 *bufferstart = D_GetExistingTextcmd(gametic, i);
@ -407,6 +409,7 @@ static void ExtraDataTicker(void)
DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i));
(listnetxcmd[id])(&curpos, i);
DEBFILE("done\n");
anyNetCmd = true;
}
else
{
@ -421,12 +424,17 @@ static void ExtraDataTicker(void)
}
}
}
}
// If you are a client, you can safely forget the net commands for this tic
// If you are the server, you need to remember them until every client has been acknowledged,
// because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it
if (client)
{
D_FreeTextcmd(gametic);
}
return anyNetCmd;
}
static void D_Clearticcmd(tic_t tic)
@ -5002,7 +5010,7 @@ static void GetPackets(void)
if (netbuffer->packettype == PT_CLIENTJOIN && server)
{
if (!levelloading) // Otherwise just ignore
if (levelloading == false) // Otherwise just ignore
{
HandleConnect(node);
}
@ -5585,35 +5593,55 @@ boolean TryRunTics(tic_t realtics)
if (ticking)
{
// run the count * tics
while (neededtic > gametic)
{
// run the count * tics
while (neededtic > gametic)
boolean dontRun = false;
DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
ps_tictime = I_GetPreciseTime();
dontRun = ExtraDataTicker();
if (levelloading == false
|| gametic > levelstarttic + 5) // Don't lock-up if a malicious client is sending tons of netxcmds
{
DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
// During level load, we want to pause
// execution until we've finished loading
// all of the netxcmds in our buffer.
dontRun = false;
}
ps_tictime = I_GetPreciseTime();
G_Ticker((gametic % NEWTICRATERATIO) == 0);
if (Playing() && netgame && (gametic % TICRATE == 0))
if (dontRun == false)
{
if (levelloading == true)
{
Schedule_Run();
if (cv_livestudioaudience.value)
{
LiveStudioAudience();
}
P_PostLoadLevel();
}
ExtraDataTicker();
G_Ticker((gametic % NEWTICRATERATIO) == 0);
}
gametic++;
consistancy[gametic%BACKUPTICS] = Consistancy();
if (Playing() && netgame && (gametic % TICRATE == 0))
{
Schedule_Run();
ps_tictime = I_GetPreciseTime() - ps_tictime;
if (cv_livestudioaudience.value)
{
LiveStudioAudience();
}
}
// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
break;
gametic++;
consistancy[gametic % BACKUPTICS] = Consistancy();
ps_tictime = I_GetPreciseTime() - ps_tictime;
// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
if (client && gamestate == GS_LEVEL && leveltime > 1 && neededtic <= gametic + cv_netticbuffer.value)
{
break;
}
}
}

View file

@ -1589,30 +1589,30 @@ void G_StartTitleCard(void)
void G_PreLevelTitleCard(void)
{
#ifndef NOWIPE
tic_t strtime = I_GetTime();
tic_t endtime = strtime + (PRELEVELTIME*NEWTICRATERATIO);
tic_t nowtime = strtime;
tic_t lasttime = strtime;
while (nowtime < endtime)
{
// draw loop
ST_runTitleCard();
ST_preLevelTitleCardDrawer();
I_FinishUpdate(); // page flip or blit buffer
tic_t strtime = I_GetTime();
tic_t endtime = strtime + (PRELEVELTIME*NEWTICRATERATIO);
tic_t nowtime = strtime;
tic_t lasttime = strtime;
while (nowtime < endtime)
{
// draw loop
ST_runTitleCard();
ST_preLevelTitleCardDrawer();
I_FinishUpdate(); // page flip or blit buffer
NetKeepAlive(); // Prevent timeouts
#ifdef HWRENDER
if (moviemode && rendermode == render_opengl)
M_LegacySaveFrame();
if (moviemode && rendermode == render_opengl)
M_LegacySaveFrame();
#endif
while (!((nowtime = I_GetTime()) - lasttime))
{
while (!((nowtime = I_GetTime()) - lasttime))
{
I_Sleep(cv_sleep.value);
I_UpdateTime(cv_timescale.value);
}
lasttime = nowtime;
}
lasttime = nowtime;
}
#endif
}

View file

@ -96,7 +96,7 @@ static void K_SpawnDuelOnlyItems(void)
void K_TimerReset(void)
{
starttime = introtime = 3;
starttime = introtime = 0;
numbulbs = 1;
inDuel = rainbowstartavailable = false;
timelimitintics = extratimeintics = secretextratime = 0;

View file

@ -118,7 +118,6 @@ ENUM (STRING_HOOK);
//#define LUA_HUDHOOK(type) LUA_HookHUD(HUD_HOOK(type))
extern boolean hook_cmd_running;
extern int hook_defrosting;
void LUA_HookVoid(int hook);
void LUA_HookHUD(huddrawlist_h, int hook);

View file

@ -39,8 +39,6 @@
lua_State *gL = NULL;
int hook_defrosting;
// List of internal libraries to load from SRB2
static lua_CFunction liblist[] = {
LUA_EnumLib, // global metatable for enums
@ -288,9 +286,6 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"starttime")) {
lua_pushinteger(L, starttime);
return 1;
} else if (fastcmp(word,"defrosting")) {
lua_pushinteger(L, hook_defrosting);
return 1;
} else if (fastcmp(word,"curWeather")) {
lua_pushinteger(L, curWeather);
return 1;

View file

@ -10938,7 +10938,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
// Call action functions when the state is set
if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC))
{
if (levelloading)
if (levelloading == true)
{
// Cache actions in a linked list
// with function pointer, and

View file

@ -7064,6 +7064,8 @@ static void P_InitLevelSettings(void)
leveltime = 0;
modulothing = 0;
K_TimerReset();
// special stage tokens, emeralds, and ring total
runemeraldmanager = false;
emeraldspawndelay = 60*TICRATE;
@ -7618,8 +7620,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
sector_t *ss;
virtlump_t *encoreLump = NULL;
K_TimerReset();
levelloading = true;
// This is needed. Don't touch.
@ -7999,10 +7999,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
G_AddMapToBuffer(gamemap-1);
levelloading = false;
P_RunCachedActions();
P_MapEnd(); // tm.thing is no longer needed from this point onwards
// Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap...
@ -8012,56 +8008,84 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
{
// I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020
if (!(ultimatemode || netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking || marathonmode)
&& !usedCheats && cursaveslot > 0)
&& !usedCheats && cursaveslot > 0)
{
G_SaveGame((UINT32)cursaveslot, gamemap);
}
// If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted.
}
lastmaploaded = gamemap; // HAS to be set after saving!!
}
if (!fromnetsave) // uglier hack
{ // to make a newly loaded level start on the second frame.
if (!fromnetsave)
{
INT32 buf = gametic % BACKUPTICS;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
}
}
P_PreTicker(2);
for (i = 0; i <= r_splitscreen; i++)
{
postimgtype[i] = postimg_none;
}
if (marathonmode & MA_INGAME)
{
marathonmode |= MA_INIT;
}
P_MapStart(); // just in case MapLoad modifies tm.thing
ACS_RunLevelStartScripts();
LUA_HookInt(gamemap, HOOK(MapLoad));
P_MapEnd(); // just in case MapLoad modifies tm.thing
}
else
{
// Don't run P_PostLoadLevel when loading netgames.
levelloading = false;
}
// No render mode or reloading gamestate, stop here.
if (rendermode == render_none || reloadinggamestate)
return true;
if (rendermode != render_none && reloadinggamestate == false)
{
R_ResetViewInterpolation(0);
R_ResetViewInterpolation(0);
R_UpdateMobjInterpolators();
R_ResetViewInterpolation(0);
R_ResetViewInterpolation(0);
R_UpdateMobjInterpolators();
// Title card!
G_StartTitleCard();
// Title card!
G_StartTitleCard();
// Can the title card actually run, though?
if (!WipeStageTitle)
return true;
if (ranspecialwipe == 2)
return true;
// If so...
// but not if joining because the fade may time us out
if (!fromnetsave)
G_PreLevelTitleCard();
// Can the title card actually run, though?
if (WipeStageTitle && ranspecialwipe != 2 && fromnetsave == false)
{
G_PreLevelTitleCard();
}
}
return true;
}
void P_PostLoadLevel(void)
{
K_TimerInit();
P_RunCachedActions();
if (marathonmode & MA_INGAME)
{
marathonmode &= ~MA_INIT;
}
// We're now done loading the level.
levelloading = false;
}
//
// P_RunSOC
//

View file

@ -103,6 +103,7 @@ extern mapthing_t *mapthings;
void P_SetupLevelSky(const char *skytexname, boolean global);
void P_RespawnThings(void);
boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate);
void P_PostLoadLevel(void);
#ifdef HWRENDER
void HWR_LoadLevel(void);
#endif

View file

@ -832,86 +832,9 @@ void P_Ticker(boolean run)
if (demo.playback)
G_StoreRewindInfo();
if (leveltime == 2)
{
// The values needed to set this properly are not correct at map load,
// so we have to do it at the second tick instead...
K_TimerInit();
}
for (i = 0; i < MAXPLAYERS; i++)
{
G_CopyTiccmd(&players[i].oldcmd, &players[i].cmd, 1);
}
// Z_CheckMemCleanup();
}
// Abbreviated ticker for pre-loading, calls thinkers and assorted things
void P_PreTicker(INT32 frames)
{
INT32 i;
ticcmd_t temptic;
for (i = 0; i <= r_splitscreen; i++)
postimgtype[i] = postimg_none;
if (marathonmode & MA_INGAME)
marathonmode |= MA_INIT;
hook_defrosting = frames;
while (hook_defrosting)
{
P_MapStart();
R_UpdateMobjInterpolators();
LUA_HOOK(PreThinkFrame);
K_UpdateAllPlayerPositions();
// OK! Now that we got all of that sorted, players can think!
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
{
// stupid fucking cmd hack
// if it isn't for this, players can move in preticker time
// (and disrupt demo recording and other things !!)
memcpy(&temptic, &players[i].cmd, sizeof(ticcmd_t));
memset(&players[i].cmd, 0, sizeof(ticcmd_t));
P_PlayerThink(&players[i]);
memcpy(&players[i].cmd, &temptic, sizeof(ticcmd_t));
}
P_RunThinkers();
// Run any "after all the other thinkers" stuff
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
P_PlayerAfterThink(&players[i]);
LUA_HOOK(ThinkFrame);
// Run shield positioning
P_RunOverlays();
P_UpdateSpecials();
P_RespawnSpecials();
LUA_HOOK(PostThinkFrame);
R_UpdateLevelInterpolators();
R_UpdateViewInterpolation();
R_ResetViewInterpolation(0);
P_MapEnd();
hook_defrosting--;
}
if (marathonmode & MA_INGAME)
marathonmode &= ~MA_INIT;
}