Retain spawn replication through admission and nearby crate use
This commit is contained in:
@@ -14,8 +14,9 @@ modded class SCR_SpawnRequestComponent
|
||||
protected bool m_ERRScenePreloadRequired;
|
||||
protected bool m_ERRSceneObserver;
|
||||
protected vector m_ERRScenePosition;
|
||||
protected bool m_ERRSceneReleased;
|
||||
protected bool m_ERRSceneFinalRequested;
|
||||
protected bool m_ERRSceneVerified;
|
||||
protected ref ERR_SpawnSceneLease m_ERRSceneLease;
|
||||
protected ref array<IEntity> m_ERRSceneRoots = {};
|
||||
protected ref array<IEntity> m_ERRSceneStockOwners = {};
|
||||
protected ref array<IEntity> m_ERRSceneStockItems = {};
|
||||
@@ -31,17 +32,14 @@ modded class SCR_SpawnRequestComponent
|
||||
|
||||
protected void ERR_StreamScene(IEntity entity, bool normalStreaming)
|
||||
{
|
||||
if (!entity || !GetPlayerController()) return;
|
||||
RplIdentity identity = GetPlayerController().GetRplIdentity();
|
||||
if (!identity.IsValid() || identity == RplIdentity.Local()) return;
|
||||
RplComponent component = RplComponent.Cast(entity.FindComponent(RplComponent));
|
||||
if (component) component.EnableStreamingConNode(identity, normalStreaming);
|
||||
ERR_SpawnSceneLease.Stream(GetPlayerController(), entity, normalStreaming);
|
||||
}
|
||||
|
||||
void ERR_BeginSpawnScene(array<IEntity> entities, bool valid, vector position)
|
||||
{
|
||||
if (!Replication.IsServer()) return;
|
||||
ERR_EndSpawnScene();
|
||||
if (m_ERRSceneLease) m_ERRSceneLease.Stop();
|
||||
m_ERRScenePosition = position;
|
||||
// Native StartSpawnPreload normally installs this observer. Keep streaming
|
||||
// the requested position while we defer asset preload until inventory arrives.
|
||||
@@ -87,7 +85,7 @@ modded class SCR_SpawnRequestComponent
|
||||
m_ERRSceneEntities.Clear();
|
||||
m_ERRSceneToken++;
|
||||
m_ERRSceneAcknowledged = false;
|
||||
m_ERRSceneReleased = false;
|
||||
m_ERRSceneFinalRequested = false;
|
||||
m_ERRSceneVerified = false;
|
||||
m_ERRScenePreloadRequired = true;
|
||||
m_ERRSceneInventoryPoll = GetGame().GetWorld().GetWorldTime() + 500;
|
||||
@@ -141,24 +139,32 @@ modded class SCR_SpawnRequestComponent
|
||||
{
|
||||
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()
|
||||
// Called only after native preload completes. Retain the scene through the
|
||||
// final check and possession; normal streaming cannot yet keep every node.
|
||||
bool ERR_VerifyLoadedScene()
|
||||
{
|
||||
if (m_ERRSceneTimedOut) return true;
|
||||
if (!ERR_SpawnSceneReady() || m_ERRScenePreloadRequired || IsPreloading()) return false;
|
||||
if (!m_ERRSceneReleased)
|
||||
if (!m_ERRSceneFinalRequested)
|
||||
{
|
||||
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);
|
||||
m_ERRSceneFinalRequested = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 preload finished; verifying held storage", GetPlayerId(), m_ERRSceneToken);
|
||||
Rpc(ERR_ConfirmClientSpawnScene, m_ERRSceneToken);
|
||||
}
|
||||
return m_ERRSceneVerified;
|
||||
}
|
||||
void ERR_RetainSpawnScene(IEntity actor)
|
||||
{
|
||||
if (!Replication.IsServer() || !ERR_SpawnSceneVerified() || !actor || !GetPlayerController()) return;
|
||||
if (!m_ERRSceneLease) m_ERRSceneLease = new ERR_SpawnSceneLease();
|
||||
m_ERRSceneLease.Start(GetPlayerController(), actor, m_ERRScenePosition, m_ERRSceneEntities);
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 retaining %2 objects until leaving the spawn area", GetPlayerId(), m_ERRSceneEntities.Count());
|
||||
// Transfer ownership of the streaming hold; request cleanup must not undo it.
|
||||
m_ERRSceneEntities.Clear();
|
||||
}
|
||||
bool ERR_SpawnSceneMayFinish()
|
||||
{
|
||||
if (!m_ERRScenePending) return true;
|
||||
@@ -187,7 +193,7 @@ modded class SCR_SpawnRequestComponent
|
||||
m_ERRSceneAcknowledged = false;
|
||||
m_ERRSceneTimedOut = false;
|
||||
m_ERRScenePreloadRequired = false;
|
||||
m_ERRSceneReleased = false;
|
||||
m_ERRSceneFinalRequested = false;
|
||||
m_ERRSceneVerified = false;
|
||||
}
|
||||
|
||||
@@ -256,7 +262,12 @@ modded class SCR_SpawnRequestComponent
|
||||
foreach (RplId id : m_ERRClientSceneNodes)
|
||||
{
|
||||
RplComponent component = RplComponent.Cast(Replication.FindItem(id));
|
||||
if (!component || !component.GetEntity()) { m_ERRClientSceneStableSince = -1; return; }
|
||||
if (!component || !component.GetEntity())
|
||||
{
|
||||
ERR_ReportStockWait(id, RplId.Invalid(), -1);
|
||||
m_ERRClientSceneStableSince = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!ERR_ClientStockReady()) { m_ERRClientSceneStableSince = -1; return; }
|
||||
if (m_ERRClientSceneFinal)
|
||||
@@ -277,7 +288,7 @@ modded class SCR_SpawnRequestComponent
|
||||
if (!Replication.IsServer() || !m_ERRScenePending || m_ERRSceneTimedOut || token != m_ERRSceneToken) return;
|
||||
if (finalCheck)
|
||||
{
|
||||
if (!m_ERRSceneReleased || !m_ERRSceneAcknowledged) return;
|
||||
if (!m_ERRSceneFinalRequested || !m_ERRSceneAcknowledged) return;
|
||||
m_ERRSceneVerified = true;
|
||||
}
|
||||
else m_ERRSceneAcknowledged = true;
|
||||
@@ -314,5 +325,6 @@ modded class SCR_SpawnRequestComponent
|
||||
if (!GetGame()) return;
|
||||
ERR_ClearClientSpawnScene();
|
||||
ERR_EndSpawnScene();
|
||||
if (m_ERRSceneLease) m_ERRSceneLease.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ 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 requestComponent.ERR_VerifyLoadedScene();
|
||||
}
|
||||
return super.CanFinalize_S(requestComponent, data, entity);
|
||||
}
|
||||
@@ -104,6 +104,7 @@ modded class SCR_FreeSpawnHandlerComponent
|
||||
return SCR_ESpawnResult.SPAWN_NOT_ALLOWED;
|
||||
}
|
||||
SCR_ESpawnResult result = super.FinalizeRequest_S(requestComponent, data, entity);
|
||||
if (result == SCR_ESpawnResult.OK) requestComponent.ERR_RetainSpawnScene(entity);
|
||||
requestComponent.ERR_EndSpawnScene();
|
||||
if (result != SCR_ESpawnResult.OK) ERR_RejectOwnedActor(requestComponent, entity, data);
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Keep the bounded spawn snapshot relevant through possession and nearby crate
|
||||
// use. Releasing it before possession can stream out runtime inventory nodes.
|
||||
class ERR_SpawnSceneLease
|
||||
{
|
||||
protected SCR_PlayerController m_Controller;
|
||||
protected IEntity m_Actor;
|
||||
protected vector m_Position;
|
||||
protected ref array<IEntity> m_Entities = {};
|
||||
|
||||
static void Stream(SCR_PlayerController controller, IEntity entity, bool normalStreaming)
|
||||
{
|
||||
if (!controller || !entity) return;
|
||||
RplIdentity identity = controller.GetRplIdentity();
|
||||
if (!identity.IsValid() || identity == RplIdentity.Local()) return;
|
||||
RplComponent component = RplComponent.Cast(entity.FindComponent(RplComponent));
|
||||
if (component) component.EnableStreamingConNode(identity, normalStreaming);
|
||||
}
|
||||
|
||||
void Start(SCR_PlayerController controller, IEntity actor, vector position, array<IEntity> entities)
|
||||
{
|
||||
Stop();
|
||||
m_Controller = controller;
|
||||
m_Actor = actor;
|
||||
m_Position = position;
|
||||
m_Entities.Copy(entities);
|
||||
GetGame().GetCallqueue().CallLater(Check, 500, true);
|
||||
}
|
||||
|
||||
protected void Check()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || !mode.Active() || !m_Controller || !m_Actor || m_Controller.GetControlledEntity() != m_Actor || vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) > 120)
|
||||
Stop();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(Check);
|
||||
foreach (IEntity entity : m_Entities) Stream(m_Controller, entity, true);
|
||||
m_Entities.Clear();
|
||||
m_Controller = null;
|
||||
m_Actor = null;
|
||||
}
|
||||
|
||||
void ~ERR_SpawnSceneLease() { Stop(); }
|
||||
}
|
||||
@@ -1,5 +1,39 @@
|
||||
# Mission preparation presentation
|
||||
|
||||
## Retain the scene through possession (0.0.9 candidate)
|
||||
|
||||
Published 0.0.8 failed on RKS #2: the client repeatedly acknowledged all 50
|
||||
objects and 28 storage items, but never acknowledged the final check. The
|
||||
server completed native preload, released the streaming hold, then timed out
|
||||
and retried every 60 seconds. This repeated for at least five attempts. The
|
||||
client's 17:48:37 session confirms the 0.0.7 -> 0.0.8 update, and the installed
|
||||
PAK contains the expected second-check implementation. The first-stage inventory
|
||||
check was succeeding; increasing the wait would not resolve this failure.
|
||||
|
||||
The candidate removes the premature release. It retains the bounded snapshot
|
||||
through preload, final acknowledgement and possession. On successful assignment,
|
||||
the request transfers its held nodes to one player-scoped `ERR_SpawnSceneLease`.
|
||||
Normal request cleanup no longer releases them. The lease checks every 500ms and
|
||||
restores normal streaming when the player leaves the 120m spawn area, loses or
|
||||
changes their controlled actor, disconnects, or the mission ends. Beginning a
|
||||
new admission or destroying the request also stops the lease. Failed admissions
|
||||
still release their own nodes. No extra spawn delay was added. Missing-node
|
||||
waits now produce a rate-limited diagnostic rather than returning silently.
|
||||
|
||||
`tests/test_spawn_scene_lease.py` executes the production handoff and lease
|
||||
lifetime, including remaining near the crate, crossing the distance boundary,
|
||||
controller loss, replacement admission and teardown. The native prisoner-kit
|
||||
fixture completed: first readiness at 17:56:41.153, final readiness at
|
||||
17:56:43.295, followed by transfer of the 50-object hold to the admitted player.
|
||||
The original runner retains a transient exiting-process cleanup classification
|
||||
failure. Results are in
|
||||
`/tmp/erri-spawn-lease-kit-20260920/logs/logs_2026-09-20_17-55-46`.
|
||||
The fixture passed 171 assertions with no script/runtime errors; four existing
|
||||
RHS/WCS localization errors remain. A separate follow-up verified the test prefix
|
||||
idle and addon sources unchanged, without rewriting the original runner result.
|
||||
All repository checks passed with the new lease regression included.
|
||||
Dedicated acceptance still requires publishing and testing 0.0.9.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Fix the startup retry loop introduced in 0.0.8. Keep the spawn area's objects and inventory items replicated through the final readiness check and player possession, and while that player remains near the starting crate. Restore normal streaming when the player leaves the area or their session ends.
|
||||
|
||||
Retain the actual storage-slot checks and add diagnostics for missing replicated objects. No extra loading delay is added.
|
||||
@@ -11,7 +11,7 @@ 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()')
|
||||
methods = methods.replace('RplComponent component = RplComponent.Cast(Replication.FindItem(id));', 'RplComponent* component = Replication::FindItem(id);')
|
||||
methods = methods.replace('component.GetEntity()', 'component->GetEntity()')
|
||||
methods = methods.replace('component.GetEntity()', 'component->GetEntity()').replace('RplId.Invalid()', '-1')
|
||||
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++')
|
||||
@@ -31,8 +31,9 @@ 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;
|
||||
bool m_ERRSceneFinalRequested=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;
|
||||
void ERR_ReportStockWait(int,int,int){}
|
||||
bool stockReady=true;bool ERR_ClientStockReady(){return stockReady;}void ERR_RefreshSceneInventory(){}
|
||||
int GetPlayerId(){return 1;}void SendAck(int token,bool finalCheck){acks++;lastAck=token;lastFinal=finalCheck;}
|
||||
'''+methods+r'''
|
||||
@@ -59,7 +60,7 @@ int main(){
|
||||
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.m_ERRSceneFinalRequested=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());
|
||||
@@ -89,7 +90,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,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 Request{bool ready=false,mayFinish=false,verified=false;bool ERR_SpawnSceneVerified(){return ready&&verified;}bool ERR_VerifyLoadedScene(){return !ready||verified;}int ends=0,retained=0;void ERR_RetainSpawnScene(void*){retained++;}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;}};
|
||||
@@ -108,7 +109,7 @@ int main(){
|
||||
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);
|
||||
h.storage.allowed=true;assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::OK&&h.assignments==1&&request.ends==3&&request.retained==1);
|
||||
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);
|
||||
}
|
||||
@@ -127,7 +128,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_ERRSceneReleased=true,m_ERRSceneVerified=true;bool m_ERRSceneObserver=true,m_ERRScenePending=true,m_ERRSceneAcknowledged=true,m_ERRSceneTimedOut=true,m_ERRScenePreloadRequired=true;
|
||||
bool m_ERRSceneFinalRequested=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++;}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Exercise production retention lifetime and the post-preload handoff."""
|
||||
from policy_harness import ROOT, body, run
|
||||
|
||||
source = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneLease.c'
|
||||
methods = '\n'.join(body(source, key) for key in ('void Start(', 'protected void Check(', 'void Stop('))
|
||||
for old, new in [
|
||||
('protected ', ''), ('SCR_PlayerController controller', 'Controller* controller'),
|
||||
('IEntity actor', 'Actor* actor'), ('vector position', 'float position'),
|
||||
('array<IEntity> entities', 'Items entities'), ('ERR_GameMode mode = ERR_GameMode.Get()', 'Mode* mode = currentMode'),
|
||||
('mode.Active()', 'mode->active'), ('m_Controller.GetControlledEntity()', 'm_Controller->actor'),
|
||||
('vector.DistanceXZ(m_Actor.GetOrigin(), m_Position)', 'std::abs(m_Actor->position - m_Position)'),
|
||||
('GetGame().GetCallqueue().CallLater(Check, 500, true)', 'starts++'),
|
||||
('if (GetGame()) GetGame().GetCallqueue().Remove(Check)', 'removes++'),
|
||||
('foreach (IEntity entity :', 'for (int entity :'), ('null', 'nullptr')]:
|
||||
methods = methods.replace(old, new)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
struct Items:std::vector<int>{using std::vector<int>::vector;void Clear(){clear();}void Copy(Items other){assign(other.begin(),other.end());}};
|
||||
struct Actor{float position=0;};struct Controller{Actor* actor=nullptr;};
|
||||
struct Mode{bool active=true;}mode;Mode* currentMode=&mode;
|
||||
struct Lease{
|
||||
Controller* m_Controller=nullptr;Actor* m_Actor=nullptr;float m_Position=0;Items m_Entities;
|
||||
int starts=0,removes=0,released=0;void Stream(Controller*,int,bool normal){assert(normal);released++;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
int main(){
|
||||
Actor actor,other;Controller controller{&actor};Lease lease;
|
||||
lease.Start(&controller,&actor,0,{11,12,13});
|
||||
for(int i=0;i<1000;i++){lease.Check();}assert(lease.released==0&&lease.m_Entities.size()==3);
|
||||
actor.position=120;lease.Check();assert(lease.released==0);
|
||||
actor.position=121;lease.Check();assert(lease.released==3&&lease.m_Entities.empty());
|
||||
lease.Stop();assert(lease.released==3);
|
||||
actor.position=0;lease.Start(&controller,&actor,0,{21});controller.actor=&other;lease.Check();assert(lease.released==4);
|
||||
controller.actor=&actor;lease.Start(&controller,&actor,0,{22});currentMode=nullptr;lease.Check();assert(lease.released==5);currentMode=&mode;
|
||||
lease.Start(&controller,&actor,0,{23});mode.active=false;lease.Check();assert(lease.released==6);mode.active=true;
|
||||
lease.Start(&controller,&actor,0,{24});lease.m_Controller=nullptr;lease.Check();assert(lease.m_Entities.empty());
|
||||
lease.Start(&controller,&actor,0,{25});lease.Start(&controller,&actor,0,{26});assert(lease.released==8);lease.Stop();assert(lease.released==9);
|
||||
}
|
||||
''')
|
||||
|
||||
scene = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c'
|
||||
verify = body(scene, 'bool ERR_VerifyLoadedScene(')
|
||||
for old, new in [('ObserversSystem observers', 'Observers* observers'),
|
||||
('observers.InsertObserverMP', 'observers->InsertObserverMP'),
|
||||
('GetPlayerController().GetRplIdentity()', '1'), ('null', 'nullptr'),
|
||||
('Rpc(ERR_ConfirmClientSpawnScene, m_ERRSceneToken)', 'confirms++')]:
|
||||
verify = verify.replace(old, new)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
template<typename... T>void PrintFormat(const char*,T...){}
|
||||
struct Observers{int calls=0;void InsertObserverMP(int,float,float,void*){calls++;}};
|
||||
struct Probe{
|
||||
bool m_ERRSceneTimedOut=false,m_ERRScenePreloadRequired=true,m_ERRSceneFinalRequested=false,m_ERRSceneVerified=false,ready=false,preloading=false;
|
||||
int m_ERRSceneToken=1,confirms=0;float m_ERRScenePosition[3]={1,0,2};Observers observer;
|
||||
bool ERR_SpawnSceneReady(){return ready;}bool IsPreloading(){return preloading;}
|
||||
Observers* GetObserversSystem(){return &observer;}bool GetPlayerController(){return true;}int GetPlayerId(){return 1;}
|
||||
// Releasing nodes here would break dedicated final readiness: intentionally
|
||||
// no streaming mutator in this adapter. The production method must retain them.
|
||||
'''+verify+r'''
|
||||
};
|
||||
int main(){
|
||||
Probe p;assert(!p.ERR_VerifyLoadedScene());p.ready=true;assert(!p.ERR_VerifyLoadedScene());
|
||||
p.m_ERRScenePreloadRequired=false;p.preloading=true;assert(!p.ERR_VerifyLoadedScene());p.preloading=false;
|
||||
assert(!p.ERR_VerifyLoadedScene()&&p.confirms==1&&p.observer.calls==1);
|
||||
assert(!p.ERR_VerifyLoadedScene()&&p.confirms==1);p.m_ERRSceneVerified=true;assert(p.ERR_VerifyLoadedScene());
|
||||
Probe timeout;timeout.m_ERRSceneTimedOut=true;assert(timeout.ERR_VerifyLoadedScene()&&timeout.confirms==0);
|
||||
}
|
||||
''')
|
||||
print('PASS: spawn scene remains held through final acknowledgement and nearby use; leaving, reassignment, disconnect and teardown release it')
|
||||
@@ -11,6 +11,7 @@ def check():
|
||||
subprocess.run([sys.executable,str(ROOT/'tests/test_prefab_identity.py')],check=True)
|
||||
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_scene.py')],check=True)
|
||||
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_scene_inventory.py')],check=True)
|
||||
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_scene_lease.py')],check=True)
|
||||
subprocess.run([sys.executable,str(ROOT/'tests/test_prison_ai.py')],check=True)
|
||||
ids={};graph={};resources={};paths={};owners={}
|
||||
for p in sorted((ROOT/'addons').glob('*/addon.gproj')):
|
||||
|
||||
Reference in New Issue
Block a user