From 8722a9920c1f6119bf6e769cba270e63097f8e25 Mon Sep 17 00:00:00 2001 From: chai <215380520@qq.com> Date: Thu, 23 May 2024 10:08:29 +0800 Subject: + astar project --- .../Utilities/BatchedEvents.cs | 243 +++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/BatchedEvents.cs') 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 { + /// Helper for batching updates to many objects efficiently + [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 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() 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(); + DontDestroyOnLoad(go); + } + + public static T Find(K key, System.Func 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 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(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 obj) where T : IEntityIndex => obj.EntityIndex != 0; + + public static void Add(T obj, Event eventTypes, System.Action action, int archetypeVariant = 0) where T : Component, IEntityIndex { + Add(obj, eventTypes, null, action, archetypeVariant); + } + + public static void Add(T obj, Event eventTypes, System.Action action, int archetypeVariant = 0) where T : Component, IEntityIndex { + Add(obj, eventTypes, action, null, archetypeVariant); + } + + static void Add(T obj, Event eventTypes, System.Action action1, System.Action 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 a1 = (objs, count, tr, ev) => ac1((T[])objs, count, tr, ev); + System.Action 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(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); + } + } +} -- cgit v1.1-26-g67d0