Recheck storage slots after spawn preload and streaming handoff
This commit is contained in:
@@ -13,6 +13,9 @@ modded class SCR_SpawnRequestComponent
|
||||
protected float m_ERRSceneInventoryPoll;
|
||||
protected bool m_ERRScenePreloadRequired;
|
||||
protected bool m_ERRSceneObserver;
|
||||
protected vector m_ERRScenePosition;
|
||||
protected bool m_ERRSceneReleased;
|
||||
protected bool m_ERRSceneVerified;
|
||||
protected ref array<IEntity> m_ERRSceneRoots = {};
|
||||
protected ref array<IEntity> m_ERRSceneStockOwners = {};
|
||||
protected ref array<IEntity> m_ERRSceneStockItems = {};
|
||||
@@ -23,6 +26,8 @@ modded class SCR_SpawnRequestComponent
|
||||
protected ref array<RplId> m_ERRClientStockOwners = {};
|
||||
protected ref array<RplId> m_ERRClientStockItems = {};
|
||||
protected float m_ERRClientStockReportAt;
|
||||
protected bool m_ERRClientSceneFinal;
|
||||
protected float m_ERRClientSceneStableSince = -1;
|
||||
|
||||
protected void ERR_StreamScene(IEntity entity, bool normalStreaming)
|
||||
{
|
||||
@@ -37,6 +42,7 @@ modded class SCR_SpawnRequestComponent
|
||||
{
|
||||
if (!Replication.IsServer()) return;
|
||||
ERR_EndSpawnScene();
|
||||
m_ERRScenePosition = position;
|
||||
// Native StartSpawnPreload normally installs this observer. Keep streaming
|
||||
// the requested position while we defer asset preload until inventory arrives.
|
||||
ObserversSystem observers = GetObserversSystem();
|
||||
@@ -81,6 +87,8 @@ modded class SCR_SpawnRequestComponent
|
||||
m_ERRSceneEntities.Clear();
|
||||
m_ERRSceneToken++;
|
||||
m_ERRSceneAcknowledged = false;
|
||||
m_ERRSceneReleased = false;
|
||||
m_ERRSceneVerified = false;
|
||||
m_ERRScenePreloadRequired = true;
|
||||
m_ERRSceneInventoryPoll = GetGame().GetWorld().GetWorldTime() + 500;
|
||||
m_ERRSceneStockOwners.Copy(owners);
|
||||
@@ -129,11 +137,33 @@ modded class SCR_SpawnRequestComponent
|
||||
{
|
||||
return m_ERRScenePending && m_ERRSceneAcknowledged && !m_ERRSceneTimedOut;
|
||||
}
|
||||
bool ERR_SpawnSceneVerified()
|
||||
{
|
||||
return ERR_SpawnSceneReady() && m_ERRSceneVerified;
|
||||
}
|
||||
// Called only after native preload completes. Recheck under normal streaming,
|
||||
// with the observer still at the spawn point, before handing over control.
|
||||
bool ERR_VerifySceneRelease()
|
||||
{
|
||||
if (m_ERRSceneTimedOut) return true;
|
||||
if (!ERR_SpawnSceneReady() || m_ERRScenePreloadRequired || IsPreloading()) return false;
|
||||
if (!m_ERRSceneReleased)
|
||||
{
|
||||
ObserversSystem observers = GetObserversSystem();
|
||||
if (observers && GetPlayerController())
|
||||
observers.InsertObserverMP(GetPlayerController().GetRplIdentity(), m_ERRScenePosition[0], m_ERRScenePosition[2], null);
|
||||
foreach (IEntity entity : m_ERRSceneEntities) ERR_StreamScene(entity, true);
|
||||
m_ERRSceneReleased = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 preload finished; verifying storage after stream release", GetPlayerId(), m_ERRSceneToken);
|
||||
Rpc(ERR_ConfirmClientSpawnScene, m_ERRSceneToken);
|
||||
}
|
||||
return m_ERRSceneVerified;
|
||||
}
|
||||
bool ERR_SpawnSceneMayFinish()
|
||||
{
|
||||
if (!m_ERRScenePending) return true;
|
||||
ERR_RefreshSceneInventory();
|
||||
if (!m_ERRSceneAcknowledged && !m_ERRSceneTimedOut && GetGame().GetWorld().GetWorldTime() >= m_ERRSceneDeadline)
|
||||
if (!m_ERRSceneVerified && !m_ERRSceneTimedOut && GetGame().GetWorld().GetWorldTime() >= m_ERRSceneDeadline)
|
||||
{
|
||||
m_ERRSceneTimedOut = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 timed out; refusing assignment and retrying", GetPlayerId(), m_ERRSceneToken);
|
||||
@@ -157,6 +187,8 @@ modded class SCR_SpawnRequestComponent
|
||||
m_ERRSceneAcknowledged = false;
|
||||
m_ERRSceneTimedOut = false;
|
||||
m_ERRScenePreloadRequired = false;
|
||||
m_ERRSceneReleased = false;
|
||||
m_ERRSceneVerified = false;
|
||||
}
|
||||
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||
@@ -192,7 +224,7 @@ modded class SCR_SpawnRequestComponent
|
||||
previousOwner = ownerId;
|
||||
}
|
||||
RplComponent item = RplComponent.Cast(Replication.FindItem(m_ERRClientStockItems[i]));
|
||||
if (!item || !item.GetEntity() || !contents.Contains(item.GetEntity()))
|
||||
if (!item || !item.GetEntity() || !contents.Contains(item.GetEntity()) || !ERR_SpawnSceneInventory.InStorage(item.GetEntity()))
|
||||
{
|
||||
ERR_ReportStockWait(ownerId, m_ERRClientStockItems[i], contents.Count());
|
||||
return false;
|
||||
@@ -205,33 +237,58 @@ modded class SCR_SpawnRequestComponent
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (now < m_ERRClientStockReportAt) return;
|
||||
m_ERRClientStockReportAt = now + 5000;
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 waiting for inventory owner=%2 item=%3 visible=%4", m_ERRClientSceneToken, owner, item, visible);
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 final=%2 waiting for storage owner=%3 item=%4 managerItems=%5", m_ERRClientSceneToken, m_ERRClientSceneFinal, owner, item, visible);
|
||||
}
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||
protected void ERR_ConfirmClientSpawnScene(int token)
|
||||
{
|
||||
if (token != m_ERRClientSceneToken || m_ERRClientSceneNodes.IsEmpty()) return;
|
||||
m_ERRClientSceneFinal = true;
|
||||
m_ERRClientScenePending = true;
|
||||
m_ERRClientSceneStableSince = -1;
|
||||
GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene);
|
||||
GetGame().GetCallqueue().CallLater(ERR_CheckClientSpawnScene, 100, true);
|
||||
}
|
||||
protected void ERR_CheckClientSpawnScene()
|
||||
{
|
||||
if (!m_ERRClientScenePending || !ERR_GameMode.Get()) { ERR_ClearClientSpawnScene(); return; }
|
||||
if (!ERR_GameMode.Get()) { ERR_ClearClientSpawnScene(); return; }
|
||||
if (!m_ERRClientScenePending) return;
|
||||
foreach (RplId id : m_ERRClientSceneNodes)
|
||||
{
|
||||
RplComponent component = RplComponent.Cast(Replication.FindItem(id));
|
||||
if (!component || !component.GetEntity()) return;
|
||||
if (!component || !component.GetEntity()) { m_ERRClientSceneStableSince = -1; return; }
|
||||
}
|
||||
if (!ERR_ClientStockReady()) { m_ERRClientSceneStableSince = -1; return; }
|
||||
if (m_ERRClientSceneFinal)
|
||||
{
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (m_ERRClientSceneStableSince < 0) m_ERRClientSceneStableSince = now;
|
||||
if (now - m_ERRClientSceneStableSince < 1000) return;
|
||||
}
|
||||
if (!ERR_ClientStockReady()) return;
|
||||
int token = m_ERRClientSceneToken;
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 all %2 objects and %3 inventory items ready", token, m_ERRClientSceneNodes.Count(), m_ERRClientStockItems.Count());
|
||||
ERR_ClearClientSpawnScene();
|
||||
Rpc(ERR_AcknowledgeSpawnScene, token);
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 final=%2 all %3 objects and %4 storage items ready", token, m_ERRClientSceneFinal, m_ERRClientSceneNodes.Count(), m_ERRClientStockItems.Count());
|
||||
GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene);
|
||||
m_ERRClientScenePending = false;
|
||||
Rpc(ERR_AcknowledgeSpawnScene, token, m_ERRClientSceneFinal);
|
||||
}
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
|
||||
protected void ERR_AcknowledgeSpawnScene(int token)
|
||||
protected void ERR_AcknowledgeSpawnScene(int token, bool finalCheck)
|
||||
{
|
||||
if (!Replication.IsServer() || !m_ERRScenePending || m_ERRSceneTimedOut || token != m_ERRSceneToken) return;
|
||||
m_ERRSceneAcknowledged = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 inventory acknowledged after %3ms", GetPlayerId(), token, GetGame().GetWorld().GetWorldTime() - m_ERRSceneStartedAt);
|
||||
if (finalCheck)
|
||||
{
|
||||
if (!m_ERRSceneReleased || !m_ERRSceneAcknowledged) return;
|
||||
m_ERRSceneVerified = true;
|
||||
}
|
||||
else m_ERRSceneAcknowledged = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 final=%3 storage acknowledged after %4ms", GetPlayerId(), token, finalCheck, GetGame().GetWorld().GetWorldTime() - m_ERRSceneStartedAt);
|
||||
}
|
||||
protected void ERR_ClearClientSpawnScene()
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene);
|
||||
m_ERRClientScenePending = false;
|
||||
m_ERRClientSceneFinal = false;
|
||||
m_ERRClientSceneStableSince = -1;
|
||||
m_ERRClientSceneNodes.Clear();
|
||||
m_ERRClientStockOwners.Clear();
|
||||
m_ERRClientStockItems.Clear();
|
||||
|
||||
@@ -56,6 +56,8 @@ modded class SCR_FreeSpawnHandlerComponent
|
||||
{
|
||||
if (!requestComponent.ERR_SpawnSceneMayFinish()) return false;
|
||||
if (requestComponent.ERR_TakeScenePreload()) super.OnFinalizeBegin_S(requestComponent, data, entity);
|
||||
if (!super.CanFinalize_S(requestComponent, data, entity)) return false;
|
||||
return requestComponent.ERR_VerifySceneRelease();
|
||||
}
|
||||
return super.CanFinalize_S(requestComponent, data, entity);
|
||||
}
|
||||
@@ -95,7 +97,7 @@ modded class SCR_FreeSpawnHandlerComponent
|
||||
if (logic) ticket = logic.TakeSpawnTicket(requestComponent, data, entity);
|
||||
// No ticket means no ownership proof: reject without deleting arbitrary input.
|
||||
if (!ticket) return SCR_ESpawnResult.SPAWN_NOT_ALLOWED;
|
||||
if (!requestComponent.ERR_SpawnSceneReady() || !logic.MayFinalizeSpawn(ticket))
|
||||
if (!requestComponent.ERR_SpawnSceneVerified() || !logic.MayFinalizeSpawn(ticket))
|
||||
{
|
||||
requestComponent.ERR_EndSpawnScene();
|
||||
ERR_RejectOwnedActor(requestComponent, entity, data);
|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
// prisoner may already have taken loot when a late arrival joins the round.
|
||||
class ERR_SpawnSceneInventory
|
||||
{
|
||||
// Manager membership alone does not establish that the storage UI can read
|
||||
// the item. Check both directions of the replicated slot and the UI's GetAll.
|
||||
static bool InStorage(IEntity entity)
|
||||
{
|
||||
if (!entity) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (!item) return false;
|
||||
InventoryStorageSlot slot = item.GetParentSlot();
|
||||
if (!slot) return false;
|
||||
BaseInventoryStorageComponent storage = slot.GetStorage();
|
||||
if (!storage || storage.FindItemSlot(entity) != slot || storage.Get(slot.GetID()) != entity) return false;
|
||||
array<IEntity> visible = {};
|
||||
storage.GetAll(visible);
|
||||
return visible.Contains(entity);
|
||||
}
|
||||
|
||||
static bool Gather(array<IEntity> roots, out array<IEntity> owners, out array<IEntity> items)
|
||||
{
|
||||
owners = {}; items = {};
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
# Mission preparation presentation
|
||||
|
||||
## Storage slots and post-preload check (0.0.8 candidate)
|
||||
|
||||
The user tested published 0.0.7 on RKS #2 and still opened an empty prison crate
|
||||
before its pistol appeared. The actual client log at 17:34 on September 20
|
||||
confirms the update from 0.0.6 to 0.0.7. It acknowledged 50 objects and 28 items
|
||||
at 17:34:35.261, then opened inventory at 17:34:40.453. The server acknowledged
|
||||
the same snapshot and began preload at 15:34:35 UTC. This was a failed retest,
|
||||
not an outdated package. The previous local tests did not establish the reported
|
||||
dedicated-client behavior.
|
||||
|
||||
Two gaps are addressed in this candidate. Manager `GetItems` membership alone
|
||||
does not check the actual storage slot or the `BaseInventoryStorageComponent.GetAll`
|
||||
list used by vanilla `SCR_InventoryStorageBaseUI`. The client now requires an
|
||||
item's parent slot, reverse slot lookup, slot occupant and storage enumeration
|
||||
to agree as well as membership in the expected manager.
|
||||
|
||||
The initial acknowledgement now only allows native asset preload. After preload
|
||||
completes, the server restores normal streaming for the snapshot and reinstates
|
||||
the spawn-position observer. It requests a second, token-bound acknowledgement.
|
||||
The client retains the manifest and requires all objects and storage links to
|
||||
remain present for one continuous second under normal streaming. A missing item
|
||||
resets this interval. Only this final acknowledgement allows assignment; timeout
|
||||
also covers this stage. Snapshot changes invalidate both acknowledgements and
|
||||
require another preload. Cleanup transfers the observer to the controlled actor
|
||||
or removes it if the admission failed.
|
||||
|
||||
Regression adapters cover stale/premature final replies, final-stage timeout,
|
||||
missing slot/storage/UI enumeration despite manager membership, and disappearance
|
||||
after preload resetting the final check. These changes close identified gaps;
|
||||
the logs do not prove which gap caused the user's empty crate. Dedicated
|
||||
fresh-join and late-join acceptance remain pending.
|
||||
|
||||
The 17:41 local Workbench run compiled the candidate. Initial storage readiness
|
||||
was acknowledged at 17:42:50.749, native preload completed and the final check
|
||||
started at 17:42:51.758, and final storage readiness was acknowledged at
|
||||
17:42:52.891. The prisoner-admission assertion followed at 17:42:53.381. The
|
||||
full victory fixture later failed in vanilla `SCR_AIGetSuppressionVolumePosition`
|
||||
(`No suppression volume provided!`); that failure is retained, and is not a
|
||||
successful full-mission verdict. Cleanup and unchanged addon sources were
|
||||
confirmed. Raw results are in
|
||||
`/tmp/erri-spawn-slots-20260920/logs/logs_2026-09-20_17-41-56`.
|
||||
|
||||
All repository checks passed. The focused native `core/prisoner-kit` fixture
|
||||
then completed 171 assertions without script/runtime failures. Both readiness
|
||||
acknowledgements preceded admission; the fixture checked native clothing,
|
||||
equipment and physical stock transfers. Cleanup and unchanged source were
|
||||
confirmed. The four external RHS/WCS localization errors remain in its strict
|
||||
native verdict. Results are in
|
||||
`/tmp/erri-spawn-slots-kit-20260920/logs/logs_2026-09-20_17-44-35`.
|
||||
This is local Workbench evidence, not a successful RKS #2 retest. Publishing and
|
||||
updating the server/client to 0.0.8 remain necessary for dedicated validation.
|
||||
|
||||
## Client inventory readiness (0.0.7 candidate)
|
||||
|
||||
The user reported arriving before loot was available with 0.0.6. The client's
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Strengthen the preparation screen's loot check: verify actual inventory storage slots, then check again after asset preload and the return to normal network streaming. Keep waiting if any required item disappears or is not yet readable in its storage. Require one continuous second of ready objects and storage contents before giving control.
|
||||
|
||||
Retain the existing retry timeout and refresh checks for loot taken by other players. Add separate initial and final readiness logging to diagnose dedicated-server joins.
|
||||
+35
-16
@@ -2,11 +2,11 @@
|
||||
from policy_harness import ROOT, body, run
|
||||
|
||||
source = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c'
|
||||
keys = ('bool ERR_SpawnSceneReady(', 'bool ERR_SpawnSceneMayFinish(',
|
||||
keys = ('bool ERR_SpawnSceneReady(', 'bool ERR_SpawnSceneMayFinish(', 'bool ERR_SpawnSceneVerified(',
|
||||
'protected void ERR_AcknowledgeSpawnScene(',
|
||||
'protected void ERR_ClearClientSpawnScene(',
|
||||
'protected void ERR_CheckClientSpawnScene(',
|
||||
'protected void ERR_CancelClientSpawnScene(')
|
||||
'protected void ERR_CancelClientSpawnScene(', 'protected void ERR_ConfirmClientSpawnScene(')
|
||||
methods = '\n'.join(body(source, key) for key in keys)
|
||||
methods = methods.replace('protected ', '').replace('Replication.IsServer()', 'Replication::IsServer()')
|
||||
methods = methods.replace('ERR_GameMode.Get()', 'Mode::Get()')
|
||||
@@ -15,7 +15,8 @@ methods = methods.replace('component.GetEntity()', 'component->GetEntity()')
|
||||
methods = methods.replace('foreach (RplId id :', 'for (RplId id :')
|
||||
methods = methods.replace('GetGame().GetWorld().GetWorldTime()', 'game.now')
|
||||
methods = methods.replace('GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene)', 'game.removes++')
|
||||
methods = methods.replace('Rpc(ERR_AcknowledgeSpawnScene, token)', 'SendAck(token)')
|
||||
methods = methods.replace('GetGame().GetCallqueue().CallLater(ERR_CheckClientSpawnScene, 100, true)', 'game.starts++')
|
||||
methods = methods.replace('Rpc(ERR_AcknowledgeSpawnScene, token, m_ERRClientSceneFinal)', 'SendAck(token, m_ERRClientSceneFinal)')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
@@ -24,39 +25,57 @@ using RplId=int;
|
||||
struct RplComponent{bool entity=true;bool GetEntity(){return entity;}};
|
||||
namespace Replication{bool server=true;std::map<int,RplComponent*> items;bool IsServer(){return server;}RplComponent* FindItem(int id){return items.count(id)?items[id]:nullptr;}}
|
||||
struct Mode{static bool present;static bool Get(){return present;}};bool Mode::present=true;
|
||||
struct Game{float now=0;int removes=0;}game;Game* GetGame(){return &game;}
|
||||
struct Nodes:std::vector<int>{using std::vector<int>::operator=;void Clear(){clear();}int Count(){return size();}};
|
||||
struct Game{float now=0;int removes=0,starts=0;}game;Game* GetGame(){return &game;}
|
||||
struct Nodes:std::vector<int>{using std::vector<int>::operator=;void Clear(){clear();}int Count(){return size();}bool IsEmpty(){return empty();}};
|
||||
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;
|
||||
bool m_ERRSceneReleased=false,m_ERRSceneVerified=false,m_ERRClientSceneFinal=false,lastFinal=false;float m_ERRClientSceneStableSince=-1;
|
||||
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;}
|
||||
int GetPlayerId(){return 1;}void SendAck(int token,bool finalCheck){acks++;lastAck=token;lastFinal=finalCheck;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
int main(){
|
||||
Probe server;assert(!server.ERR_SpawnSceneReady());
|
||||
server.ERR_AcknowledgeSpawnScene(4);assert(!server.m_ERRSceneAcknowledged);
|
||||
server.ERR_AcknowledgeSpawnScene(4,false);assert(!server.m_ERRSceneAcknowledged);
|
||||
server.m_ERRScenePending=true;assert(!server.ERR_SpawnSceneMayFinish());
|
||||
server.ERR_AcknowledgeSpawnScene(3);assert(!server.ERR_SpawnSceneReady());
|
||||
Replication::server=false;server.ERR_AcknowledgeSpawnScene(4);assert(!server.ERR_SpawnSceneReady());Replication::server=true;
|
||||
server.ERR_AcknowledgeSpawnScene(4);assert(server.ERR_SpawnSceneReady()&&server.ERR_SpawnSceneMayFinish());
|
||||
server.ERR_AcknowledgeSpawnScene(3,false);assert(!server.ERR_SpawnSceneReady());
|
||||
Replication::server=false;server.ERR_AcknowledgeSpawnScene(4,false);assert(!server.ERR_SpawnSceneReady());Replication::server=true;
|
||||
server.ERR_AcknowledgeSpawnScene(4,false);assert(server.ERR_SpawnSceneReady()&&server.ERR_SpawnSceneMayFinish());
|
||||
server.m_ERRSceneAcknowledged=false;game.now=999;assert(!server.ERR_SpawnSceneMayFinish());
|
||||
game.now=1000;assert(server.ERR_SpawnSceneMayFinish()&&server.m_ERRSceneTimedOut&&!server.ERR_SpawnSceneReady());
|
||||
server.ERR_AcknowledgeSpawnScene(4);assert(!server.ERR_SpawnSceneReady());
|
||||
server.ERR_AcknowledgeSpawnScene(4,false);assert(!server.ERR_SpawnSceneReady());
|
||||
Probe client;client.m_ERRClientScenePending=true;client.m_ERRClientSceneNodes={11,22,33};
|
||||
RplComponent root,wall,gate;Replication::items[11]=&root;
|
||||
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.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.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);
|
||||
// The first reply cannot finalize. Final replies before release are ignored.
|
||||
Probe handshake;handshake.m_ERRScenePending=true;
|
||||
handshake.ERR_AcknowledgeSpawnScene(4,true);assert(!handshake.ERR_SpawnSceneVerified());
|
||||
handshake.ERR_AcknowledgeSpawnScene(4,false);handshake.ERR_AcknowledgeSpawnScene(4,true);assert(!handshake.ERR_SpawnSceneVerified());
|
||||
handshake.m_ERRSceneReleased=true;handshake.ERR_AcknowledgeSpawnScene(3,true);assert(!handshake.ERR_SpawnSceneVerified());
|
||||
handshake.ERR_AcknowledgeSpawnScene(4,true);assert(handshake.ERR_SpawnSceneVerified());
|
||||
Probe finalTimeout;finalTimeout.m_ERRScenePending=true;finalTimeout.m_ERRSceneAcknowledged=true;
|
||||
finalTimeout.ERR_SpawnSceneMayFinish();assert(finalTimeout.m_ERRSceneTimedOut&&!finalTimeout.ERR_SpawnSceneVerified());
|
||||
// A replica disappearing after preload resets the continuous final check.
|
||||
client.ERR_ConfirmClientSpawnScene(3);assert(!client.m_ERRClientSceneFinal&&game.starts==0);
|
||||
client.ERR_ConfirmClientSpawnScene(4);assert(client.m_ERRClientSceneFinal&&client.m_ERRClientScenePending&&game.starts==1);game.now=2000;
|
||||
client.ERR_CheckClientSpawnScene();game.now=2500;client.ERR_CheckClientSpawnScene();assert(client.acks==1);
|
||||
gate.entity=false;client.ERR_CheckClientSpawnScene();gate.entity=true;game.now=2600;client.ERR_CheckClientSpawnScene();
|
||||
game.now=3500;client.ERR_CheckClientSpawnScene();assert(client.acks==1);
|
||||
client.stockReady=false;client.ERR_CheckClientSpawnScene();client.stockReady=true;game.now=3600;client.ERR_CheckClientSpawnScene();
|
||||
game.now=4599;client.ERR_CheckClientSpawnScene();assert(client.acks==1);
|
||||
game.now=4600;client.ERR_CheckClientSpawnScene();assert(client.acks==2&&client.lastFinal);
|
||||
client.m_ERRClientScenePending=true;client.m_ERRClientSceneToken=5;client.m_ERRClientSceneNodes={99};
|
||||
client.ERR_CancelClientSpawnScene(4);assert(client.m_ERRClientScenePending);
|
||||
client.ERR_CancelClientSpawnScene(5);assert(!client.m_ERRClientScenePending&&client.m_ERRClientSceneNodes.empty());
|
||||
client.m_ERRClientScenePending=true;Mode::present=false;client.ERR_CheckClientSpawnScene();assert(!client.m_ERRClientScenePending&&client.acks==1);
|
||||
client.m_ERRClientScenePending=true;Mode::present=false;client.ERR_CheckClientSpawnScene();assert(!client.m_ERRClientScenePending&&client.acks==2);
|
||||
}
|
||||
''')
|
||||
finalizer = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnFinalization.c'
|
||||
@@ -70,7 +89,7 @@ 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;}bool preloadRequired=true;bool ERR_TakeScenePreload(){if(!ready||!preloadRequired)return false;preloadRequired=false;return true;}void ERR_EndSpawnScene(){ends++;}};
|
||||
struct Request{bool ready=false,mayFinish=false,verified=false;bool ERR_SpawnSceneVerified(){return ready&&verified;}bool ERR_VerifySceneRelease(){return !ready||verified;}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;}};
|
||||
@@ -86,7 +105,7 @@ int main(){
|
||||
// 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;assert(!h.CanFinalize_S(&request,&data,&actor)&&h.preloadStarts==1);
|
||||
h.preloadReady=true;assert(h.CanFinalize_S(&request,&data,&actor)&&h.preloadStarts==1);
|
||||
h.preloadReady=true;assert(!h.CanFinalize_S(&request,&data,&actor));request.verified=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);
|
||||
@@ -108,7 +127,7 @@ 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;
|
||||
bool m_ERRSceneReleased=true,m_ERRSceneVerified=true;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++;}
|
||||
|
||||
@@ -4,6 +4,11 @@ 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('ERR_SpawnSceneInventory.InStorage(', 'InStorage(')
|
||||
s=s.replace('InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));', 'Item* item = entity->item;')
|
||||
s=s.replace('InventoryStorageSlot slot = item.GetParentSlot();', 'Slot* slot = item->slot;')
|
||||
s=s.replace('BaseInventoryStorageComponent storage = slot.GetStorage();', 'Storage* storage = slot->storage;')
|
||||
s=s.replace('storage.FindItemSlot(entity)', 'storage->FindItemSlot(entity)').replace('storage.Get(slot.GetID())', 'storage->Get(slot->id)').replace('storage.GetAll(visible)', 'storage->GetAll(visible)')
|
||||
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;')
|
||||
@@ -15,7 +20,7 @@ def cpp(s):
|
||||
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'.join(cpp(body(inventory,key)) for key in ('static bool InStorage(', '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('))
|
||||
@@ -28,7 +33,10 @@ template<class T>struct List:std::vector<T>{using std::vector<T>::vector;using s
|
||||
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;};
|
||||
struct Storage;struct Slot{Storage* storage=nullptr;int id=0;};struct Item{Slot* slot=nullptr;};
|
||||
struct Inventory{Items items;};struct Entity{Inventory* inventory=nullptr;Item* item=nullptr;};
|
||||
struct Storage{Items visible;IEntity stored=nullptr;Slot* slot=nullptr;
|
||||
Slot* FindItemSlot(IEntity e){return stored==e?slot:nullptr;}IEntity Get(int id){return slot&&slot->id==id?stored:nullptr;}void GetAll(Items& out){out=visible;}};
|
||||
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;}}
|
||||
@@ -61,7 +69,18 @@ int main(){
|
||||
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());
|
||||
inv.items={&gun,&mag,&map};assert(!p.ERR_ClientStockReady());
|
||||
Storage gs,ms,ps;Slot gslot{&gs,0},mslot{&ms,1},pslot{&ps,2};Item gi{&gslot},mi{&mslot},pi{&pslot};
|
||||
gun.item=&gi;mag.item=&mi;map.item=π
|
||||
gs.stored=&gun;gs.slot=&gslot;gs.visible={&gun};ms.stored=&mag;ms.slot=&mslot;ms.visible={&mag};ps.stored=↦ps.slot=&pslot;ps.visible={&map};
|
||||
assert(p.ERR_ClientStockReady());
|
||||
// Manager still contains the gun, but the actual crate UI cannot read it.
|
||||
gs.visible={};assert(!p.ERR_ClientStockReady());gs.visible={&gun};
|
||||
gs.stored=&other;assert(!p.ERR_ClientStockReady());gs.stored=&gun;
|
||||
gi.slot=nullptr;assert(!p.ERR_ClientStockReady());gi.slot=&gslot;
|
||||
gslot.storage=nullptr;assert(!p.ERR_ClientStockReady());gslot.storage=&gs;
|
||||
gs.slot=&mslot;assert(!p.ERR_ClientStockReady());gs.slot=&gslot;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user