Wait for client inventory readiness before player admission

This commit is contained in:
2026-09-20 17:31:33 +02:00
parent cab998fb64
commit 1201f9666f
8 changed files with 362 additions and 25 deletions
+39 -6
View File
@@ -30,7 +30,8 @@ template<typename... T>void PrintFormat(const char*,T...){}
struct Probe{
int m_ERRSceneToken=4,m_ERRClientSceneToken=4,acks=0,lastAck=0;
bool m_ERRScenePending=false,m_ERRSceneAcknowledged=false,m_ERRSceneTimedOut=false,m_ERRClientScenePending=false;
float m_ERRSceneDeadline=1000;Nodes m_ERRClientSceneNodes;
float m_ERRSceneDeadline=1000,m_ERRSceneStartedAt=0;Nodes m_ERRClientSceneNodes,m_ERRClientStockOwners,m_ERRClientStockItems;
bool stockReady=true;bool ERR_ClientStockReady(){return stockReady;}void ERR_RefreshSceneInventory(){}
int GetPlayerId(){return 1;}void SendAck(int token){acks++;lastAck=token;}
'''+methods+r'''
};
@@ -49,7 +50,8 @@ int main(){
for(int i=0;i<100;i++){client.ERR_CheckClientSpawnScene();}assert(client.acks==0&&client.m_ERRClientScenePending);
Replication::items[22]=&wall;client.ERR_CheckClientSpawnScene();assert(client.acks==0);
gate.entity=false;Replication::items[33]=&gate;client.ERR_CheckClientSpawnScene();assert(client.acks==0);
gate.entity=true;client.ERR_CheckClientSpawnScene();assert(client.acks==1&&client.lastAck==4&&!client.m_ERRClientScenePending&&client.m_ERRClientSceneNodes.empty());
gate.entity=true;client.stockReady=false;client.ERR_CheckClientSpawnScene();assert(client.acks==0&&client.m_ERRClientScenePending);
client.stockReady=true;client.ERR_CheckClientSpawnScene();assert(client.acks==1&&client.lastAck==4&&!client.m_ERRClientScenePending&&client.m_ERRClientSceneNodes.empty());
client.ERR_CheckClientSpawnScene();assert(client.acks==1);
client.m_ERRClientScenePending=true;client.m_ERRClientSceneToken=5;client.m_ERRClientSceneNodes={99};
client.ERR_CancelClientSpawnScene(4);assert(client.m_ERRClientScenePending);
@@ -68,11 +70,11 @@ for old, new in [('override ', ''), ('SCR_SpawnRequestComponent requestComponent
run(r'''
#include <cassert>
enum class SCR_ESpawnResult{OK,SPAWN_NOT_ALLOWED,CANNOT_ASSIGN};
struct Request{bool ready=false,mayFinish=false;int ends=0;bool ERR_SpawnSceneReady(){return ready;}bool ERR_SpawnSceneMayFinish(){return mayFinish;}void ERR_EndSpawnScene(){ends++;}};
struct Request{bool ready=false,mayFinish=false;int ends=0;bool ERR_SpawnSceneReady(){return ready;}bool ERR_SpawnSceneMayFinish(){return mayFinish;}bool preloadRequired=true;bool ERR_TakeScenePreload(){if(!ready||!preloadRequired)return false;preloadRequired=false;return true;}void ERR_EndSpawnScene(){ends++;}};
struct Data{};struct Actor{};struct Ticket{};
struct Mode{static bool current;static bool Get(){return current;}};bool Mode::current=true;
struct Logic{Ticket ticket;bool hasTicket=true,allowed=true;Ticket* TakeSpawnTicket(Request*,Data*,Actor*){return hasTicket?&ticket:nullptr;}bool MayFinalizeSpawn(Ticket*){return allowed;}};
struct Native{bool preloadReady=false;int assignments=0;SCR_ESpawnResult result=SCR_ESpawnResult::OK;bool CanFinalize_S(Request*,Data*,Actor*){return preloadReady;}SCR_ESpawnResult FinalizeRequest_S(Request*,Data*,Actor*){assignments++;return result;}};
struct Native{bool preloadReady=false;int assignments=0,preloadStarts=0;void OnFinalizeBegin_S(Request*,Data*,Actor*){preloadStarts++;preloadReady=false;}SCR_ESpawnResult result=SCR_ESpawnResult::OK;bool CanFinalize_S(Request*,Data*,Actor*){return preloadReady;}SCR_ESpawnResult FinalizeRequest_S(Request*,Data*,Actor*){assignments++;return result;}};
struct Handler:Native{Logic storage;Logic* logic=&storage;int rejects=0;Logic* ERR_AdmissionLogic(){return logic;}void ERR_RejectOwnedActor(Request*,Actor*,Data*){rejects++;}
'''+methods+r'''
};
@@ -83,11 +85,42 @@ int main(){
h.preloadReady=true;assert(h.CanFinalize_S(&request,&data,&actor));
// Timeout can end the pending request, but cannot assign an actor.
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED&&h.assignments==0&&h.rejects==1&&request.ends==1);
request.ready=true;h.storage.allowed=false;
request.ready=true;assert(!h.CanFinalize_S(&request,&data,&actor)&&h.preloadStarts==1);
h.preloadReady=true;assert(h.CanFinalize_S(&request,&data,&actor)&&h.preloadStarts==1);
h.storage.allowed=false;
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED&&h.assignments==0&&h.rejects==2);
h.storage.allowed=true;assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::OK&&h.assignments==1&&request.ends==3);
h.storage.hasTicket=false;assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED&&h.assignments==1&&h.rejects==2);
Mode::current=false;h.logic=nullptr;assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::OK&&h.assignments==2);
}
''')
print('PASS: delayed wall/gate replication withholds assignment; native preload, identity checks, stale replies, cancellation and timeout remain enforced')
cleanup = body(source, 'void ERR_EndSpawnScene(')
for old, new in [('Replication.IsServer()', 'Replication::server'),
('foreach (IEntity entity :', 'for (int entity :'),
('GetPlayerController().GetControlledEntity()', 'GetPlayerController()->GetControlledEntity()'),
('Rpc(ERR_CancelClientSpawnScene, m_ERRSceneToken)', 'Cancel(m_ERRSceneToken)')]:
cleanup = cleanup.replace(old, new)
run(r'''
#include <cassert>
#include <vector>
namespace Replication{bool server=true;}
struct Items:std::vector<int>{using std::vector<int>::vector;void Clear(){clear();}};
struct Controller{int GetControlledEntity(){return 42;}};
struct Probe{
Items m_ERRSceneEntities{1,2},m_ERRSceneRoots,m_ERRSceneStockOwners,m_ERRSceneStockItems;
bool m_ERRSceneObserver=true,m_ERRScenePending=true,m_ERRSceneAcknowledged=true,m_ERRSceneTimedOut=true,m_ERRScenePreloadRequired=true;
int m_ERRSceneToken=7,restored=0,cancelled=0,observed=0;Controller controller;bool connected=true;
Controller* GetPlayerController(){return connected?&controller:nullptr;}
void ERR_StreamScene(int,bool normal){assert(normal);restored++;}
void UpdateObserverMP_S(int entity){observed=entity;}void Cancel(int token){assert(token==7);cancelled++;}
'''+cleanup+r'''
};
int main(){
Probe p;Replication::server=false;p.ERR_EndSpawnScene();assert(p.restored==0&&p.m_ERRScenePending);
Replication::server=true;p.ERR_EndSpawnScene();assert(p.restored==2&&p.cancelled==1&&p.observed==42);
assert(p.m_ERRSceneEntities.empty()&&!p.m_ERRSceneObserver&&!p.m_ERRScenePending&&!p.m_ERRSceneAcknowledged&&!p.m_ERRSceneTimedOut&&!p.m_ERRScenePreloadRequired);
p.ERR_EndSpawnScene();assert(p.restored==2&&p.cancelled==1);
Probe disconnected;disconnected.connected=false;disconnected.ERR_EndSpawnScene();assert(disconnected.observed==0&&!disconnected.m_ERRSceneObserver);
}
''')
print('PASS: delayed scene/inventory arrival, deferred preload, identity checks, stale replies, timeout and observer cleanup')