summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Misc/GraphModifier.cs
blob: b94de4f70f2849b1a6839b247d2c6b90c53996ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Util;

namespace Pathfinding {
	/// <summary>
	/// GraphModifier is used for modifying graphs or processing graph data based on events.
	/// This class is a simple container for a number of events.
	///
	/// \borderlessimage{graph_events.png}
	///
	/// Warning: Some events will be called both in play mode <b>and in editor mode</b> (at least the scan events).
	/// So make sure your code handles both cases well. You may choose to ignore editor events.
	/// See: Application.IsPlaying
	///
	/// Warning: Events may be received before Awake and OnEnable has been called on the component. This is because
	/// graphs are typically scanned during Awake on the AstarPath component, which may happen before Awake on the graph modifier itself.
	/// </summary>
	[ExecuteInEditMode]
	public abstract class GraphModifier : VersionedMonoBehaviour {
		/// <summary>All active graph modifiers</summary>
		private static GraphModifier root;

		private GraphModifier prev;
		private GraphModifier next;

		/// <summary>Unique persistent ID for this component, used for serialization</summary>
		[SerializeField]
		[HideInInspector]
		protected ulong uniqueID;

		/// <summary>Maps persistent IDs to the component that uses it</summary>
		protected static Dictionary<ulong, GraphModifier> usedIDs = new Dictionary<ulong, GraphModifier>();

		protected static List<T> GetModifiersOfType<T>() where T : GraphModifier {
			var current = root;
			var result = new List<T>();

			while (current != null) {
				var cast = current as T;
				if (cast != null) result.Add(cast);
				current = current.next;
			}
			return result;
		}

		public static void FindAllModifiers () {
			var allModifiers = UnityCompatibility.FindObjectsByTypeSorted<GraphModifier>();

			for (int i = 0; i < allModifiers.Length; i++) {
				if (allModifiers[i].enabled) {
					if (allModifiers[i].next == null) {
						// The modifier is not yet registered. Presumably it is enabled,
						// but unity hasn't had time to call OnEnable yet.
						// Disabling it and enabling it will force unity to call OnEnable immediately.
						// We don't want to call it ourselves, because then Unity won't know that it has been called,
						// which could cause issues for lifecycle management.
						// For example, if we called OnEnable manually (before Unity did), and then the object was destroyed
						// before Unity had a chance to call OnEnable, then Unity would not call OnDisable.
						allModifiers[i].enabled = false;
						allModifiers[i].enabled = true;
					}
				}
			}
		}

		/// <summary>GraphModifier event type</summary>
		public enum EventType {
			PostScan = 1 << 0,
			PreScan = 1 << 1,
			LatePostScan = 1 << 2,
			PreUpdate = 1 << 3,
			PostUpdate = 1 << 4,
			PostCacheLoad = 1 << 5,
			PostUpdateBeforeAreaRecalculation = 1 << 6,
			PostGraphLoad = 1 << 7,
		}

		/// <summary>Triggers an event for all active graph modifiers</summary>
		public static void TriggerEvent (GraphModifier.EventType type) {
			if (!Application.isPlaying) {
				FindAllModifiers();
			}

			try {
				GraphModifier c = root;
				switch (type) {
				case EventType.PreScan:
					while (c != null) { c.OnPreScan(); c = c.next; }
					break;
				case EventType.PostScan:
					while (c != null) { c.OnPostScan(); c = c.next; }
					break;
				case EventType.LatePostScan:
					while (c != null) { c.OnLatePostScan(); c = c.next; }
					break;
				case EventType.PreUpdate:
					while (c != null) { c.OnGraphsPreUpdate(); c = c.next; }
					break;
				case EventType.PostUpdate:
					while (c != null) { c.OnGraphsPostUpdate(); c = c.next; }
					break;
				case EventType.PostUpdateBeforeAreaRecalculation:
					while (c != null) { c.OnGraphsPostUpdateBeforeAreaRecalculation(); c = c.next; }
					break;
				case EventType.PostCacheLoad:
					while (c != null) { c.OnPostCacheLoad(); c = c.next; }
					break;
				case EventType.PostGraphLoad:
					while (c != null) { c.OnPostGraphLoad(); c = c.next; }
					break;
				}
			} catch (System.Exception e) {
				Debug.LogException(e);
			}
		}

		/// <summary>Adds this modifier to list of active modifiers</summary>
		protected virtual void OnEnable () {
			RemoveFromLinkedList();
			AddToLinkedList();
			ConfigureUniqueID();
		}

