summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-05-23 10:08:29 +0800
committerchai <215380520@qq.com>2024-05-23 10:08:29 +0800
commit8722a9920c1f6119bf6e769cba270e63097f8e25 (patch)
tree2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs
parent3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff)
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs243
1 files changed, 243 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs
new file mode 100644
index 0000000..f8a0a7e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs
@@ -0,0 +1,243 @@
+using Unity.Mathematics;
+using UnityEngine;
+using UnityEngine.Jobs;
+using UnityEngine.Profiling;
+
+namespace Pathfinding.Util {
+ /// <summary>Helper for batching updates to many objects efficiently</summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/batchedevents.html")]
+ public class BatchedEvents : VersionedMonoBehaviour {
+ const int ArchetypeOffset = 22;
+ const int ArchetypeMask = 0xFF << ArchetypeOffset;
+
+ static Archetype[] data = new Archetype[0];
+ static BatchedEvents instance;
+ static int isIteratingOverTypeIndex = -1;
+ static bool isIterating = false;
+
+ [System.Flags]
+ public enum Event {
+ Update = 1 << 0,
+ LateUpdate = 1 << 1,
+ FixedUpdate = 1 << 2,
+ Custom = 1 << 3,
+ None = 0,
+ };
+
+
+ struct Archetype {
+ public object[] objects;
+ public int objectCount;
+ public System.Type type;
+ public TransformAccessArray transforms;
+ public int variant;
+ public int archetypeIndex;
+ public Event events;
+ public System.Action<object[], int, TransformAccessArray, Event> action;
+ public CustomSampler sampler;
+
+ public void Add (Component obj) {
+ objectCount++;
+ UnityEngine.Assertions.Assert.IsTrue(objectCount < (1 << ArchetypeOffset));
+ if (objects == null) objects = (object[])System.Array.CreateInstance(type, math.ceilpow2(objectCount));
+ if (objectCount > objects.Length) {
+ var newObjects = System.Array.CreateInstance(type, math.ceilpow2(objectCount));
+ objects.CopyTo(newObjects, 0);
+ objects = (object[])newObjects;
+ }
+ objects[objectCount-1] = obj;
+ if (!transforms.isCreated) transforms = new TransformAccessArray(16, -1);
+ transforms.Add(obj.transform);
+ ((IEntityIndex)obj).EntityIndex = (archetypeIndex << ArchetypeOffset) | (objectCount-1);
+ }
+
+ public void Remove (int index) {
+ objectCount--;
+ ((IEntityIndex)objects[objectCount]).EntityIndex = (archetypeIndex << ArchetypeOffset) | index;
+ ((IEntityIndex)objects[index]).EntityIndex = 0;
+ objects[index] = objects[objectCount];
+ objects[objectCount] = null;
+ transforms.RemoveAtSwapBack(index);
+
+ if (objectCount == 0) transforms.Dispose();
+ }
+ }
+
+#if UNITY_EDITOR
+ void DelayedDestroy () {
+ UnityEditor.EditorApplication.update -= DelayedDestroy;
+ GameObject.DestroyImmediate(gameObject);
+ }
+#endif
+
+ void OnEnable () {
+ if (instance == null) instance = this;
+ if (instance != this) {
+ // We cannot destroy the object while it is being enabled, so we need to delay it a bit
+#if UNITY_EDITOR
+ // This is only important in the editor to avoid a build-up of old managers.
+ // In an actual game at most 1 (though in practice zero) old managers will be laying around.
+ // It would be nice to use a coroutine for this instead, but unfortunately they do not work for objects marked with HideAndDontSave.
+ UnityEditor.EditorApplication.update += DelayedDestroy;
+#endif
+ }
+ }
+
+ static void CreateInstance () {
+ // If scripts are recompiled the the static variable will be lost.
+ // Some users recompile scripts in play mode and then reload the scene (https://forum.arongranberg.com/t/rts-game-pathfinding/6623/48?u=aron_granberg)
+ // which makes handling this a requirement.
+
+ // Here one might try to look for existing instances of the class that haven't yet been enabled.
+ // However, this turns out to be tricky.
+ // Resources.FindObjectsOfTypeAll<T>() is the only call that includes HideInInspector GameObjects.
+ // But it is hard to distinguish between objects that are internal ones which will never be enabled and objects that will be enabled.
+ // Checking .gameObject.scene.isLoaded doesn't work reliably (object may be enabled and working even if isLoaded is false)
+ // Checking .gameObject.scene.isValid doesn't work reliably (object may be enabled and working even if isValid is false)
+
+ // So instead we just always create a new instance. This is not a particularly heavy operation and it only happens once per game, so why not.
+ // The OnEnable call will clean up duplicate managers if there are any.
+
+ var go = new GameObject("Batch Helper") {
+ hideFlags = HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInInspector | HideFlags.HideInHierarchy
+ };
+
+ instance = go.AddComponent<BatchedEvents>();
+ DontDestroyOnLoad(go);
+ }
+
+ public static T Find<T, K>(K key, System.Func<T, K, bool> predicate) where T : class, IEntityIndex {
+ var t = typeof(T);
+ for (int i = 0; i < data.Length; i++) {
+ if (data[i].type == t) {
+ var objs = data[i].objects as T[];
+ for (int j = 0; j < data[i].objectCount; j++) {
+ if (predicate(objs[j], key)) return objs[j];
+ }
+ }
+ }
+ return null;
+ }
+
+ public static void Remove<T>(T obj) where T : IEntityIndex {
+ int index = obj.EntityIndex;
+
+ if (index == 0) return;
+
+ var archetypeIndex = ((index & ArchetypeMask) >> ArchetypeOffset) - 1;
+ index &= ~ArchetypeMask;
+ UnityEngine.Assertions.Assert.IsTrue(data[archetypeIndex].type == obj.GetType());
+
+ if (isIterating && isIteratingOverTypeIndex == archetypeIndex) throw new System.Exception("Cannot add or remove entities during an event (Update/LateUpdate/...) that this helper initiated");
+ data[archetypeIndex].Remove(index);
+ }
+
+ public static int GetComponents<T>(Event eventTypes, out TransformAccessArray transforms, out T[] components) where T : Component, IEntityIndex {
+ if (instance == null) CreateInstance();
+
+ // Add in a hash of the event types
+ var archetypeVariant = (int)eventTypes * 12582917;
+ if (isIterating && isIteratingOverTypeIndex == archetypeVariant) throw new System.Exception("Cannot add or remove entities during an event (Update/LateUpdate/...) that this helper initiated");
+
+ var type = typeof(T);
+ for (int i = 0; i < data.Length; i++) {
+ if (data[i].type == type && data[i].variant == archetypeVariant) {
+ transforms = data[i].transforms;
+ components = data[i].objects as T[];
+ return data[i].objectCount;
+ }
+ }
+
+ transforms = default;
+ components = null;
+ return 0;
+ }
+
+ public static bool Has<T>(T obj) where T : IEntityIndex => obj.EntityIndex != 0;
+
+ public static void Add<T>(T obj, Event eventTypes, System.Action<T[], int> action, int archetypeVariant = 0) where T : Component, IEntityIndex {
+ Add(obj, eventTypes, null, action, archetypeVariant);
+ }
+
+ public static void Add<T>(T obj, Event eventTypes, System.Action<T[], int, TransformAccessArray, Event> action, int archetypeVariant = 0) where T : Component, IEntityIndex {
+ Add(obj, eventTypes, action, null, archetypeVariant);
+ }
+
+ static void Add<T>(T obj, Event eventTypes, System.Action<T[], int, TransformAccessArray, Event> action1, System.Action<T[], int> action2, int archetypeVariant = 0) where T : Component, IEntityIndex {
+ if (obj.EntityIndex != 0) {
+ throw new System.ArgumentException("This object is already registered. Call Remove before adding the object again.");
+ }
+
+ if (instance == null) CreateInstance();
+
+ // Add in a hash of the event types
+ archetypeVariant = (int)eventTypes * 12582917;
+ if (isIterating && isIteratingOverTypeIndex == archetypeVariant) throw new System.Exception("Cannot add or remove entities during an event (Update/LateUpdate/...) that this helper initiated");
+
+
+ var type = obj.GetType();
+ for (int i = 0; i < data.Length; i++) {
+ if (data[i].type == type && data[i].variant == archetypeVariant) {
+ data[i].Add(obj);
+ return;
+ }
+ }
+
+ {
+ Memory.Realloc(ref data, data.Length + 1);
+ // A copy is made here so that these variables are captured by the lambdas below instead of the original action1/action2 parameters.
+ // If this is not done then the C# JIT will allocate a lambda capture object every time this function is executed
+ // instead of only when we need to create a new archetype. Doing that would create a lot more unnecessary garbage.
+ var ac1 = action1;
+ var ac2 = action2;
+ System.Action<object[], int, TransformAccessArray, Event> a1 = (objs, count, tr, ev) => ac1((T[])objs, count, tr, ev);
+ System.Action<object[], int, TransformAccessArray, Event> a2 = (objs, count, tr, ev) => ac2((T[])objs, count);
+ data[data.Length - 1] = new Archetype {
+ type = type,
+ events = eventTypes,
+ variant = archetypeVariant,
+ archetypeIndex = (data.Length - 1) + 1, // Note: offset by +1 to ensure that entity index = 0 is an invalid index
+ action = ac1 != null ? a1 : a2,
+ sampler = CustomSampler.Create(type.Name),
+ };
+ data[data.Length - 1].Add(obj);
+ }
+ }
+
+ void Process (Event eventType, System.Type typeFilter) {
+ try {
+ isIterating = true;
+ for (int i = 0; i < data.Length; i++) {
+ ref var archetype = ref data[i];
+ if (archetype.objectCount > 0 && (archetype.events & eventType) != 0 && (typeFilter == null || typeFilter == archetype.type)) {
+ isIteratingOverTypeIndex = archetype.variant;
+ try {
+ archetype.sampler.Begin();
+ archetype.action(archetype.objects, archetype.objectCount, archetype.transforms, eventType);
+ } finally {
+ archetype.sampler.End();
+ }
+ }
+ }
+ } finally {
+ isIterating = false;
+ }
+ }
+
+ public static void ProcessEvent<T>(Event eventType) {
+ instance?.Process(eventType, typeof(T));
+ }
+
+ void Update () {
+ Process(Event.Update, null);
+ }
+
+ void LateUpdate () {
+ Process(Event.LateUpdate, null);
+ }
+
+ void FixedUpdate () {
+ Process(Event.FixedUpdate, null);
+ }
+ }
+}