Skip to content

Instantly share code, notes, and snippets.

@nullmastermind
Last active June 7, 2023 10:07
Show Gist options
  • Save nullmastermind/43025ea67ad5f942ab89315a5563d433 to your computer and use it in GitHub Desktop.
Save nullmastermind/43025ea67ad5f942ab89315a5563d433 to your computer and use it in GitHub Desktop.
<1ms 10k objects - Hybrid ECS Transform Synchronization https://www.youtube.com/watch?v=5pNEDnXITXw
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.Jobs;
namespace _Source.Scripts.DOTS.Systems {
[BurstCompile]
public struct SyncTransformsJob : IJobParallelForTransform {
public NativeArray<Entity> Entities;
[ReadOnly]
public ComponentLookup<LocalToWorld> LLocalToWorld;
public void Execute(int index, TransformAccess transform) {
var e = Entities[index];
if (!LLocalToWorld.TryGetComponent(e, out var localToWorld)) {
return;
}
transform.position = localToWorld.Position;
transform.rotation = localToWorld.Rotation;
transform.localScale = localToWorld.Value.Scale();
}
}
[UpdateInGroup(typeof(TransformSystemGroup))]
[UpdateAfter(typeof(LocalToWorldSystem))]
public partial class ProcessorSyncTransforms : SystemBase {
private static readonly Stack<int> FreeIds = new();
private ComponentLookup<LocalToWorld> _lLocalToWorld;
private TransformAccessArray _refArray;
private NativeList<Entity> _refEntities;
[BurstCompile]
protected override void OnCreate() {
base.OnCreate();
_refArray = new TransformAccessArray(128);
_refEntities = new NativeList<Entity>(128, Allocator.Persistent);
_lLocalToWorld = GetComponentLookup<LocalToWorld>(true);
}
[BurstCompile]
protected override void OnDestroy() {
base.OnDestroy();
_refArray.Dispose();
_refEntities.Dispose();
}
protected override void OnStopRunning() {
base.OnStopRunning();
FreeIds.Clear();
}
[BurstCompile]
protected override void OnUpdate() {
_lLocalToWorld.Update(this);
// Sync
var transformEntities = _refEntities.ToArray(Allocator.TempJob);
Dependency = new SyncTransformsJob {
Entities = transformEntities,
LLocalToWorld = _lLocalToWorld,
}.Schedule(_refArray, Dependency);
transformEntities.Dispose(Dependency);
}
public int AddTransform(Transform trm, Entity entity) {
int refId;
if (FreeIds.Count > 0) {
refId = FreeIds.Pop();
_refArray[refId] = trm;
_refEntities[refId] = entity;
} else {
refId = _refArray.length;
_refArray.Add(trm);
_refEntities.Add(entity);
}
return refId;
}
public void ReleaseTransform(int id) {
if (id >= 0) {
FreeIds.Push(id);
}
}
}
}
@nullmastermind
Copy link
Author

nullmastermind commented Jun 6, 2023

Example usage:

using _Game.Scripts;
using _Source.Scripts.Utility;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

namespace _Source.Scripts.DOTS.Systems {
  [UpdateInGroup(typeof(InitializationSystemGroup))]
  public partial class ProcessorSpawnBall : SystemBase {
    private EntityQuery _query;
+    private ProcessorSyncTransforms _processorSyncTransforms;

    protected override void OnCreate() {
      base.OnCreate();

      _query = GetEntityQuery(new EntityQueryDesc {
        All = new ComponentType[] {
          typeof(CSyncTransform),
        },
      });

+      _processorSyncTransforms = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<ProcessorSyncTransforms>();

      RequireForUpdate<CPrefabs>();
    }

    protected override void OnUpdate() {
      var prefabs = SystemAPI.GetSingleton<CPrefabs>();
      var startEcb = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(World.Unmanaged);
      var endEcb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(World.Unmanaged);
      var count = _query.CalculateEntityCount();
      var dt = SystemAPI.Time.DeltaTime;

      Entities.ForEach((Entity e, CSyncTransform cSyncTransform, ref LocalTransform localTransform) =>
      {
        if (!cSyncTransform.GameObject) {
          cSyncTransform.GameObject = U.Spawn("Sphere").gameObject;
+          cSyncTransform.TransformId = _processorSyncTransforms.AddTransform(cSyncTransform.GameObject.transform, e);
        }

        if (cSyncTransform.GameObject.transform.position.y < -15) {
          endEcb.DestroyEntity(e);
          U.Despawn("Sphere", cSyncTransform.GameObject.transform);
+          _processorSyncTransforms.ReleaseTransform(cSyncTransform.TransformId);
        }
      }).WithoutBurst().WithAll<CSyncTransform>().Run();

      const int spawnCount = 10000;

      if (count < spawnCount) {
        for (var i = 0; i < math.min(spawnCount - count, 100); i++) {
          startEcb.Instantiate(prefabs.Sphere);
        }
      }
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment