Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jobify scenes sorting #239

Merged
merged 2 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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