Make empty-server restarts configurable in the mission menu
Add persistent last-disconnect and idle-interval controls with enabled/five-hour defaults. Apply saved settings immediately, reset idle time while occupied, and cover migration, routing, layout and restart behavior.
This commit is contained in:
@@ -8,7 +8,8 @@ def cpp(source):
|
||||
source = source.replace('protected ', '').replace('PlayerManager manager', 'PlayerManager* manager')
|
||||
for name in ('Replication', 'System', 'SCR_GameModeEndData', 'EGameOverTypes'):
|
||||
source = source.replace(name + '.', name + '::')
|
||||
for name in ('GetGame()', 'manager', 'm_Performance'):
|
||||
source = source.replace('string.Format', 'EnforceString::Format')
|
||||
for name in ('GetGame()', 'manager', 'm_Performance', 'm_Settings'):
|
||||
source = source.replace(name + '.', name + '->')
|
||||
return source
|
||||
|
||||
@@ -28,9 +29,17 @@ struct Game {PlayerManager* manager=nullptr;PlayerManager* GetPlayerManager(){re
|
||||
Game* GetGame(){return &game;}
|
||||
template<class... T> void PrintFormat(const char*,T...){}
|
||||
struct Performance {int starts=0,ends=0;int BeginTick(){return ++starts;}void EndTick(int){ends++;}};
|
||||
struct EnforceString {
|
||||
static std::string Format(std::string pattern,int value){pattern.replace(pattern.find("%1"),2,std::to_string(value));return pattern;}
|
||||
};
|
||||
struct Settings {
|
||||
int restart=1,hours=5;
|
||||
int Get(std::string key){if(key=="restartOnEmpty")return restart;assert(key=="emptyRestartHours");return hours;}
|
||||
};
|
||||
struct Mode {
|
||||
Settings settings;Settings* m_Settings=&settings;
|
||||
bool m_Stopped=false,m_NativeEndHandled=false,m_HadConnectedPlayers=false,m_EmptyRestartRequested=false;
|
||||
int m_EmptyMissionStartedAt=System::now,phase=1,endCalls=0,measuredTicks=0;
|
||||
int m_EmptySince=System::now,phase=1,endCalls=0,measuredTicks=0;
|
||||
std::string m_EndReason;
|
||||
Performance performance;Performance* m_Performance=&performance;
|
||||
bool Active(){return !m_Stopped&&!m_NativeEndHandled&&phase>0&&phase<5;}
|
||||
@@ -55,7 +64,7 @@ int main(){
|
||||
int before=f.mode.measuredTicks;
|
||||
f.tick(start+fiveHours,0);
|
||||
assert(f.mode.endCalls==1&&f.mode.measuredTicks==before);
|
||||
assert(f.mode.m_EndReason=="The server has been empty for five hours. Restarting the mission.");
|
||||
assert(f.mode.m_EndReason=="The server has been empty for 5 hours. Restarting the mission.");
|
||||
f.tick(start+fiveHours+1000,0);assert(f.mode.endCalls==1);
|
||||
}
|
||||
// All native connections block a scheduled restart, even without a spawned life.
|
||||
@@ -82,21 +91,54 @@ int main(){
|
||||
f.tick(disconnected?1000:fiveHours,0);
|
||||
assert(f.mode.endCalls==1&&f.mode.measuredTicks==0&&f.mode.performance.starts==0);
|
||||
}
|
||||
// Client, stopped, already-ended and unavailable population must never reload.
|
||||
for(int fault=0;fault<4;fault++){
|
||||
// Independent controls: no idle restart at any age when both are disabled.
|
||||
System::now=0;
|
||||
{Fixture f;f.mode.settings.restart=0;f.mode.settings.hours=0;
|
||||
f.tick(0,1);f.mode.ObservePlayerDisconnect();f.tick(1000,0);f.tick(169*3600000,0);
|
||||
assert(f.mode.endCalls==0);}
|
||||
// Last-disconnect restart remains available when the periodic interval is off.
|
||||
System::now=0;
|
||||
{Fixture f;f.mode.settings.hours=0;f.tick(1000,1);f.tick(2000,0);assert(f.mode.endCalls==1);}
|
||||
// With immediate restart disabled, the full configured interval starts at
|
||||
// the last disconnect even if the mission has been occupied for many hours.
|
||||
for(int hours:{1,5,168}){
|
||||
System::now=0;Fixture f;f.mode.settings.restart=0;f.mode.settings.hours=hours;
|
||||
f.tick(20*3600000,1);System::now+=100;f.mode.ObservePlayerDisconnect();int since=System::now;
|
||||
f.tick(since,0);f.tick(since+hours*3600000-1,0);assert(f.mode.endCalls==0);
|
||||
f.tick(since+hours*3600000,0);assert(f.mode.endCalls==1);
|
||||
}
|
||||
// A new connection resets the idle interval, including a short unaudited visit.
|
||||
System::now=0;
|
||||
{Fixture f;f.mode.settings.restart=0;f.mode.settings.hours=1;
|
||||
f.tick(3599999,1);System::now=3600000;f.mode.ObservePlayerDisconnect();
|
||||
f.tick(7199999,0);assert(f.mode.endCalls==0);f.tick(7200000,0);assert(f.mode.endCalls==1);}
|
||||
// Saved changes take effect on subsequent checks; disabling/enabling the
|
||||
// interval does not forget elapsed empty time, and never restarts an occupied game.
|
||||
System::now=0;
|
||||
{Fixture f;f.mode.settings.restart=0;f.tick(2*3600000,0);assert(f.mode.endCalls==0);
|
||||
f.mode.settings.hours=0;f.tick(6*3600000,0);assert(f.mode.endCalls==0);
|
||||
f.mode.settings.hours=1;f.tick(6*3600000+1,1);assert(f.mode.endCalls==0);
|
||||
f.tick(6*3600000+2,0);assert(f.mode.endCalls==0);
|
||||
f.mode.settings.restart=1;f.tick(6*3600000+3,0);assert(f.mode.endCalls==1);}
|
||||
System::now=0;
|
||||
{Fixture f;f.mode.settings.restart=0;f.tick(2*3600000,0);f.mode.settings.hours=1;
|
||||
f.tick(2*3600000+1,0);assert(f.mode.endCalls==1);}
|
||||
// Client, stopped, already-ended and unavailable population/settings never reload.
|
||||
for(int fault=0;fault<5;fault++){
|
||||
System::now=0;Fixture f;
|
||||
if(fault==0)Replication::server=false;
|
||||
if(fault==1)f.mode.m_Stopped=true;
|
||||
if(fault==2)f.mode.m_NativeEndHandled=true;
|
||||
if(fault==3)game.manager=nullptr;
|
||||
if(fault==4)f.mode.m_Settings=nullptr;
|
||||
f.mode.ObservePlayerDisconnect();f.tick(fiveHours,0);assert(f.mode.endCalls==0);
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
init = body(MODE, 'protected override void EOnInit(')
|
||||
assert init.index('if (!Replication.IsServer()) return;') < init.index('m_EmptyMissionStartedAt = System.GetTickCount();')
|
||||
assert init.index('m_EmptyMissionStartedAt = System.GetTickCount();') < init.index('CallLater(ServerTick, 1000, true)')
|
||||
assert init.index('if (!Replication.IsServer()) return;') < init.index('m_EmptySince = System.GetTickCount();')
|
||||
assert init.index('m_EmptySince = System.GetTickCount();') < init.index('CallLater(ServerTick, 1000, true)')
|
||||
assert 'mode.ObservePlayerDisconnect();' in body(SPAWN, 'override void OnPlayerDisconnected_S(')
|
||||
assert 'Remove(ServerTick)' in body(MODE, 'protected void StopServices(')
|
||||
print('PASS: last disconnect, pending admission/spectator population, five-hour boundary, repeated empty rounds, preparation, authority and duplicate-end guards')
|
||||
print('PASS: configurable independent restarts, idle clock reset and live setting changes; last disconnect, pending admission/spectator population, five-hour boundary, repeated empty rounds, preparation, authority and duplicate-end guards')
|
||||
|
||||
Reference in New Issue
Block a user