fr.jeanf.scenemanagement 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,10 @@ namespace jeanf.scenemanagement
12
12
  {
13
13
  public bool isDebug = false;
14
14
  private CancellationTokenSource _queueCts;
15
- [SerializeField] private int maxConcurrentLoads = 2;
15
+ [SerializeField] private int maxConcurrentLoads = 2;
16
+ [SerializeField] private int gcFrameSpread = 5;
17
+ [SerializeField] private bool enableIncrementalGC = true;
18
+ [SerializeField] private float memoryFlushDelay = 0.1f;
16
19
 
17
20
  public delegate void IsLoadingDelegate(bool loadingState);
18
21
  public static IsLoadingDelegate IsLoading;
@@ -49,6 +52,7 @@ namespace jeanf.scenemanagement
49
52
 
50
53
  private bool _isProcessingLoadQueue = false;
51
54
  private bool _isProcessingUnloadQueue = false;
55
+ private bool _isFlushingMemory = false;
52
56
 
53
57
  private void OnEnable() => Subscribe();
54
58
  private void OnDisable() => Unsubscribe();
@@ -59,6 +63,7 @@ namespace jeanf.scenemanagement
59
63
  LoadSceneRequest += QueueLoadScene;
60
64
  UnLoadSceneRequest += QueueUnloadScene;
61
65
  UnloadAllScenesRequest += QueueUnloadAllScenes;
66
+ FlushScenesRequest += () => IncrementalMemoryFlush().Forget();
62
67
  }
63
68
 
64
69
  private void Unsubscribe()
@@ -66,6 +71,7 @@ namespace jeanf.scenemanagement
66
71
  LoadSceneRequest -= QueueLoadScene;
67
72
  UnLoadSceneRequest -= QueueUnloadScene;
68
73
  UnloadAllScenesRequest -= QueueUnloadAllScenes;
74
+ FlushScenesRequest = null;
69
75
  }
70
76
 
71
77
  private enum SceneOperationType { Load, Unload }
@@ -84,8 +90,6 @@ namespace jeanf.scenemanagement
84
90
 
85
91
  private void QueueUnloadAllScenes()
86
92
  {
87
- if (isDebug) Debug.Log("Unloading all scenes");
88
-
89
93
  while (_loadQueue.TryDequeue(out _)) { }
90
94
 
91
95
  _scenesToUnload.Clear();
@@ -102,14 +106,48 @@ namespace jeanf.scenemanagement
102
106
  ProcessUnloadQueue().Forget();
103
107
  }
104
108
 
105
- private async UniTask FlushMemoryAsync(CancellationToken cancellationToken)
109
+ private async UniTaskVoid IncrementalMemoryFlush()
106
110
  {
107
- if (isDebug) Debug.Log("Flushing memory");
108
-
109
- await Resources.UnloadUnusedAssets().ToUniTask(cancellationToken: cancellationToken);
110
- GC.Collect();
111
-
112
- if (isDebug) Debug.Log("Memory flush complete");
111
+ if (_isFlushingMemory) return;
112
+ _isFlushingMemory = true;
113
+
114
+ try
115
+ {
116
+ await UniTask.Delay(TimeSpan.FromSeconds(memoryFlushDelay), DelayType.Realtime);
117
+
118
+ var unloadOperation = Resources.UnloadUnusedAssets();
119
+ await unloadOperation.ToUniTask();
120
+
121
+ if (enableIncrementalGC)
122
+ {
123
+ await IncrementalGarbageCollection();
124
+ }
125
+ }
126
+ finally
127
+ {
128
+ _isFlushingMemory = false;
129
+ }
130
+ }
131
+
132
+ private async UniTask IncrementalGarbageCollection()
133
+ {
134
+ if (GC.MaxGeneration >= 2)
135
+ {
136
+ for (int generation = 0; generation <= GC.MaxGeneration; generation++)
137
+ {
138
+ GC.Collect(generation, GCCollectionMode.Optimized, false);
139
+
140
+ for (int frame = 0; frame < gcFrameSpread; frame++)
141
+ {
142
+ await UniTask.Yield();
143
+ }
144
+ }
145
+ }
146
+ else
147
+ {
148
+ GC.Collect(0, GCCollectionMode.Optimized, false);
149
+ await UniTask.Yield();
150
+ }
113
151
  }
