From ed6c430c80768e6fc28aaf4cb43d36bc8c2e74b0 Mon Sep 17 00:00:00 2001 From: Jucan Andrei Daniel Date: Sat, 13 Jun 2026 13:02:12 +0300 Subject: [PATCH 1/3] perf: dedup VOB lazy-init queue with an IsQueued flag Replaces the O(n) Queue.Contains() guard with a per-loader IsQueued flag, batch-drains the queue (10/frame) skipping destroyed entries, and clears pending entries on world change. --- .../Scripts/Adapters/Scenes/WorldScene.cs | 3 ++ .../Scripts/Adapters/Vob/VobLoader.cs | 1 + .../Scripts/Services/Vobs/VobService.cs | 47 ++++++++++++------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/Assets/Gothic-Core/Scripts/Adapters/Scenes/WorldScene.cs b/Assets/Gothic-Core/Scripts/Adapters/Scenes/WorldScene.cs index 188b7e8e1..1a571cda4 100644 --- a/Assets/Gothic-Core/Scripts/Adapters/Scenes/WorldScene.cs +++ b/Assets/Gothic-Core/Scripts/Adapters/Scenes/WorldScene.cs @@ -165,6 +165,9 @@ await _vobService.CreateWorldVobsAsync(_configService.Dev, _loadingService, _sav Logger.LogError(e.ToString(), LogCat.Loading); } + // The culling sweep inside WorldSceneLoaded only _enqueued_ all VOBs around the spawn point for + // lazy initialization. InitVobCoroutine() drains the queue in the background while the player is + // already in the world - a brief pop-in is preferred over holding the loading screen any longer. _loadingService.StopLoading(); SceneManager.UnloadSceneAsync(Constants.SceneLoading); } diff --git a/Assets/Gothic-Core/Scripts/Adapters/Vob/VobLoader.cs b/Assets/Gothic-Core/Scripts/Adapters/Vob/VobLoader.cs index bb5be91f1..e2bb01d96 100644 --- a/Assets/Gothic-Core/Scripts/Adapters/Vob/VobLoader.cs +++ b/Assets/Gothic-Core/Scripts/Adapters/Vob/VobLoader.cs @@ -7,5 +7,6 @@ public class VobLoader : MonoBehaviour { public VobContainer Container; public bool IsLoaded; + public bool IsQueued; } } diff --git a/Assets/Gothic-Core/Scripts/Services/Vobs/VobService.cs b/Assets/Gothic-Core/Scripts/Services/Vobs/VobService.cs index c6ea15d55..ac165c29b 100644 --- a/Assets/Gothic-Core/Scripts/Services/Vobs/VobService.cs +++ b/Assets/Gothic-Core/Scripts/Services/Vobs/VobService.cs @@ -55,6 +55,8 @@ public class VobService private readonly char[] _itemCountSeparators = { ':', '.' }; + private const int VobInitBatchSize = 10; + private Dictionary _vobTypeParentGOs = new(); private Queue _objectsToInitQueue = new(); @@ -138,16 +140,14 @@ public void InitVob(GameObject go) { go.TryGetComponent(out VobLoader loaderComp); - if (loaderComp == null || loaderComp.IsLoaded) + // IsQueued ensures we do not add elements to be loaded twice (without an O(n) Queue.Contains() check). + if (loaderComp == null || loaderComp.IsLoaded || loaderComp.IsQueued) { return; } - - // Do not add elements to be loaded twice. - if (_objectsToInitQueue.Contains(loaderComp)) - return; - _objectsToInitQueue.Enqueue(go.GetComponent()); + loaderComp.IsQueued = true; + _objectsToInitQueue.Enqueue(loaderComp); } // DEBUG - Check how many frames it took to initialize all the objects @@ -175,19 +175,27 @@ private IEnumerator InitVobCoroutine() // firstFrameQueueFilledUp = Time.frameCount; // } - var item = _objectsToInitQueue.Dequeue(); - - item.IsLoaded = true; - - // We assume that each loaded VOB is centered at parent=0,0,0. - // Should work smoothly until we start lazy loading sub-vobs ;-) - try - { - _initializerDomain.InitVob(item.Container.Vob, item.gameObject, default, true); - } - catch (Exception e) + for (var i = 0; i < VobInitBatchSize && !_objectsToInitQueue.IsEmpty(); i++) { - Logger.LogError($"Failed to init VOB {item.name}: {e}", LogCat.Vob); + var item = _objectsToInitQueue.Dequeue(); + + // Destroyed in the meantime (e.g. world change). + if (item == null) + continue; + + item.IsLoaded = true; + + // We assume that each loaded VOB is centered at parent=0,0,0. + // Should work smoothly until we start lazy loading sub-vobs ;-) + try + { + _initializerDomain.InitVob(item.Container.Vob, item.gameObject, default, true); + } + catch (Exception e) + { + Logger.LogError($"Failed to init VOB {item.name}: {e}", LogCat.Vob); + } + } yield return _frameSkipperService.TrySkipToNextFrameCoroutine(); @@ -242,6 +250,9 @@ private void PreCreateWorldVobs(List vobs, GameObject rootGo, Lo // We reset the GO dictionary. _vobTypeParentGOs = new(); + // Drop pending lazy-init entries from a previous world. Their VobLoader GOs are destroyed by now. + _objectsToInitQueue.Clear(); + ObjectRoutines.ClearAndReleaseMemory(); ObjectRoutines = new(); From 370ab1b6f251a3968e74099ef531b708aa3c6740 Mon Sep 17 00:00:00 2001 From: Jucan Andrei Daniel Date: Sat, 13 Jun 2026 13:02:12 +0300 Subject: [PATCH 2/3] feat: route bed-visual mob doors to the bed prefab oCMobDoor VOBs whose visual name starts with BED_ now resolve the VobBed prefab instead of VobDoor. --- .../Scripts/Domain/Vobs/VobInitializerDomain.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Assets/Gothic-Core/Scripts/Domain/Vobs/VobInitializerDomain.cs b/Assets/Gothic-Core/Scripts/Domain/Vobs/VobInitializerDomain.cs index 1a1a9110d..a2df4fe5e 100644 --- a/Assets/Gothic-Core/Scripts/Domain/Vobs/VobInitializerDomain.cs +++ b/Assets/Gothic-Core/Scripts/Domain/Vobs/VobInitializerDomain.cs @@ -291,7 +291,12 @@ private GameObject GetPrefab(IVirtualObject vob, GameObject parent = null) go = _resourceCacheService.TryGetPrefabObject(PrefabType.VobSwitch, name: name, parent: parent); break; case VirtualObjectType.oCMobDoor: - go = _resourceCacheService.TryGetPrefabObject(PrefabType.VobDoor, name: name, parent: parent); + var visualName = vob.Visual?.Name; + var isBed = !visualName.IsNullOrEmpty() + && visualName.Split('_')[0].EqualsIgnoreCase("BED"); + go = isBed + ? _resourceCacheService.TryGetPrefabObject(PrefabType.VobBed, name: name, parent: parent) + : _resourceCacheService.TryGetPrefabObject(PrefabType.VobDoor, name: name, parent: parent); break; case VirtualObjectType.oCMobContainer: go = _resourceCacheService.TryGetPrefabObject(PrefabType.VobContainer, name: name, parent: parent); From c1eb8151bade58ceaf81aaa20d8640fb3bd3e211 Mon Sep 17 00:00:00 2001 From: Jucan Andrei Daniel Date: Sat, 13 Jun 2026 13:04:07 +0300 Subject: [PATCH 3/3] perf: enable FFR, symmetric projection and quality tuning Foveated rendering at max level, symmetric projection and SustainedHigh hints for Quest-class headsets. --- ProjectSettings/ProjectSettings.asset | 17 ++++++++++------- ProjectSettings/QualitySettings.asset | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 1f9f0d28a..fe45996e4 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -8,7 +8,7 @@ PlayerSettings: AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 AndroidEnableSustainedPerformanceMode: 0 - defaultScreenOrientation: 4 + defaultScreenOrientation: 3 targetDevice: 2 useOnDemandResources: 0 accelerometerFrequency: 60 @@ -17,8 +17,8 @@ PlayerSettings: defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} - m_ShowUnitySplashScreen: 1 - m_ShowUnitySplashLogo: 1 + m_ShowUnitySplashScreen: 0 + m_ShowUnitySplashLogo: 0 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 @@ -285,7 +285,7 @@ PlayerSettings: AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 - androidUseCustomKeystore: 1 + androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 @@ -536,7 +536,7 @@ PlayerSettings: - m_BuildTarget: LuminSupport m_GraphicsJobs: 0 - m_BuildTarget: AndroidPlayer - m_GraphicsJobs: 0 + m_GraphicsJobs: 1 - m_BuildTarget: WebGLSupport m_GraphicsJobs: 0 m_BuildTargetGraphicsJobMode: @@ -560,6 +560,9 @@ PlayerSettings: - m_BuildTarget: WindowsStandaloneSupport m_APIs: 0200000012000000 m_Automatic: 0 + - m_BuildTarget: LinuxStandaloneSupport + m_APIs: 1500000011000000 + m_Automatic: 0 m_BuildTargetVRSettings: - m_BuildTarget: Standalone m_Enabled: 0 @@ -875,8 +878,8 @@ PlayerSettings: webWasm2023: 0 webEnableSubmoduleStrippingCompatibility: 0 scriptingDefineSymbols: - Android: USE_INPUT_SYSTEM_POSE_CONTROL;ENABLE_UBERLOGGING;GOTHIC_HVR_INSTALLED;SENTIS_ANALYTICS_ENABLED;APP_UI_EDITOR_ONLY - Standalone: USE_INPUT_SYSTEM_POSE_CONTROL;ENABLE_UBERLOGGING;GOTHIC_HVR_INSTALLED;SENTIS_ANALYTICS_ENABLED;APP_UI_EDITOR_ONLY + Android: USE_INPUT_SYSTEM_POSE_CONTROL;ENABLE_UBERLOGGING;GOTHIC_HVR_INSTALLED;APP_UI_EDITOR_ONLY + Standalone: USE_INPUT_SYSTEM_POSE_CONTROL;ENABLE_UBERLOGGING;GOTHIC_HVR_INSTALLED;APP_UI_EDITOR_ONLY Windows Store Apps: USE_INPUT_SYSTEM_POSE_CONTROL additionalCompilerArguments: {} platformArchitecture: {} diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset index 53d6d20c8..a2bd8d045 100644 --- a/ProjectSettings/QualitySettings.asset +++ b/ProjectSettings/QualitySettings.asset @@ -157,7 +157,7 @@ QualitySettings: - Android m_TextureMipmapLimitGroupNames: [] m_PerPlatformDefaultQuality: - Android: 1 + Android: 0 Lumin: 2 Nintendo 3DS: 2 Nintendo Switch: 2