		/// <summary>Removes this modifier from list of active modifiers</summary>
		protected virtual void OnDisable () {
			RemoveFromLinkedList();
		}

		protected override void Awake () {
			base.Awake();
			ConfigureUniqueID();
		}

		void ConfigureUniqueID () {
			// Check if any other object is using the same uniqueID
			// In that case this object may have been duplicated
			GraphModifier usedBy;

			if (usedIDs.TryGetValue(uniqueID, out usedBy) && usedBy != this) {
				Reset();
			}

			usedIDs[uniqueID] = this;
		}

		void AddToLinkedList () {
			if (root == null) {
				root = this;
			} else {
				next = root;
				root.prev = this;
				root = this;
			}
		}

		void RemoveFromLinkedList () {
			if (root == this) {
				root = next;
				if (root != null) root.prev = null;
			} else {
				if (prev != null) prev.next = next;
				if (next != null) next.prev = prev;
			}
			prev = null;
			next = null;
		}

		protected virtual void OnDestroy () {
			usedIDs.Remove(uniqueID);
		}

		/// <summary>
		/// Called right after all graphs have been scanned.
		///
		/// Note: Area information (see <see cref="Pathfinding.HierarchicalGraph)"/> may not be up to date when this event is sent.
		/// This means some methods like <see cref="Pathfinding.PathUtilities.IsPathPossible"/> may return incorrect results.
		/// Use <see cref="OnLatePostScan"/> if you need that info to be up to date.
		///
		/// See: OnLatePostScan
		/// </summary>
		public virtual void OnPostScan () {}

		/// <summary>
		/// Called right before graphs are going to be scanned.
		///
		/// See: OnLatePostScan
		/// </summary>
		public virtual void OnPreScan () {}

		/// <summary>
		/// Called at the end of the scanning procedure.
		/// This is the absolute last thing done by Scan.
		/// </summary>
		public virtual void OnLatePostScan () {}

		/// <summary>
		/// Called after cached graphs have been loaded.
		/// When using cached startup, this event is analogous to OnLatePostScan and implementing scripts
		/// should do roughly the same thing for both events.
		/// </summary>
		public virtual void OnPostCacheLoad () {}

		/// <summary>
		/// Called after a graph has been deserialized and loaded.
		/// Note: The graph may not have had any valid node data, it might just contain the graph settings.
		///
		/// This will be called often outside of play mode. Make sure to check Application.isPlaying if appropriate.
		/// </summary>
		public virtual void OnPostGraphLoad () {}

		/// <summary>Called before graphs are updated using GraphUpdateObjects</summary>
		public virtual void OnGraphsPreUpdate () {}

		/// <summary>
		/// Called after graphs have been updated using GraphUpdateObjects or navmesh cutting.
		///
		/// This is among other times called after graphs have been scanned, updated using GraphUpdateObjects, navmesh cuts, or GraphUpdateScene components.
		///
		/// Area recalculations (see <see cref="Pathfinding.HierarchicalGraph"/>) have been done at this stage so things like PathUtilities.IsPathPossible will work.
		///
		/// Use <see cref="OnGraphsPostUpdateBeforeAreaRecalculation"/> instead if you are modifying the graph in any way, especially connections and walkability.
		/// This is because if you do this then area recalculations
		/// </summary>
		public virtual void OnGraphsPostUpdate () {}

		/// <summary>
		/// Called after graphs have been updated.
		///
		/// This is among other times called after graphs have been scanned, updated using GraphUpdateObjects, navmesh cuts, or GraphUpdateScene components.
		///
		/// Note: Area information (see <see cref="Pathfinding.HierarchicalGraph)"/> may not be up to date when this event is sent.
		/// This means some methods like <see cref="Pathfinding.PathUtilities.IsPathPossible"/> may return incorrect results.
		/// Use <see cref="OnLatePostScan"/> if you need that info to be up to date.
		///
		/// Use this if you are modifying any graph connections or walkability.
		///
		/// See: <see cref="OnGraphsPostUpdate"/>
		/// </summary>
		public virtual void OnGraphsPostUpdateBeforeAreaRecalculation () {}

		protected override void Reset () {
			base.Reset();
			// Create a new random 64 bit value (62 bit actually because we skip negative numbers, but that's still enough by a huge margin)
			var rnd1 = (ulong)Random.Range(0, int.MaxValue);
			var rnd2 = ((ulong)Random.Range(0, int.MaxValue) << 32);

			uniqueID = rnd1 | rnd2;
			usedIDs[uniqueID] = this;
		}
	}
}