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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
using System.Collections.Generic;
namespace Pathfinding.Graphs.Grid.Rules {
using Pathfinding.Serialization;
using Pathfinding.Jobs;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
public class CustomGridGraphRuleEditorAttribute : System.Attribute {
public System.Type type;
public string name;
public CustomGridGraphRuleEditorAttribute(System.Type type, string name) {
this.type = type;
this.name = name;
}
}
/// <summary>
/// Container for all rules in a grid graph.
///
/// <code>
/// // Get the first grid graph in the scene
/// var gridGraph = AstarPath.active.data.gridGraph;
///
/// gridGraph.rules.AddRule(new Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty {
/// penaltyScale = 10000,
/// curve = AnimationCurve.Linear(0, 0, 90, 1),
/// });
/// </code>
///
/// See: <see cref="Pathfinding.GridGraph.rules"/>
/// See: grid-rules (view in online documentation for working links)
/// </summary>
[JsonOptIn]
public class GridGraphRules {
List<System.Action<Context> >[] jobSystemCallbacks;
List<System.Action<Context> >[] mainThreadCallbacks;
/// <summary>List of all rules</summary>
[JsonMember]
List<GridGraphRule> rules = new List<GridGraphRule>();
long lastHash;
/// <summary>Context for when scanning or updating a graph</summary>
public class Context {
/// <summary>Graph which is being scanned or updated</summary>
public GridGraph graph;
/// <summary>Data for all the nodes as NativeArrays</summary>
public GridGraphScanData data;
/// <summary>
/// Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually.
/// Always use when scheduling jobs.
/// </summary>
public JobDependencyTracker tracker => data.dependencyTracker;
}
public void AddRule (GridGraphRule rule) {
rules.Add(rule);
lastHash = -1;
}
public void RemoveRule (GridGraphRule rule) {
rules.Remove(rule);
lastHash = -1;
}
public IReadOnlyList<GridGraphRule> GetRules () {
if (rules == null) rules = new List<GridGraphRule>();
return rules.AsReadOnly();
}
long Hash () {
long hash = 196613;
for (int i = 0; i < rules.Count; i++) {
if (rules[i] != null && rules[i].enabled) hash = hash * 1572869 ^ (long)rules[i].Hash;
}
return hash;
}
public void RebuildIfNecessary () {
var newHash = Hash();
if (newHash == lastHash && jobSystemCallbacks != null && mainThreadCallbacks != null) return;
lastHash = newHash;
Rebuild();
}
public void Rebuild () {
rules = rules ?? new List<GridGraphRule>();
jobSystemCallbacks = jobSystemCallbacks ?? new List<System.Action<Context> >[6];
for (int i = 0; i < jobSystemCallbacks.Length; i++) {
if (jobSystemCallbacks[i] != null) jobSystemCallbacks[i].Clear();
}
mainThreadCallbacks = mainThreadCallbacks ?? new List<System.Action<Context> >[6];
for (int i = 0; i < mainThreadCallbacks.Length; i++) {
if (mainThreadCallbacks[i] != null) mainThreadCallbacks[i].Clear();
}
for (int i = 0; i < rules.Count; i++) {
if (rules[i].enabled) rules[i].Register(this);
}
}
public void DisposeUnmanagedData () {
if (rules != null) {
for (int i = 0; i < rules.Count; i++) {
if (rules[i] != null) {
rules[i].DisposeUnmanagedData();
rules[i].SetDirty();
}
}
}
}
static void CallActions (List<System.Action<Context> > actions, Context context) {
if (actions != null) {
try {
for (int i = 0; i < actions.Count; i++) actions[i](context);
} catch (System.Exception e) {
UnityEngine.Debug.LogException(e);
}
}
}
/// <summary>
/// Executes the rules for the given pass.
/// Call handle.Complete on, or wait for, all yielded job handles.
/// </summary>
public IEnumerator<JobHandle> ExecuteRule (GridGraphRule.Pass rule, Context context) {
if (jobSystemCallbacks == null) Rebuild();
CallActions(jobSystemCallbacks[(int)rule], context);
if (mainThreadCallbacks[(int)rule] != null && mainThreadCallbacks[(int)rule].Count > 0) {
if (!context.tracker.forceLinearDependencies) yield return context.tracker.AllWritesDependency;
CallActions(mainThreadCallbacks[(int)rule], context);
}
}
public void ExecuteRuleMainThread (GridGraphRule.Pass rule, Context context) {
if (jobSystemCallbacks[(int)rule] != null && jobSystemCallbacks[(int)rule].Count > 0) throw new System.Exception("A job system pass has been added for the " + rule + " pass. " + rule + " only supports main thread callbacks.");
if (context.tracker != null) context.tracker.AllWritesDependency.Complete();
CallActions(mainThreadCallbacks[(int)rule], context);
}
/// <summary>
/// Adds a pass callback that uses the job system.
/// This rule should only schedule jobs using the `Context.tracker` dependency tracker. Data is not safe to access directly in the callback
///
/// This method should only be called from rules in their Register method.
/// </summary>
public void AddJobSystemPass (GridGraphRule.Pass pass, System.Action<Context> action) {
var index = (int)pass;
if (jobSystemCallbacks[index] == null) {
jobSystemCallbacks[index] = new List<System.Action<Context> >();
}
jobSystemCallbacks[index].Add(action);
}
/// <summary>
/// Adds a pass callback that runs in the main thread.
/// The callback may access and modify any data in the context.
/// You do not need to schedule jobs in order to access the data.
///
/// Warning: Not all data in the Context is valid for every pass. For example you cannot access node connections in the BeforeConnections pass
/// since they haven't been calculated yet.
///
/// This is a bit slower than <see cref="AddJobSystemPass"/> since parallelism and the burst compiler cannot be used.
/// But if you need to use non-thread-safe APIs or data then this is a good choice.
///
/// This method should only be called from rules in their Register method.
/// </summary>
public void AddMainThreadPass (GridGraphRule.Pass pass, System.Action<Context> action) {
var index = (int)pass;
if (mainThreadCallbacks[index] == null) {
mainThreadCallbacks[index] = new List<System.Action<Context> >();
}
mainThreadCallbacks[index].Add(action);
}
/// <summary>Deprecated: Use AddJobSystemPass or AddMainThreadPass instead</summary>
[System.Obsolete("Use AddJobSystemPass or AddMainThreadPass instead")]
public void Add (GridGraphRule.Pass pass, System.Action<Context> action) {
AddJobSystemPass(pass, action);
}
}
/// <summary>
/// Custom rule for a grid graph.
/// See: <see cref="GridGraphRules"/>
/// See: grid-rules (view in online documentation for working links)
/// </summary>
[JsonDynamicType]
// Compatibility with old versions
[JsonDynamicTypeAlias("Pathfinding.RuleTexture", typeof(RuleTexture))]
[JsonDynamicTypeAlias("Pathfinding.RuleAnglePenalty", typeof(RuleAnglePenalty))]
[JsonDynamicTypeAlias("Pathfinding.RuleElevationPenalty", typeof(RuleElevationPenalty))]
[JsonDynamicTypeAlias("Pathfinding.RulePerLayerModifications", typeof(RulePerLayerModifications))]
public abstract class GridGraphRule {
/// <summary>Only enabled rules are executed</summary>
[JsonMember]
public bool enabled = true;
int dirty = 1;
/// <summary>
/// Where in the scanning process a rule will be executed.
/// Check the documentation for <see cref="GridGraphScanData"/> to see which data fields are valid in which passes.
/// </summary>
public enum Pass {
/// <summary>
/// Before the collision testing phase but after height testing.
/// This is very early. Most data is not valid by this point.
///
/// You can use this if you want to modify the node positions and still have it picked up by the collision testing code.
/// </summary>
BeforeCollision,
/// <summary>
/// Before connections are calculated.
/// At this point height testing and collision testing has been done (if they are enabled).
///
/// This is the most common pass to use.
/// If you are modifying walkability here then connections and erosion will be calculated properly.
/// </summary>
BeforeConnections,
/// <summary>
/// After connections are calculated.
///
/// If you are modifying connections directly you should do that in this pass.
///
/// Note: If erosion is used then this pass will be executed twice. One time before erosion and one time after erosion
/// when the connections are calculated again.
/// </summary>
AfterConnections,
/// <summary>
/// After erosion is calculated but before connections have been recalculated.
///
/// If no erosion is used then this pass will not be executed.
/// </summary>
AfterErosion,
/// <summary>
/// After everything else.
/// This pass is executed after everything else is done.
/// You should not modify walkability in this pass because then the node connections will not be up to date.
/// </summary>
PostProcess,
/// <summary>
/// After the graph update has been applied to the graph.
///
/// This pass can only be added as a main-thread pass.
///
/// Warning: No native data in the context is valid at this point. It has all been disposed.
/// You cannot modify any data in this pass.
/// </summary>
AfterApplied,
}
/// <summary>
/// Hash of the settings for this rule.
/// The <see cref="Register"/> method will be called again whenever the hash changes.
/// If the hash does not change it is assumed that the <see cref="Register"/> method does not need to be called again.
/// </summary>
public virtual int Hash => dirty;
/// <summary>
/// Call if you have changed any setting of the rule.
/// This will ensure that any cached data the rule uses is rebuilt.
/// If you do not do this then any settings changes may not affect the graph when it is rescanned or updated.
///
/// The purpose of this method call is to cause the <see cref="Hash"/> property to change. If your custom rule overrides the Hash property to
/// return a hash of some settings, then you do not need to call this method for the changes the hash function already accounts for.
/// </summary>
public virtual void SetDirty () {
dirty++;
}
/// <summary>
/// Called when the rule is removed or the graph is destroyed.
/// Use this to e.g. clean up any NativeArrays that the rule uses.
///
/// Note: The rule should remain valid after this method has been called.
/// However the <see cref="Register"/> method is guaranteed to be called before the rule is executed again.
/// </summary>
public virtual void DisposeUnmanagedData () {
}
/// <summary>Does preprocessing and adds callbacks to the <see cref="GridGraphRules"/> object</summary>
public virtual void Register (GridGraphRules rules) {
}
}
}
|