-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathResolveSceneStateByIncreasingRadiusSystem.cs
More file actions
435 lines (366 loc) · 18.6 KB
/
Copy pathResolveSceneStateByIncreasingRadiusSystem.cs
File metadata and controls
435 lines (366 loc) · 18.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
using Arch.Core;
using Arch.System;
using Arch.SystemGroups;
using DCL.Character.Components;
using DCL.Ipfs;
using DCL.LOD;
using DCL.LOD.Components;
using DCL.Roads.Components;
using DCL.SceneRunner.Scene;
using ECS.Abstract;
using ECS.LifeCycle;
using ECS.LifeCycle.Components;
using ECS.Prioritization;
using ECS.Prioritization.Components;
using ECS.SceneLifeCycle.Components;
using ECS.SceneLifeCycle.SceneDefinition;
using ECS.SceneLifeCycle.Systems;
using ECS.StreamableLoading.AssetBundles.InitialSceneState;
using ECS.StreamableLoading.Common;
using SceneRunner.Scene;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine;
using Utility;
namespace ECS.SceneLifeCycle.IncreasingRadius
{
[UpdateInGroup(typeof(RealmGroup))]
[UpdateAfter(typeof(LoadPointersByIncreasingRadiusSystem))]
[UpdateAfter(typeof(LoadFixedPointersSystem))]
[UpdateAfter(typeof(LoadStaticPointersSystem))]
public partial class ResolveSceneStateByIncreasingRadiusSystem : BaseUnityLoopSystem, IFinalizeWorldSystem
{
private static readonly OrdenedDataNativeComparer COMPARER_INSTANCE = new ();
private readonly Entity playerEntity;
private readonly Transform playerTransform;
private readonly IRealmPartitionSettings realmPartitionSettings;
//Array sorting helpers
private readonly List<OrderedDataManaged> orderedDataManaged;
private NativeList<OrderedDataNative> orderedDataNative;
internal JobHandle? sortingJobHandle;
private bool arraysInSync;
private bool inTeleport;
//Loading helpers
private int promisesCreated;
private readonly SceneLoadingLimit sceneLoadingLimit;
private readonly VisualSceneStateResolver visualSceneStateResolver;
internal ResolveSceneStateByIncreasingRadiusSystem(World world, IRealmPartitionSettings realmPartitionSettings, Entity playerEntity,
VisualSceneStateResolver visualSceneStateResolver,
SceneLoadingLimit sceneLoadingLimit) : base(world)
{
playerTransform = World.Get<CharacterTransform>(playerEntity).Transform;
this.playerEntity = playerEntity;
this.visualSceneStateResolver = visualSceneStateResolver;
this.realmPartitionSettings = realmPartitionSettings;
this.sceneLoadingLimit = sceneLoadingLimit;
// Set initial capacity to 1/3 of the total capacity required for all rings
int initialCapacity = ParcelMathJobifiedHelper.GetRingsArraySize(realmPartitionSettings.MaxLoadingDistanceInParcels) / 3;
orderedDataManaged = new List<OrderedDataManaged>(initialCapacity);
orderedDataNative = new NativeList<OrderedDataNative>(initialCapacity, Allocator.Persistent);
ResetUtilsArrays();
}
public void FinalizeComponents(in Query query)
{
//On realm change, reset the ordered data array
ResetUtilsArrays();
}
private void ResetUtilsArrays()
{
if (sortingJobHandle.HasValue)
sortingJobHandle.Value.Complete();
orderedDataManaged.Clear();
orderedDataNative.Clear();
arraysInSync = false;
}
protected override void Update(float t)
{
// Start a new loading if the previous batch is finished
var anyNonEmpty = false;
CheckAnyLoadingInProgressQuery(World, ref anyNonEmpty);
if (!anyNonEmpty)
{
AddNewSceneDefinitionToListQuery(World);
ProcessVolatileRealmQuery(World);
ProcessesFixedRealmQuery(World);
}
ProcessScenesUnloadingInRealmQuery(World);
}
[Query]
[None(typeof(SceneLoadingState), typeof(DeleteEntityIntention), typeof(RoadInfo))]
private void AddNewSceneDefinitionToList(in Entity entity, in PartitionComponent partitionComponent,
in SceneDefinitionComponent sceneDefinitionComponent, ISSDescriptor issDescriptor)
{
if (sceneDefinitionComponent.IsPortableExperience)
{
//Portable experiences shouldnt be analyzed. Create straight away
World.Add(entity, AssetPromise<ISceneFacade, GetSceneFacadeIntention>.Create(World,
new GetSceneFacadeIntention(sceneDefinitionComponent, issDescriptor), partitionComponent), SceneLoadingState.CreatePortableExperience());
}
else
{
var sceneLoadingState = new SceneLoadingState();
//Sizes should always be the same
orderedDataManaged.Add(new OrderedDataManaged(entity, sceneDefinitionComponent, partitionComponent, sceneLoadingState, issDescriptor));
orderedDataNative.Add(new OrderedDataNative());
arraysInSync = false;
World.Add(entity, sceneLoadingState);
}
}
[Query]
[All(typeof(PartitionComponent), typeof(SceneDefinitionComponent))]
[None(typeof(ISceneFacade))]
private void CheckAnyLoadingInProgress([Data] ref bool anyNonEmpty, ref AssetPromise<ISceneFacade, GetSceneFacadeIntention> promise)
{
anyNonEmpty |= !promise.IsConsumed;
}
[Query]
[None(typeof(StaticScenePointers), typeof(FixedScenePointers))]
private void ProcessVolatileRealm(ref RealmComponent realmComponent)
{
StartScenesLoading(realmComponent);
}
[Query]
[None(typeof(RoadInfo))]
private void StartUnloading(in Entity entity, in PartitionComponent partitionComponent, ref SceneLoadingState sceneLoadingState)
{
if (partitionComponent.OutOfRange)
TryUnload(entity, ref sceneLoadingState);
}
[Query]
[None(typeof(StaticScenePointers))]
[All(typeof(RealmComponent))]
private void ProcessScenesUnloadingInRealm()
{
StartUnloadingQuery(World);
}
/// <summary>
/// Start loading scenes when all fixed pointers are loaded, otherwise we can't
/// weigh them against each other, and may start loading distant scenes first
/// </summary>
[Query]
[None(typeof(StaticScenePointers), typeof(VolatileScenePointers))]
private void ProcessesFixedRealm(in RealmComponent realmComponent, ref FixedScenePointers fixedScenePointers)
{
if (fixedScenePointers.AllPromisesResolved)
StartScenesLoading(realmComponent);
}
private void StartScenesLoading(in RealmComponent realmComponent)
{
if (sortingJobHandle is { IsCompleted: true })
{
sortingJobHandle.Value.Complete();
// Since adding new values is throttled, arrays may be out of sync. They need to be synced to work
if (arraysInSync)
CreatePromisesFromOrderedData(realmComponent.Ipfs);
}
if (sortingJobHandle is { IsCompleted: false }) return;
TeleportUtils.PlayerTeleportingState teleportParcel = TeleportUtils.GetTeleportParcel(World, playerEntity);
int xCoordinate;
int yCoordinate;
if (teleportParcel.IsTeleporting)
{
xCoordinate = teleportParcel.Parcel.x;
yCoordinate = teleportParcel.Parcel.y;
}
else
{
Vector2Int currentParcel = playerTransform.position.ToParcel();
xCoordinate = currentParcel.x;
yCoordinate = currentParcel.y;
}
unsafe
{
OrderedDataNative* dataPtr = orderedDataNative.GetUnsafePtr();
for (var i = 0; i < orderedDataManaged.Count; i++)
{
OrderedDataManaged currentOrderedData = orderedDataManaged[i];
dataPtr[i] = new OrderedDataNative
{
ReferenceListIndex = i,
RawSqrDistance = currentOrderedData.PartitionComponent.RawSqrDistance,
IsBehind = currentOrderedData.PartitionComponent.IsBehind,
IsPlayerInsideParcel = currentOrderedData.SceneDefinitionComponent.Contains(xCoordinate, yCoordinate),
XCoordinate = currentOrderedData.XCoordinate,
OutOfRange = currentOrderedData.PartitionComponent.OutOfRange,
};
}
}
arraysInSync = true;
inTeleport = teleportParcel.IsTeleporting;
sortingJobHandle = orderedDataNative.SortJob(COMPARER_INSTANCE).Schedule();
}
private void CreatePromisesFromOrderedData(IIpfsRealm ipfsRealm)
{
sceneLoadingLimit.ResetCurrentUsage();
promisesCreated = 0;
unsafe
{
int orderedDataNativeLength = orderedDataNative.Length;
if (orderedDataNativeLength == 0) return;
OrderedDataNative* dataPtr = orderedDataNative.GetUnsafePtr();
if (inTeleport)
{
//The parcel we are teleporting to should be the first one
OrderedDataManaged data = orderedDataManaged[dataPtr[0].ReferenceListIndex];
UpdateLoadingState(ipfsRealm, data.Entity, data.SceneDefinitionComponent, data.PartitionComponent, data.SceneLoadingState, data.ISSDescriptor);
return;
}
for (var i = 0; i < orderedDataNativeLength && promisesCreated < realmPartitionSettings.ScenesRequestBatchSize; i++)
{
//Ignore unpartitioned and out of range
//Optimization: remove out of range from list when adding DeleteEntityIntention
if (dataPtr[i].RawSqrDistance < 0 || dataPtr[i].OutOfRange) continue;
OrderedDataManaged data = orderedDataManaged[dataPtr[i].ReferenceListIndex];
UpdateLoadingState(ipfsRealm, data.Entity, data.SceneDefinitionComponent, data.PartitionComponent, data.SceneLoadingState, data.ISSDescriptor);
}
}
}
private void TryUnload(in Entity entity, ref SceneLoadingState sceneState)
{
if (sceneState.PromiseCreated)
Unload(entity, ref sceneState);
}
private void Unload(in Entity entity, ref SceneLoadingState sceneState)
{
sceneState.VisualSceneState = VisualSceneState.UNINITIALIZED;
sceneState.PromiseCreated = false;
sceneState.FullQuality = false;
//We mark it as Defer because, down the line, the entity wont be deleted.
//Either the LOD or the SceneFacade will be removed, but the Entity with the
//SceneDefinitionComponent should persist
World.Add(entity, new DeleteEntityIntention { DeferDeletion = true });
}
private void UpdateLoadingState(IIpfsRealm ipfsRealm, in Entity entity, in SceneDefinitionComponent sceneDefinitionComponent, in PartitionComponent partitionComponent,
SceneLoadingState sceneState, ISSDescriptor issDescriptor)
{
// Promises for banned-scene entities are not consumed downstream; issuing one here would leak its SceneFacade.
if (World.Has<BannedSceneComponent>(entity))
return;
VisualSceneState candidateBy
= visualSceneStateResolver.ResolveVisualSceneState(partitionComponent, sceneDefinitionComponent, sceneState.VisualSceneState, ipfsRealm.SceneUrns.Count > 0);
//If we are over the amount of scenes that can be loaded, we downgrade quality to LOD
if (candidateBy == VisualSceneState.SHOWING_SCENE && !sceneLoadingLimit.CanLoadScene(sceneDefinitionComponent))
{
//Lets do a quality reduction analysis
candidateBy = VisualSceneState.SHOWING_LOD;
}
//Reduce quality
if (candidateBy == VisualSceneState.SHOWING_LOD)
{
if (sceneLoadingLimit.CanLoadLOD(sceneDefinitionComponent))
{
// This LOD is within the full-quality limit, so load it normally. Nothing to do here
sceneState.FullQuality = true;
}
else if (sceneLoadingLimit.CanLoadQualityReductedLOD(sceneDefinitionComponent))
{
if (sceneState.FullQuality)
{
//This wasnt previously quality reducted. Lets try to unload it and on next iteration we will try to load
TryUnload(entity, ref sceneState);
candidateBy = VisualSceneState.UNINITIALIZED;
}
// Reduce the quality of this LOD if we have not yet hit the quality-reduction limit
sceneState.FullQuality = false;
}
else
{
// Nothing else can load. And we need to unload the loaded which are still inside the loading range
TryUnload(entity, ref sceneState);
candidateBy = VisualSceneState.UNINITIALIZED;
}
}
//No new promise is required
if (candidateBy == VisualSceneState.UNINITIALIZED
|| sceneState.VisualSceneState == candidateBy)
return;
// ISS descriptor gate: SHOWING_LOD / SHOWING_SCENE both need the descriptor to be resolved
// before consumers downstream (UpdateSceneLODInfoSystem reads it, GetSceneFacadeIntention
// captures it). If still in Uninitialized state, attach the resolver promise to the entity
// and bail; the next tick re-enters and re-checks. ResolveISSDescriptorSystem consumes the
// promise, mutates the same ISSDescriptor instance in place via MarkResolved, and removes
// the promise component. Because issDescriptor is a class reference cached in
// OrderedDataManaged, the gate sees the resolved state on the next tick without a refetch.
if (issDescriptor.CurrentState == ISSDescriptorState.Uninitialized)
{
if (!World.Has<AssetPromise<ISSDescriptorMetadata, GetISSDescriptorIntention>>(entity))
World.Add(entity, AssetPromise<ISSDescriptorMetadata, GetISSDescriptorIntention>.Create(
World, GetISSDescriptorIntention.For(sceneDefinitionComponent.Definition), partitionComponent));
return;
}
promisesCreated++;
sceneState.PromiseCreated = true;
sceneState.VisualSceneState = candidateBy;
switch (sceneState.VisualSceneState)
{
case VisualSceneState.SHOWING_LOD:
//The SceneLODInfo may still be in the entity, since it remains there until SceneIsReady (Check UnloadSceneLODInfoSystem)
//Therefore, we need to make this check because we dont want to break the entity mutual exclusive state
if (!World.Has<SceneLODInfo>(entity))
World.Add(entity, SceneLODInfo.Create());
break;
default:
//The previous ISceneFacade may not be fully discarded because of its async dispose (Check UnloadSceneSystem)
//Therefore, we need to make this check because we dont want to break the entity mutual exclusive state
if (!World.Has<ISceneFacade>(entity))
World.Add(entity, AssetPromise<ISceneFacade, GetSceneFacadeIntention>.Create(World,
new GetSceneFacadeIntention(sceneDefinitionComponent, issDescriptor), partitionComponent));
break;
}
}
public struct OrdenedDataNativeComparer : IComparer<OrderedDataNative>
{
public int Compare(OrderedDataNative x, OrderedDataNative y)
{
//Out of range always go last
int compareOutOfRange = x.OutOfRange.CompareTo(y.OutOfRange);
if (compareOutOfRange != 0)
return compareOutOfRange;
//Parcels infront should always have higher priority
int compareIsBehind = x.IsBehind.CompareTo(y.IsBehind);
if (compareIsBehind != 0)
return compareIsBehind;
if (x.IsPlayerInsideParcel && !y.IsPlayerInsideParcel) return -1;
if (y.IsPlayerInsideParcel && !x.IsPlayerInsideParcel) return 1;
// discrete distance comparison
int bucketComparison = x.RawSqrDistance.CompareTo(y.RawSqrDistance);
if (bucketComparison != 0)
return bucketComparison;
//If everything fails, the scene on the right has higher priority
return x.XCoordinate.CompareTo(y.XCoordinate);
}
}
public struct OrderedDataNative
{
public int ReferenceListIndex;
public float RawSqrDistance;
public bool IsBehind;
public bool IsPlayerInsideParcel;
public int XCoordinate;
public bool OutOfRange;
}
public class OrderedDataManaged
{
//Need it to get the ref of SceneLoadingState
public Entity Entity;
public readonly SceneDefinitionComponent SceneDefinitionComponent;
public readonly SceneLoadingState SceneLoadingState;
public readonly PartitionComponent PartitionComponent;
// Class-typed component: same reference is mutated in place by ResolveISSDescriptorSystem,
// so the gate below sees live state without a World.Get refresh.
public readonly ISSDescriptor ISSDescriptor;
public int XCoordinate;
public OrderedDataManaged(Entity entity, SceneDefinitionComponent sceneDefinitionComponent, PartitionComponent partitionComponent, SceneLoadingState sceneLoadingState, ISSDescriptor issDescriptor)
{
Entity = entity;
SceneDefinitionComponent = sceneDefinitionComponent;
SceneLoadingState = sceneLoadingState;
PartitionComponent = partitionComponent;
ISSDescriptor = issDescriptor;
XCoordinate = sceneDefinitionComponent.Definition.metadata.scene.DecodedBase.x;
}
}
}
}