Wait for client inventory readiness before player admission
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Execute production inventory manifest and client containment checks."""
|
||||
from policy_harness import ROOT, body, run
|
||||
inventory=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneInventory.c'
|
||||
scene=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c'
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('static ','')
|
||||
s=s.replace('out array<IEntity> ', 'Items& ').replace('array<IEntity> ', 'Items ')
|
||||
s=s.replace('foreach (IEntity root : roots)', 'for (IEntity root : roots)').replace('foreach (IEntity item : contents)', 'for (IEntity item : contents)')
|
||||
s=s.replace('InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(root.FindComponent(InventoryStorageManagerComponent));', 'Inventory* inventory = root->inventory;')
|
||||
s=s.replace('InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.GetEntity().FindComponent(InventoryStorageManagerComponent));', 'Inventory* inventory = owner->GetEntity()->inventory;')
|
||||
s=s.replace('RplComponent owner = RplComponent.Cast(Replication.FindItem(ownerId));', 'RplComponent* owner = Replication::FindItem(ownerId);')
|
||||
s=s.replace('RplComponent item = RplComponent.Cast(Replication.FindItem(m_ERRClientStockItems[i]));', 'RplComponent* item = Replication::FindItem(m_ERRClientStockItems[i]);')
|
||||
s=s.replace('owner.GetEntity()', 'owner->GetEntity()').replace('item.GetEntity()', 'item->GetEntity()')
|
||||
s=s.replace('RplId.Invalid()', '-1').replace('ERR_Inventory.Items(', 'InventoryItems(')
|
||||
s=s.replace('Replication.IsServer()', 'Replication::server').replace('GetGame().GetWorld().GetWorldTime()', 'now')
|
||||
s=s.replace('ERR_SpawnSceneInventory.Gather(', 'Gather(').replace('ERR_SpawnSceneInventory.Same(', 'Same(')
|
||||
return s
|
||||
methods='\n'.join(cpp(body(inventory,key)) for key in ('static bool Gather(', 'static bool Same('))
|
||||
methods+='\n'+cpp(body(scene,'protected bool ERR_ClientStockReady('))
|
||||
methods+='\n'+cpp(body(scene,'protected void ERR_RefreshSceneInventory('))
|
||||
methods+='\n'+cpp(body(scene,'bool ERR_TakeScenePreload('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
template<class T>struct List:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;
|
||||
int Count()const{return this->size();}void Insert(T x){this->push_back(x);}bool Contains(T x)const{return std::find(this->begin(),this->end(),x)!=this->end();}
|
||||
int Find(T x)const{auto i=std::find(this->begin(),this->end(),x);return i==this->end()?-1:i-this->begin();}};
|
||||
struct Entity;using IEntity=Entity*;using Items=List<IEntity>;using RplId=int;
|
||||
struct Inventory{Items items;};struct Entity{Inventory* inventory=nullptr;};
|
||||
void InventoryItems(Inventory* inv,Items& items){items={};for(auto x:inv->items)if(x&&!items.Contains(x))items.Insert(x);}
|
||||
struct RplComponent{IEntity entity=nullptr;IEntity GetEntity(){return entity;}};
|
||||
namespace Replication{bool server=true;std::map<int,RplComponent*> nodes;RplComponent* FindItem(int id){auto it=nodes.find(id);return it==nodes.end()?nullptr:it->second;}}
|
||||
template<typename... T>void PrintFormat(const char*,T...){}
|
||||
struct Probe{
|
||||
List<int> m_ERRClientStockOwners,m_ERRClientStockItems;
|
||||
bool m_ERRScenePending=true,m_ERRSceneTimedOut=false,m_ERRSceneAcknowledged=false,m_ERRScenePreloadRequired=true,preloading=false;
|
||||
int publishes=0,waits=0,m_ERRSceneToken=7;
|
||||
float now=0,m_ERRSceneInventoryPoll=0,m_ERRSceneDeadline=60000,m_ERRSceneStartedAt=0;
|
||||
Items m_ERRSceneRoots,m_ERRSceneStockOwners,m_ERRSceneStockItems;
|
||||
void ERR_ReportStockWait(int,int,int){waits++;}int GetPlayerId(){return 1;}
|
||||
bool IsPreloading(){return preloading;}bool ERR_SpawnSceneReady(){return m_ERRSceneAcknowledged&&!m_ERRSceneTimedOut;}
|
||||
void ERR_PublishSpawnScene(Items owners,Items items,bool valid){publishes++;m_ERRSceneToken++;m_ERRSceneAcknowledged=false;m_ERRSceneStockOwners=owners;m_ERRSceneStockItems=items;m_ERRSceneTimedOut=!valid;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
int main(){
|
||||
Inventory inv;Entity crate{&inv},gun,mag,map;inv.items={&gun,&mag,&map,&gun,nullptr};
|
||||
Probe p;Items owners,items;assert(p.Gather({&crate,&crate,nullptr},owners,items)&&items.Count()==3);
|
||||
assert((items==Items{&gun,&mag,&map})&&(owners==Items{&crate,&crate,&crate}));
|
||||
assert(p.Same(owners,items,{&crate,&crate,&crate},{&map,&mag,&gun}));
|
||||
Entity other;assert(!p.Same(owners,items,{&other,&crate,&crate},items));
|
||||
assert(!p.Same(owners,items,{&crate,&crate},{&gun,&mag}));
|
||||
std::vector<Entity> many(257);Inventory full;for(auto& e:many)full.items.Insert(&e);Entity huge{&full};
|
||||
assert(!p.Gather({&huge},owners,items)&&items.Count()==256&&owners.Count()==256);
|
||||
// Receiving every replication entity is insufficient: inventory links may
|
||||
// arrive later, and a wrong crate with the same item count is insufficient.
|
||||
RplComponent cr{&crate},gr{&gun},mr{&mag},pr{&map};
|
||||
Replication::nodes={{10,&cr},{11,&gr},{12,&mr},{13,&pr}};
|
||||
p.m_ERRClientStockOwners={10,10,10};p.m_ERRClientStockItems={11,12,13};
|
||||
inv.items={};for(int i=0;i<20;i++)assert(!p.ERR_ClientStockReady());
|
||||
inv.items={&gun,&mag};assert(!p.ERR_ClientStockReady());
|
||||
inv.items={&gun,&mag,&other};assert(!p.ERR_ClientStockReady());
|
||||
inv.items={&gun,&mag,&map};assert(p.ERR_ClientStockReady());
|
||||
cr.entity=nullptr;assert(!p.ERR_ClientStockReady());cr.entity=&crate;
|
||||
crate.inventory=nullptr;assert(!p.ERR_ClientStockReady());crate.inventory=&inv;
|
||||
mr.entity=nullptr;assert(!p.ERR_ClientStockReady());mr.entity=&mag;
|
||||
p.m_ERRClientStockOwners={10};assert(!p.ERR_ClientStockReady());
|
||||
// Refresh a consumed/relocated stock snapshot without extending its timeout.
|
||||
p.m_ERRSceneRoots={&crate};assert(p.Gather(p.m_ERRSceneRoots,p.m_ERRSceneStockOwners,p.m_ERRSceneStockItems));
|
||||
p.ERR_RefreshSceneInventory();assert(p.publishes==0);
|
||||
p.m_ERRSceneAcknowledged=true;inv.items={&gun};p.now=499;p.ERR_RefreshSceneInventory();assert(p.publishes==0);
|
||||
p.now=500;p.ERR_RefreshSceneInventory();assert(p.publishes==1&&p.m_ERRSceneToken==8&&!p.m_ERRSceneAcknowledged&&p.m_ERRSceneDeadline==60000);
|
||||
assert((p.m_ERRSceneStockItems==Items{&gun}));
|
||||
p.m_ERRSceneStockItems={&gun,&mag};p.now=1000;Replication::server=false;p.ERR_RefreshSceneInventory();assert(p.publishes==1);Replication::server=true;
|
||||
p.m_ERRSceneTimedOut=true;p.ERR_RefreshSceneInventory();assert(p.publishes==1);p.m_ERRSceneTimedOut=false;
|
||||
p.m_ERRScenePending=false;p.ERR_RefreshSceneInventory();assert(p.publishes==1);
|
||||
// Preload cannot start before inventory acknowledgement, starts once, and
|
||||
// waits for an earlier native pass before consuming a refreshed request.
|
||||
assert(!p.ERR_TakeScenePreload());p.m_ERRSceneAcknowledged=true;
|
||||
assert(p.ERR_TakeScenePreload()&&!p.ERR_TakeScenePreload());
|
||||
p.m_ERRScenePreloadRequired=true;p.preloading=true;assert(!p.ERR_TakeScenePreload());
|
||||
p.preloading=false;assert(p.ERR_TakeScenePreload());
|
||||
}
|
||||
''')
|
||||
print('PASS: actual inventory manifest, delayed/missing/wrong-owner item links, finite cap, consumed-stock refresh and preload ordering')
|
||||
Reference in New Issue
Block a user