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; } } /// /// Container for all rules in a grid graph. /// /// /// // 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), /// }); /// /// /// See: /// See: grid-rules (view in online documentation for working links) /// [JsonOptIn] public class GridGraphRules { List >[] jobSystemCallbacks; List >[] mainThreadCallbacks; /// List of all rules [JsonMember] List rules = new List(); long lastHash; /// Context for when scanning or updating a graph public class Context { /// Graph which is being scanned or updated public GridGraph graph; /// Data for all the nodes as NativeArrays public GridGraphScanData data; /// /// Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually. /// Always use when scheduling jobs. /// 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 GetRules () { if (rules == null) rules = new List(); 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(); jobSystemCallbacks = jobSystemCallbacks ?? new List >[6]; for (int i = 0; i < jobSystemCallbacks.Length; i++) { if (jobSystemCallbacks[i] != null) jobSystemCallbacks[i].Clear(); } mainThreadCallbacks = mainThreadCallbacks ?? new List >[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 > 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); } } } /// /// Executes the rules for the given pass. /// Call handle.Complete on, or wait for, all yielded job handles. /// public IEnumerator 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 == null) Rebuild(); 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); } /// /// 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. /// public void AddJobSystemPass (GridGraphRule.Pass pass, System.Action action) { var index = (int)pass; if (jobSystemCallbacks[index] == null) { jobSystemCallbacks[index] = new List >(); } jobSystemCallbacks[index].Add(action); } /// /// 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 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. /// public void AddMainThreadPass (GridGraphRule.Pass pass, System.Action action) { var index = (int)pass; if (mainThreadCallbacks[index] == null) { mainThreadCallbacks[index] = new List >(); } mainThreadCallbacks[index].Add(action); } /// Deprecated: Use AddJobSystemPass or AddMainThreadPass instead [System.Obsolete("Use AddJobSystemPass or AddMainThreadPass instead")] public void Add (GridGraphRule.Pass pass, System.Action action) { AddJobSystemPass(pass, action); } } /// /// Custom rule for a grid graph. /// See: /// See: grid-rules (view in online documentation for working links) /// [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 { /// Only enabled rules are executed [JsonMember] public bool enabled = true; int dirty = 1; /// /// Where in the scanning process a rule will be executed. /// Check the documentation for to see which data fields are valid in which passes. /// public enum Pass { /// /// 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. /// BeforeCollision, /// /// 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. /// BeforeConnections, /// /// 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. /// AfterConnections, /// /// After erosion is calculated but before connections have been recalculated. /// /// If no erosion is used then this pass will not be executed. /// AfterErosion, /// /// 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. /// PostProcess, /// /// After the graph update has been applied to the graph. /// /// This pass can only be added as a main-thread pass. /// If many updates are applied to the graph at the same time, this pass will only execute once after all updates have been applied. /// /// Warning: No data in the context except the reference to the graph is valid at this point. It has all been disposed. /// You cannot modify any data in this pass. /// AfterApplied, } /// /// Hash of the settings for this rule. /// The method will be called again whenever the hash changes. /// If the hash does not change it is assumed that the method does not need to be called again. /// public virtual int Hash => dirty; /// /// 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 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. /// public virtual void SetDirty () { dirty++; } /// /// 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 method is guaranteed to be called before the rule is executed again. /// public virtual void DisposeUnmanagedData () { } /// Does preprocessing and adds callbacks to the object public virtual void Register (GridGraphRules rules) { } } }