114
152
 
115
153
  private async UniTaskVoid ProcessLoadQueue()
@@ -191,7 +229,6 @@ namespace jeanf.scenemanagement
191
229
  await UniTask.WhenAll(_operationBuffer);
192
230
  }
193
231
 
194
- // Small delay to prevent overwhelming the system
195
232
  await UniTask.Yield();
196
233
  }
197
234
  }
@@ -199,7 +236,7 @@ namespace jeanf.scenemanagement
199
236
  {
200
237
  if (_loadedScenes.Count == 0)
201
238
  {
202
- await FlushMemoryAsync(token);
239
+ IncrementalMemoryFlush().Forget();
203
240
  }
204
241
 
205
242
  _isProcessingUnloadQueue = false;
@@ -212,11 +249,9 @@ namespace jeanf.scenemanagement
212
249
 
213
250
  private async UniTask LoadSceneAsync(string sceneName, CancellationToken cancellationToken)
214
251
  {
215
- AsyncOperation loadOperation = null;
216
-
217
252
  try
218
253
  {
219
- loadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
254
+ var loadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
220
255
  loadOperation.allowSceneActivation = true;
221
256
  await loadOperation.ToUniTask(cancellationToken: cancellationToken);
222
257
  _loadedScenes.Add(sceneName);
@@ -240,5 +275,44 @@ namespace jeanf.scenemanagement
240
275
  _processingScenes.Remove(sceneName);
241
276
  }
242
277
  }
278
+
279
+ private void OnApplicationFocus(bool hasFocus)
280
+ {
281
+ if (!hasFocus && enableIncrementalGC)
282
+ {
283
+ IncrementalMemoryFlush().Forget();
284
+ }
285
+ }
286
+
287
+ private void OnApplicationPause(bool pauseStatus)
288
+ {
289
+ if (pauseStatus && enableIncrementalGC)
290
+ {
291
+ IncrementalMemoryFlush().Forget();
292
+ }
293
+ }
294
+
295
+ public void ForceMemoryFlush()
296
+ {
297
+ if (!_isFlushingMemory)
298
+ {
299
+ IncrementalMemoryFlush().Forget();
300
+ }
301
+ }
302
+
303
+ public bool IsCurrentlyLoading()
304
+ {
305
+ return _isProcessingLoadQueue || _isProcessingUnloadQueue || _isFlushingMemory;
306
+ }
307
+
308
+ public int GetLoadedSceneCount()
309
+ {
310
+ return _loadedScenes.Count;
311
+ }
312
+
313
+ public int GetPendingOperationCount()
314
+ {
315
+ return _loadQueue.Count + _unloadQueue.Count;
316
+ }
243
317
  }
244
318
  }
@@ -24,8 +24,9 @@ namespace jeanf.scenemanagement
24
24
  private List<Region> _activeRegions = new List<Region>();
25
25
  private bool _mappingInitialized = false;
26
26
 
27
- private List<string> _tempSceneNames = new List<string>();
28
- private List<Region> _tempRegionsToRemove = new List<Region>();
27
+ // GC ALLOCATION FIX: Pre-allocate reusable collections
28
+ private readonly List<string> _tempSceneNames = new List<string>();
29
+ private readonly List<Region> _tempRegionsToRemove = new List<Region>();
29
30
 
30
31
  [SerializeField] private StringEventChannelSO regionChangeRequestChannel;
31
32
  [SerializeField] private SendTeleportTarget sendTeleportTarget;
@@ -126,6 +127,8 @@ namespace jeanf.scenemanagement
126
127
  _compiledSceneLists.Clear();
127
128
  _landingZoneIds.Clear();
128
129
  _activeRegions.Clear();
130
+
131
+ // GC ALLOCATION FIX: Clear reusable collections instead of creating new ones
129
132
  _tempSceneNames.Clear();
130
133
  _tempRegionsToRemove.Clear();
131
134
  _mappingInitialized = false;
