Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
71 lines
5.6 KiB
Python
71 lines
5.6 KiB
Python
"""Execute production item-station lifecycle with modeled native subscriptions."""
|
|
from policy_harness import ROOT, body, run
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ItemStationLifecycle.c'
|
|
code='\n'.join(body(p,x) for x in ['override void OnPostInit(', 'protected override void DelayedInit(', 'protected override void AddRemoveOnDamageStateChanged(', 'override void OnDelete('])
|
|
code=code.replace('protected ','').replace('override ','').replace('IEntity owner','IEntity* owner')
|
|
for name in ['owner','m_ERRItemStationManager','m_ERRItemStationResource']:
|
|
code=code.replace(name+'.',name+'->')
|
|
for name in ['SCR_Global','ERR_GameMode','ERR_TerrainTakeoverSettings','SCR_EntityCatalogManagerComponent','SCR_SupportStationManagerComponent']:
|
|
code=code.replace(name+'.',name+'::')
|
|
code=code.replace('GetGame().','GetGame()->').replace('Remove(DelayedInit)','Remove(0)').replace('Remove(TEMP_OnInteractorReplicated)','Remove(1)')
|
|
for method in ['OnPostInit','DelayedInit','OnDelete','AddRemoveOnDamageStateChanged']:
|
|
code=code.replace('super.'+method,'Native'+method)
|
|
run(r'''
|
|
#include <cassert>
|
|
#define null nullptr
|
|
namespace SCR_Global{bool editing=false;bool IsEditMode(){return editing;}}
|
|
namespace ERR_GameMode{bool present=true;bool Get(){return present;}}
|
|
namespace ERR_TerrainTakeoverSettings{bool enabled=true;bool Enabled(){return enabled;}}
|
|
namespace SCR_EntityCatalogManagerComponent{bool present=true;bool GetInstance(){return present;}}
|
|
struct Queue{int cancelled=0;void Remove(int){cancelled++;}};
|
|
struct Game{int world=1;Queue queue;int GetWorld(){return world;}Queue& GetCallqueue(){return queue;}}game;
|
|
Game* gamePtr=&game;Game* GetGame(){return gamePtr;}
|
|
struct Event{int subscribed=0,removed=0;void Remove(int){subscribed=0;removed++;}};
|
|
struct Resource{Event event;Event& TEMP_GetOnInteractorReplicated(){return event;}};
|
|
struct Manager{int removed=0;void RemoveSupportStation(void*){removed++;}}oldManager,newManager;
|
|
namespace SCR_SupportStationManagerComponent{Manager* current=&oldManager;Manager* GetInstance(){return current;}}
|
|
struct IEntity{int world=1;int GetWorld(){return world;}};
|
|
struct Station{
|
|
bool m_ERRItemStationScoped=false,m_ERRItemStationInitializing=false,m_ERRItemStationInitialized=false,m_ERRItemStationDeleted=false;
|
|
Resource* m_ERRItemStationResource=nullptr;Manager* m_ERRItemStationManager=nullptr;
|
|
bool m_ResourceConsumer=false,m_ResourceGenerator=false,range=false,invalid=false;
|
|
Resource resource;int initCalls=0,errors=0,deleteCalls=0,damageAdded=0,damageRemoved=0;
|
|
Resource* GetResourceComponent(){return &resource;}bool UsesRange(){return range;}
|
|
void NativeOnPostInit(IEntity*){}
|
|
void NativeDelayedInit(IEntity* owner){initCalls++;if(!SCR_EntityCatalogManagerComponent::present||invalid){errors++;return;}m_ResourceConsumer=true;resource.event.subscribed++;AddRemoveOnDamageStateChanged(owner,true);}
|
|
void NativeOnDelete(IEntity*){deleteCalls++;}
|
|
void NativeAddRemoveOnDamageStateChanged(IEntity*,bool subscribe){if(subscribe)damageAdded++;else damageRemoved++;}
|
|
'''+code+r'''
|
|
};
|
|
int main(){
|
|
IEntity main,preview{2};
|
|
// Live initialization validates normally and records both subscriptions.
|
|
Station live;live.range=true;live.OnPostInit(&main);live.DelayedInit(&main);
|
|
assert(live.m_ERRItemStationInitialized&&live.damageAdded==1&&live.resource.event.subscribed==1);
|
|
// Destroy the catalog and replace the global manager before cleanup.
|
|
SCR_EntityCatalogManagerComponent::present=false;SCR_SupportStationManagerComponent::current=&newManager;
|
|
live.OnDelete(&main);live.OnDelete(&main);assert(!live.deleteCalls&&live.damageRemoved==1&&live.resource.event.removed==1);
|
|
assert(oldManager.removed==1&&newManager.removed==0&&!live.m_ERRItemStationResource&&!live.m_ERRItemStationManager);
|
|
live.DelayedInit(&main);assert(live.initCalls==1); // Late queued init cannot resurrect subscriptions.
|
|
// Delete before the scheduled initialization: no subscription and no startup revalidation.
|
|
Station unborn;unborn.OnPostInit(&main);unborn.OnDelete(&main);unborn.DelayedInit(&main);
|
|
assert(unborn.initCalls==0&&unborn.damageRemoved==0&&unborn.errors==0);
|
|
// A catalog-less preload object is not a live station and requires no validation.
|
|
Station preload;preload.OnPostInit(&preview);preload.DelayedInit(&preview);preload.OnDelete(&preview);
|
|
assert(!preload.initCalls&&!preload.errors&&!preload.damageRemoved);
|
|
// Missing catalogs in the MAIN world remain a real validation error.
|
|
Station broken;broken.OnPostInit(&main);broken.DelayedInit(&main);assert(broken.errors==1&&!broken.m_ERRItemStationInitialized);
|
|
broken.OnDelete(&main);assert(!broken.damageRemoved);
|
|
// Non-range stations also detach the resource event, which native teardown skipped.
|
|
SCR_EntityCatalogManagerComponent::present=true;Station portable;portable.OnPostInit(&main);portable.DelayedInit(&main);portable.OnDelete(&main);
|
|
assert(portable.resource.event.removed==1&&newManager.removed==0);
|
|
Station badType;badType.invalid=true;badType.OnPostInit(&main);badType.DelayedInit(&main);badType.OnDelete(&main);assert(badType.errors==1&&!badType.damageRemoved);
|
|
// Opted-out unrelated modes and editor mode keep their original lifecycle.
|
|
ERR_GameMode::present=false;ERR_TerrainTakeoverSettings::enabled=false;
|
|
Station unrelated;unrelated.OnPostInit(&main);unrelated.OnDelete(&main);assert(unrelated.deleteCalls==1);
|
|
ERR_TerrainTakeoverSettings::enabled=true;SCR_Global::editing=true;
|
|
Station editing;editing.OnPostInit(&preview);editing.OnDelete(&preview);assert(editing.deleteCalls==1);
|
|
}
|
|
''')
|
|
print('PASS: catalog-independent cleanup removes original subscriptions once, handles never-initialized/preload items, preserves live validation and opted-out/editor behavior')
|