Skip to content

Commit

Permalink
Jobify scenes sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhail-dcl committed Dec 29, 2023
1 parent 82f5825 commit 107f1f6
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ public class DistanceBasedComparer : IComparer<IPartitionComponent>
{
public static readonly DistanceBasedComparer INSTANCE = new ();

public int Compare(IPartitionComponent x, IPartitionComponent y)
public int Compare(IPartitionComponent x, IPartitionComponent y) =>
Compare(new DataSurrogate(x.RawSqrDistance, x.IsBehind), new DataSurrogate(y.RawSqrDistance, y.IsBehind));

public static int Compare(DataSurrogate x, DataSurrogate y)
{
// discrete distance comparison
// break down by SQR_PARCEL_SIZE
Expand All @@ -19,5 +22,20 @@ public int Compare(IPartitionComponent x, IPartitionComponent y)
int bucketComparison = xParcelBucket.CompareTo(yParcelBucket);
return bucketComparison != 0 ? bucketComparison : x.IsBehind.CompareTo(y.IsBehind);
}

/// <summary>
/// Blittable data to be used in the comparer
/// </summary>
public readonly struct DataSurrogate
{
public readonly bool IsBehind;
public readonly float RawSqrDistance;

public DataSurrogate(float rawSqrDistance, bool isBehind)
{
RawSqrDistance = rawSqrDistance;
IsBehind = isBehind;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
using SceneRunner.Scene;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Pool;
using Utility;

namespace ECS.SceneLifeCycle.IncreasingRadius
Expand All @@ -31,24 +32,31 @@ namespace ECS.SceneLifeCycle.IncreasingRadius
[UpdateAfter(typeof(CreateEmptyPointersInFixedRealmSystem))]
public partial class ResolveSceneStateByIncreasingRadiusSystem : BaseUnityLoopSystem
{
private static readonly Comparer COMPARER_INSTANCE = new ();

private static readonly QueryDescription START_SCENES_LOADING = new QueryDescription()
.WithAll<SceneDefinitionComponent, PartitionComponent>()
.WithNone<ISceneFacade, AssetPromise<ISceneFacade, GetSceneFacadeIntention>>();

private readonly IRealmPartitionSettings realmPartitionSettings;

private readonly List<OrderedData> orderedData;
internal JobHandle? sortingJobHandle;

private NativeList<OrderedData> orderedData;

internal ResolveSceneStateByIncreasingRadiusSystem(World world, IRealmPartitionSettings realmPartitionSettings) : base(world)
{
this.realmPartitionSettings = realmPartitionSettings;

orderedData = ListPool<OrderedData>.Get();
// Set initial capacity to 1/3 of the total capacity required for all rings
orderedData = new NativeList<OrderedData>(
ParcelMathJobifiedHelper.GetRingsArraySize(realmPartitionSettings.MaxLoadingDistanceInParcels) / 3,
Allocator.Persistent);
}

public override void Dispose()
{
ListPool<OrderedData>.Release(orderedData);
orderedData.Dispose();
}

protected override void Update(float t)
Expand Down Expand Up @@ -103,19 +111,26 @@ private void ProcessesFixedRealm([Data] float maxLoadingSqrDistance, ref RealmCo

private void StartScenesLoading(ref RealmComponent realmComponent, float maxLoadingSqrDistance)
{
if (sortingJobHandle is { IsCompleted: true })
{
sortingJobHandle.Value.Complete();
CreatePromisesFromOrderedData(realmComponent.Ipfs);
}

if (sortingJobHandle is { IsCompleted: false }) return;

// Start new sorting
// Order the scenes definitions by the CURRENT partition and serve first N of them

orderedData.Clear();

foreach (ref Chunk chunk in World.Query(in START_SCENES_LOADING))
{
ref Entity entityFirstElement = ref chunk.Entity(0);
ref SceneDefinitionComponent sceneDefinitionFirst = ref chunk.GetFirst<SceneDefinitionComponent>();
ref PartitionComponent partitionComponentFirst = ref chunk.GetFirst<PartitionComponent>();

foreach (int entityIndex in chunk)
{
ref SceneDefinitionComponent definition = ref Unsafe.Add(ref sceneDefinitionFirst, entityIndex);
ref readonly Entity entity = ref Unsafe.Add(ref entityFirstElement, entityIndex);
ref PartitionComponent partitionComponent = ref Unsafe.Add(ref partitionComponentFirst, entityIndex);

Expand All @@ -124,27 +139,37 @@ private void StartScenesLoading(ref RealmComponent realmComponent, float maxLoad
orderedData.Add(new OrderedData
{
Entity = entity,
PartitionComponent = partitionComponent,
DefinitionComponent = definition,
Data = new DistanceBasedComparer.DataSurrogate(partitionComponent.RawSqrDistance, partitionComponent.IsBehind),
});
}
}

// Raw Distance will give more stable results in terms of scenes loading order, especially in cases
// when a wide range falls into the same bucket
orderedData.Sort(static (p1, p2) => DistanceBasedComparer.INSTANCE.Compare(p1.PartitionComponent, p2.PartitionComponent));
sortingJobHandle = orderedData.SortJob(COMPARER_INSTANCE).Schedule();
}

IIpfsRealm ipfsRealm = realmComponent.Ipfs;
private void CreatePromisesFromOrderedData(IIpfsRealm ipfsRealm)
{
var promisesCreated = 0;

// Take first N
for (var i = 0; i < orderedData.Count && i < realmPartitionSettings.ScenesRequestBatchSize; i++)
for (var i = 0; i < orderedData.Length && promisesCreated < realmPartitionSettings.ScenesRequestBatchSize; i++)
{
OrderedData data = orderedData[i];

// As sorting is throttled Entity might gone out of scope
if (!World.IsAlive(data.Entity))
continue;

// We can't save component to data as sorting is throttled and components could change
Components<SceneDefinitionComponent, PartitionComponent> components = World.Get<SceneDefinitionComponent, PartitionComponent>(data.Entity);

World.Add(data.Entity,
AssetPromise<ISceneFacade, GetSceneFacadeIntention>.Create(World,
new GetSceneFacadeIntention(ipfsRealm, data.DefinitionComponent),
data.PartitionComponent));
new GetSceneFacadeIntention(ipfsRealm, components.t0),
components.t1.Value));

promisesCreated++;
}
}

Expand All @@ -157,11 +182,22 @@ private void StartUnloading([Data] float unloadingSqrDistance, in Entity entity,
World.Add(entity, DeleteEntityIntention.DeferredDeletion);
}

/// <summary>
/// It must be a structure to be compatible with Burst SortJob
/// </summary>
private struct Comparer : IComparer<OrderedData>
{
public int Compare(OrderedData x, OrderedData y) =>
DistanceBasedComparer.Compare(x.Data, y.Data);
}

private struct OrderedData
{
public PartitionComponent PartitionComponent;
public SceneDefinitionComponent DefinitionComponent;
/// <summary>
/// Referencing entity is expensive and at the moment we don't delete scene entities at all
/// </summary>
public Entity Entity;
public DistanceBasedComparer.DataSurrogate Data;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using Utility;

Expand All @@ -32,7 +33,7 @@ public void SetUp()
}

[Test]
public void LimitScenesLoading()
public async Task LimitScenesLoading()
{
realmPartitionSettings.ScenesRequestBatchSize.Returns(2);
realmPartitionSettings.MaxLoadingDistanceInParcels.Returns(int.MaxValue);
Expand All @@ -56,6 +57,15 @@ public void LimitScenesLoading()

system.Update(0f);

// Wait for job to complete
while (!system.sortingJobHandle.Value.IsCompleted)
{
await Task.Yield();
system.Update(0f);
}

system.Update(0f);

// Serve 2
var entities = new List<Entity>();
world.GetEntities(new QueryDescription().WithAll<GetSceneFacadeIntention>(), entities);
Expand Down
13 changes: 10 additions & 3 deletions Explorer/Assets/Scripts/Utility/ParcelMathJobifiedHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Unity.Burst;
using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
Expand Down Expand Up @@ -29,11 +30,17 @@ private void EnsureRingsArraySize(int maxRadius)
if (rings.Length != maxRadius)
{
rings.Dispose();
int d = (maxRadius * 2) + 1;
rings = new NativeArray<ParcelInfo>(d * d, Allocator.Persistent);
rings = new NativeArray<ParcelInfo>(GetRingsArraySize(maxRadius), Allocator.Persistent);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetRingsArraySize(int maxRadius)
{
int d = (maxRadius * 2) + 1;
return d * d;
}

/// <summary>
/// Schedule a job so it can be completed later
/// </summary>
Expand Down

0 comments on commit 107f1f6

Please sign in to comment.