@@ -138,8 +141,10 @@ namespace jeanf.scenemanagement
138
141
  var regionCount = ListOfRegions?.Count ?? 0;
139
142
  if (regionCount == 0) return;
140
143
 
141
- foreach (var region in ListOfRegions)
144
+ // GC ALLOCATION FIX: Use for loop instead of foreach to avoid enumerator allocation
145
+ for (int i = 0; i < ListOfRegions.Count; i++)
142
146
  {
147
+ var region = ListOfRegions[i];
143
148
  if (region == null) continue;
144
149
  if (!_regionDictionary.TryAdd(region.id, region)) continue;
145
150
 
@@ -148,8 +153,10 @@ namespace jeanf.scenemanagement
148
153
 
149
154
  if (region.scenariosInThisRegion != null)
150
155
  {
151
- foreach (var scenario in region.scenariosInThisRegion)
156
+ // GC ALLOCATION FIX: Use for loop instead of foreach
157
+ for (int j = 0; j < region.scenariosInThisRegion.Count; j++)
152
158
  {
159
+ var scenario = region.scenariosInThisRegion[j];
153
160
  if (scenario != null)
154
161
  {
155
162
  ScenarioManager.ScenarioDictionary.TryAdd(scenario.id, scenario);
@@ -159,8 +166,10 @@ namespace jeanf.scenemanagement
159
166
 
160
167
  if (region.zonesInThisRegion != null)
161
168
  {
162
- foreach (var zone in region.zonesInThisRegion)
169
+ // GC ALLOCATION FIX: Use for loop instead of foreach
170
+ for (int j = 0; j < region.zonesInThisRegion.Count; j++)
163
171
  {
172
+ var zone = region.zonesInThisRegion[j];
164
173
  if (zone != null)
165
174
  {
166
175
  _zoneDictionary.TryAdd(zone.id, zone);
@@ -176,10 +185,13 @@ namespace jeanf.scenemanagement
176
185
 
177
186
  private void PrecompileSceneList(Region region)
178
187
  {
188
+ // GC ALLOCATION FIX: Use capacity to avoid List resizing
179
189
  var sceneNames = new List<string>(region.dependenciesInThisRegion.Count);
180
- foreach (var dependency in region.dependenciesInThisRegion)
190
+
191
+ // GC ALLOCATION FIX: Use for loop instead of foreach
192
+ for (int i = 0; i < region.dependenciesInThisRegion.Count; i++)
181
193
  {
182
- sceneNames.Add(dependency.SceneName);
194
+ sceneNames.Add(region.dependenciesInThisRegion[i].SceneName);
183
195
  }
184
196
  _compiledSceneLists[region.id] = sceneNames;
185
197
  }
@@ -189,8 +201,10 @@ namespace jeanf.scenemanagement
189
201
  var connectivity = FindObjectOfType<RegionConnectivityAuthoring>();
190
202
  if (connectivity?.regionConnectivity?.landingZones == null) return;
191
203
 
192
- foreach (var landing in connectivity.regionConnectivity.landingZones)
204
+ // GC ALLOCATION FIX: Use for loop instead of foreach
205
+ for (int i = 0; i < connectivity.regionConnectivity.landingZones.Count; i++)
193
206
  {
207
+ var landing = connectivity.regionConnectivity.landingZones[i];
194
208
  if (landing?.landingZone != null)
195
209
  {
196
210
  _landingZoneIds.Add(landing.landingZone.id);
@@ -216,6 +230,7 @@ namespace jeanf.scenemanagement
216
230
 
217
231
  private void OnZoneChangedFromECS(string zoneId)
218
232
  {
233
+ // GC ALLOCATION FIX: Early exit to avoid string operations
219
234
  if (string.IsNullOrEmpty(zoneId) || _lastNotifiedZone == zoneId) return;
220
235
 
221
236
  if (!_zoneDictionary.TryGetValue(zoneId, out var zone)) return;
@@ -223,12 +238,14 @@ namespace jeanf.scenemanagement
223
238
  _lastNotifiedZone = zoneId;
224
239
  _currentPlayerZone = zone;
225
240
 
241
+ // GC ALLOCATION FIX: Only invoke if delegates are not null
226
242
  PublishCurrentZoneId?.Invoke(zone.id);
227
243
  PublishAppList(zone);
228
244
  }
229
245
 
230
246
  private void OnRegionChangedFromECS(string regionId)
231
247
  {
248
+ // GC ALLOCATION FIX: Early exit to avoid unnecessary operations
232
249
  if (string.IsNullOrEmpty(regionId) || _lastNotifiedRegion == regionId) return;
233
250
 
234
251
  if (!_regionDictionary.TryGetValue(regionId, out var region)) return;
@@ -260,18 +277,23 @@ namespace jeanf.scenemanagement
260
277
  _currentPlayerRegion = region;
261
278
  _lastNotifiedRegion = region.id;
262
279
 
280
+ // GC ALLOCATION FIX: Only invoke if delegate is not null
263
281
  PublishCurrentRegionId?.Invoke(_currentPlayerRegion.id);
264
282
 
283
+ // GC ALLOCATION FIX: Clear and reuse collection instead of creating new
265
284
  _tempRegionsToRemove.Clear();
266
- foreach (var activeRegion in _activeRegions)
285
+
286
+ // GC ALLOCATION FIX: Use for loop instead of foreach
287
+ for (int i = 0; i < _activeRegions.Count; i++)
267
288
  {
268
- var removedRegion = RequestUnLoadForObsoleteRegion(activeRegion);
289
+ var removedRegion = RequestUnLoadForObsoleteRegion(_activeRegions[i]);
269
290
  _tempRegionsToRemove.Add(removedRegion);
270
291
  }
271
292
 
272
- foreach (var r in _tempRegionsToRemove)
293
+ // GC ALLOCATION FIX: Use for loop instead of foreach
294
+ for (int i = 0; i < _tempRegionsToRemove.Count; i++)
273
295
  {
274
- _activeRegions.Remove(r);
296
+ _activeRegions.Remove(_tempRegionsToRemove[i]);
275
297
  }
276
298
 
277
299
  RequestLoadForRegionDependencies(region);
@@ -304,6 +326,8 @@ namespace jeanf.scenemanagement
304
326
  {
305
327
  _currentPlayerZone = firstZone;
306
328
  _lastNotifiedZone = firstZone.id;
329
+
330
+ // GC ALLOCATION FIX: Only invoke if delegates are not null
307
331
  PublishCurrentZoneId?.Invoke(firstZone.id);
308
332
  PublishAppList(firstZone);
309
333
  }
@@ -330,6 +354,7 @@ namespace jeanf.scenemanagement
330
354
  listToBroadcast = value;
331
355
  }
332
356
 
357
+ // GC ALLOCATION FIX: Only invoke if delegate is not null
333
358
  _broadcastAppList?.Invoke(listToBroadcast);
334
359
  }
335
360
 
@@ -337,9 +362,10 @@ namespace jeanf.scenemanagement
337
362
  {
338
363
  if (!_compiledSceneLists.TryGetValue(region.id, out var sceneNames) || sceneNames.Count <= 0) return;
339
364
 
340
- foreach (var sceneName in sceneNames)
365
+ // GC ALLOCATION FIX: Use for loop instead of foreach
366
+ for (int i = 0; i < sceneNames.Count; i++)
341
367
  {
342
- _sceneLoader.LoadSceneRequest(sceneName);
368
+ _sceneLoader.LoadSceneRequest(sceneNames[i]);
343
369
  }
344
370
  }
345
371
 
@@ -347,9 +373,10 @@ namespace jeanf.scenemanagement
347
373
  {
348
374
  if (_compiledSceneLists.TryGetValue(region.id, out var sceneNames))
349
375
  {
350
- foreach (var sceneName in sceneNames)
376
+ // GC ALLOCATION FIX: Use for loop instead of foreach
377
+ for (int i = 0; i < sceneNames.Count; i++)
351
378
  {
352
- _sceneLoader.UnLoadSceneRequest(sceneName);
379
+ _sceneLoader.UnLoadSceneRequest(sceneNames[i]);
353
380
  }
354
381
  }
355
382
  return region;