--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c @@ -465,7 +465,7 @@ SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController()); if (!controller) return; controller.ERR_StartObserverMonitor(); - GetGame().GetCallqueue().Remove(BindLocalObserver); + // Keep watching for a replacement local controller; Start is idempotent. } protected void StartScenario() --- a/tests/test_observer_ownership.py +++ b/tests/test_observer_ownership.py @@ -3,6 +3,10 @@ from pathlib import Path from policy_harness import ROOT,body,run p=Path(os.environ.get('ERR_OBSERVER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c')) +game_mode=Path(os.environ.get('ERR_GAME_MODE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c')) +binder=body(game_mode,'protected void BindLocalObserver(') +binder=binder.replace('protected ', '').replace('SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());','Controller* controller = static_cast(GetGame()->GetPlayerController());') +binder=binder.replace('GetGame().','GetGame()->').replace('GetCallqueue().','GetCallqueue()->').replace('Remove(BindLocalObserver)','Remove(this)').replace('controller.','controller->') def cpp(s): s=s.replace('protected ','').replace('null','nullptr') for n in ('ERR_GameMode','ERR_Actors','ERR_ObserverRules','ERR_ObserverGuidance','SCR_HintManagerComponent','SCR_RespawnSystemComponent','SCR_EditorManagerEntity','Resource','SCR_ManualCamera','RplId','ETransformMode','LogLevel','System'): @@ -60,7 +64,28 @@ void ERR_OnObserverMonitorStarted(){starts++;} '''+methods+r''' }; +struct Binder {bool m_Stopped=false,m_NativeEndHandled=false; +'''+binder+r''' +}; int main(){ + // The real repeating binder must survive the first controller and gaps. + Binder binder;Controller first,replacement;queue.owners.insert(&binder); + game.local=nullptr;binder.BindLocalObserver();assert(queue.owners.count(&binder)&&queue.added==0); + game.local=&first;binder.BindLocalObserver(); + assert(first.m_ERRObserverMonitor&&first.starts==1&&queue.owners.count(&first)); + assert(queue.owners.count(&binder)); + binder.BindLocalObserver();assert(first.starts==1&&queue.added==1); + game.local=nullptr;binder.BindLocalObserver();assert(queue.owners.count(&binder)); + game.local=&replacement;binder.BindLocalObserver(); + assert(replacement.m_ERRObserverMonitor&&replacement.starts==1&&queue.owners.count(&replacement)); + first.ERR_UpdateObserverClient(); + assert(!first.m_ERRObserverMonitor&&!queue.owners.count(&first)&&queue.owners.count(&replacement)); + binder.BindLocalObserver();assert(replacement.starts==1&&queue.added==2); + binder.m_Stopped=true;binder.BindLocalObserver();assert(!queue.owners.count(&binder)&&queue.owners.count(&replacement)); + binder.m_Stopped=false;binder.m_NativeEndHandled=true;queue.owners.insert(&binder);binder.BindLocalObserver(); + assert(!queue.owners.count(&binder)&&queue.owners.count(&replacement)&&queue.added==2); + queue.owners.clear();queue.added=0;game.local=nullptr; + Actor body;CameraBase formerBodyCamera,newBodyCamera; Controller former,current;former.actor=&body;former.playerCamera=&formerBodyCamera; game.local=&former;manager.current=&formerBodyCamera;former.ERR_StartObserverMonitor();