using System; using System.Collections.Generic; using System.Linq; using UnityEngine.Experimental.LowLevel; using UnityEngine.Experimental.PlayerLoop; namespace Unity.Entities { // Updating before or after an engine system guarantees it is in the same update phase as the dependency // Update After a phase means in that pase but after all engine systems, Before a phase means in that phase but before all engine systems [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class UpdateBeforeAttribute : Attribute { public UpdateBeforeAttribute(Type systemType) { SystemType = systemType; } public Type SystemType { get; } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class UpdateAfterAttribute : Attribute { public UpdateAfterAttribute(Type systemType) { SystemType = systemType; } public Type SystemType { get; } } // Updating in a group means all dependencies from that group are inherited. A system can be in multiple goups // There is nothing preventing systems from being in multiple groups, it can be added if there is a use-case for it [AttributeUsage(AttributeTargets.Class)] public class UpdateInGroupAttribute : Attribute { public UpdateInGroupAttribute(Type groupType) { GroupType = groupType; } public Type GroupType { get; } } public static class ScriptBehaviourUpdateOrder { // Try to find a system of the specified type in the default playerloop and update the min / max insertion position private static void UpdateInsertionPos(DependantBehavior target, Type dep, PlayerLoopSystem defaultPlayerLoop, bool after) { var pos = 0; foreach (var sys in defaultPlayerLoop.subSystemList) { ++pos; if (sys.type == dep) { if (after) { pos += sys.subSystemList.Length; if (target.MinInsertPos < pos) target.MinInsertPos = pos; if (target.MaxInsertPos == 0 || target.MaxInsertPos > pos) target.MaxInsertPos = pos; } else { if (target.MinInsertPos < pos) target.MinInsertPos = pos; if (target.MaxInsertPos == 0 || target.MaxInsertPos > pos) target.MaxInsertPos = pos; } return; } var beginPos = pos; var endPos = pos + sys.subSystemList.Length; foreach (var subsys in sys.subSystemList) { if (subsys.type == dep) { if (after) { ++pos; if (target.MinInsertPos < pos) target.MinInsertPos = pos; if (target.MaxInsertPos == 0 || target.MaxInsertPos > endPos) target.MaxInsertPos = endPos; } else { if (target.MinInsertPos < beginPos) target.MinInsertPos = beginPos; if (target.MaxInsertPos == 0 || target.MaxInsertPos > pos) target.MaxInsertPos = pos; } return; } ++pos; } } // System was not found } private static void AddDependencies(DependantBehavior targetSystem, IReadOnlyDictionary dependencies, IReadOnlyDictionary allGroups, PlayerLoopSystem defaultPlayerLoop) { var target = targetSystem.Manager.GetType(); var attribs = target.GetCustomAttributes(typeof(UpdateAfterAttribute), true); foreach (var attr in attribs) { var attribDep = attr as UpdateAfterAttribute; DependantBehavior otherSystem; ScriptBehaviourGroup otherGroup; if (dependencies.TryGetValue(attribDep.SystemType, out otherSystem)) { targetSystem.UpdateAfter.Add(attribDep.SystemType); otherSystem.UpdateBefore.Add(target); } else if (allGroups.TryGetValue(attribDep.SystemType, out otherGroup)) { otherGroup.AddUpdateBeforeToAllChildBehaviours(targetSystem, dependencies); } else { UpdateInsertionPos(targetSystem, attribDep.SystemType, defaultPlayerLoop, true); } } attribs = target.GetCustomAttributes(typeof(UpdateBeforeAttribute), true); foreach (var attr in attribs) { var attribDep = attr as UpdateBeforeAttribute; DependantBehavior otherSystem; ScriptBehaviourGroup otherGroup; if (dependencies.TryGetValue(attribDep.SystemType, out otherSystem)) { targetSystem.UpdateBefore.Add(attribDep.SystemType); otherSystem.UpdateAfter.Add(target); } else if (allGroups.TryGetValue(attribDep.SystemType, out otherGroup)) { otherGroup.AddUpdateAfterToAllChildBehaviours(targetSystem, dependencies); } else { UpdateInsertionPos(targetSystem, attribDep.SystemType, defaultPlayerLoop, false); } } attribs = target.GetCustomAttributes(typeof(UpdateInGroupAttribute), true); foreach (var attr in attribs) { var attribDep = attr as UpdateInGroupAttribute; ScriptBehaviourGroup group; if (!allGroups.TryGetValue(attribDep.GroupType, out group)) continue; DependantBehavior otherSystem; ScriptBehaviourGroup otherGroup; foreach (var dep in group.UpdateAfter) if (dependencies.TryGetValue(dep, out otherSystem)) { targetSystem.UpdateAfter.Add(dep); otherSystem.UpdateBefore.Add(target); } else if (allGroups.TryGetValue(dep, out otherGroup)) { otherGroup.AddUpdateBeforeToAllChildBehaviours(targetSystem, dependencies); } else { UpdateInsertionPos(targetSystem, dep, defaultPlayerLoop, true); } foreach (var dep in group.UpdateBefore) if (dependencies.TryGetValue(dep, out otherSystem)) { targetSystem.UpdateBefore.Add(dep); otherSystem.UpdateAfter.Add(target); } else if (allGroups.TryGetValue(dep, out otherGroup)) { otherGroup.AddUpdateAfterToAllChildBehaviours(targetSystem, dependencies); } else { UpdateInsertionPos(targetSystem, dep, defaultPlayerLoop, false); } } } private static void CollectGroups(IEnumerable activeManagers, out Dictionary allGroups, out Dictionary dependencies) { allGroups = new Dictionary(); dependencies = new Dictionary(); foreach (var manager in activeManagers) { var attribs = manager.GetType().GetCustomAttributes(typeof(UpdateInGroupAttribute), true); foreach (var attr in attribs) { var grp = attr as UpdateInGroupAttribute; ScriptBehaviourGroup groupData; if (!allGroups.TryGetValue(grp.GroupType, out groupData)) groupData = new ScriptBehaviourGroup(grp.GroupType, allGroups); groupData.Managers.Add(manager.GetType()); } var dep = new DependantBehavior(manager); dependencies.Add(manager.GetType(), dep); } } private static Dictionary BuildSystemGraph( IEnumerable activeManagers, PlayerLoopSystem defaultPlayerLoop) { // Collect all groups and create empty dependency data Dictionary allGroups; Dictionary dependencies; CollectGroups(activeManagers, out allGroups, out dependencies); // @TODO: apply additional sideloaded constraints here // Apply the update before / after dependencies foreach (var manager in dependencies) // @TODO: need to deal with extracting dependencies for GenericProcessComponentSystem AddDependencies(manager.Value, dependencies, allGroups, defaultPlayerLoop); ValidateAndFixSystemGraph(dependencies); return dependencies; } private static void ValidateAndFixSystemGraph(Dictionary dependencyGraph) { // Check for simple over constraints on engine systems foreach (var typeAndSystem in dependencyGraph) { var system = typeAndSystem.Value; if (system.MinInsertPos > system.MaxInsertPos) { Debug.LogError( $"{system.Manager.GetType()} is over constrained with engine containts - ignoring dependencies"); system.MinInsertPos = system.MaxInsertPos = 0; } system.UnvalidatedSystemsUpdatingBefore = system.UpdateAfter.Count; system.LongestSystemsUpdatingBeforeChain = 0; system.LongestSystemsUpdatingAfterChain = 0; } // Check for circular dependencies, start with all systems updating first, mark all systems it updates after as having one more validated dep and start over var progress = true; while (progress) { progress = false; foreach (var typeAndSystem in dependencyGraph) { var system = typeAndSystem.Value; if (system.UnvalidatedSystemsUpdatingBefore != 0) continue; system.UnvalidatedSystemsUpdatingBefore = -1; foreach (var nextInChain in system.UpdateBefore) { --dependencyGraph[nextInChain].UnvalidatedSystemsUpdatingBefore; progress = true; } } } // If some systems were found to have circular dependencies, drop all of them. This is a bit over aggressive - but it only happens on badly setup dependency chains foreach (var typeAndSystem in dependencyGraph) { var system = typeAndSystem.Value; if (system.UnvalidatedSystemsUpdatingBefore <= 0) continue; Debug.LogError( $"{system.Manager.GetType()} is in a chain of circular dependencies - ignoring dependencies"); foreach (var after in system.UpdateAfter) dependencyGraph[after].UpdateBefore.Remove(system.Manager.GetType()); system.UpdateAfter.Clear(); } // Validate that the chains are not over constrained with combinations of system and engine dependencies foreach (var typeAndSystem in dependencyGraph) { var system = typeAndSystem.Value; if (system.UpdateBefore.Count == 0) ValidateAndFixSingleChainMaxPos(system, dependencyGraph, system.MaxInsertPos); if (system.UpdateAfter.Count == 0) ValidateAndFixSingleChainMinPos(system, dependencyGraph, system.MinInsertPos); } } private static void ValidateAndFixSingleChainMinPos(DependantBehavior system, IReadOnlyDictionary dependencyGraph, int minInsertPos) { foreach (var nextInChain in system.UpdateBefore) { var nextSys = dependencyGraph[nextInChain]; if (system.LongestSystemsUpdatingBeforeChain >= nextSys.LongestSystemsUpdatingBeforeChain) nextSys.LongestSystemsUpdatingBeforeChain = system.LongestSystemsUpdatingBeforeChain + 1; if (nextSys.MinInsertPos < minInsertPos) nextSys.MinInsertPos = minInsertPos; if (nextSys.MaxInsertPos > 0 && nextSys.MaxInsertPos < nextSys.MinInsertPos) { Debug.LogError( $"{nextInChain} is over constrained with engine and system containts - ignoring dependencies"); nextSys.MaxInsertPos = nextSys.MinInsertPos; } ValidateAndFixSingleChainMinPos(nextSys, dependencyGraph, nextSys.MinInsertPos); } } private static void ValidateAndFixSingleChainMaxPos(DependantBehavior system, Dictionary dependencyGraph, int maxInsertPos) { foreach (var prevInChain in system.UpdateAfter) { var prevSys = dependencyGraph[prevInChain]; if (system.LongestSystemsUpdatingAfterChain >= prevSys.LongestSystemsUpdatingAfterChain) prevSys.LongestSystemsUpdatingAfterChain = system.LongestSystemsUpdatingAfterChain + 1; if (prevSys.MaxInsertPos == 0 || prevSys.MaxInsertPos > maxInsertPos) prevSys.MaxInsertPos = maxInsertPos; if (prevSys.MaxInsertPos > 0 && prevSys.MaxInsertPos < prevSys.MinInsertPos) { Debug.LogError( $"{prevInChain} is over constrained with engine and system containts - ignoring dependencies"); prevSys.MinInsertPos = prevSys.MaxInsertPos; } ValidateAndFixSingleChainMaxPos(prevSys, dependencyGraph, prevSys.MaxInsertPos); } } private static void MarkSchedulingAndWaitingJobs(Dictionary dependencyGraph) { // @TODO: sync rules for read-only var schedulers = new HashSet(); foreach (var systemKeyValue in dependencyGraph) { var system = systemKeyValue.Value; // @TODO: GenericProcessComponentSystem // @TODO: attribute if (!(system.Manager is JobComponentSystem)) continue; system.spawnsJobs = true; schedulers.Add(system); } foreach (var systemKeyValue in dependencyGraph) { var system = systemKeyValue.Value; // @TODO: attribute for sync if ((system.Manager as ComponentSystem)?.ComponentGroups == null) continue; var waitComponent = new HashSet(); foreach (var componentGroup in ((ComponentSystem) system.Manager).ComponentGroups) foreach (var type in componentGroup.Types) if (type.RequiresJobDependency) waitComponent.Add(type.TypeIndex); foreach (var scheduler in schedulers) { if (!(scheduler.Manager is ComponentSystem)) continue; // Check if the component groups overlaps var scheduleComponent = new HashSet(); foreach (var componentGroup in ((ComponentSystem) scheduler.Manager).ComponentGroups) foreach (var type in componentGroup.Types) if (type.RequiresJobDependency) scheduleComponent.Add(type.TypeIndex); var overlap = false; foreach (var waitComp in waitComponent) { if (!scheduleComponent.Contains(waitComp)) continue; overlap = true; break; } if (!overlap) continue; system.WaitsForJobs = true; break; } } } private static PlayerLoopSystem InsertWorldManagersInPlayerLoop(PlayerLoopSystem defaultPlayerLoop, params World[] worlds) { var systemList = new List(); foreach (var world in worlds) { if (world.BehaviourManagers.Count() == 0) continue; systemList.AddRange(CreateSystemDependencyList(world.BehaviourManagers, defaultPlayerLoop)); } var ecsPlayerLoop = CreatePlayerLoop(systemList, defaultPlayerLoop); return ecsPlayerLoop; } internal static PlayerLoopSystem InsertManagersInPlayerLoop(IEnumerable activeManagers, PlayerLoopSystem defaultPlayerLoop) { if (activeManagers.Count() == 0) return defaultPlayerLoop; var list = CreateSystemDependencyList(activeManagers, defaultPlayerLoop); return CreatePlayerLoop(list, defaultPlayerLoop); } private static List CreateSystemDependencyList( IEnumerable activeManagers, PlayerLoopSystem defaultPlayerLoop) { var dependencyGraph = BuildSystemGraph(activeManagers, defaultPlayerLoop); MarkSchedulingAndWaitingJobs(dependencyGraph); // Figure out which systems should be inserted early or late var earlyUpdates = new HashSet(); var normalUpdates = new HashSet(); var lateUpdates = new HashSet(); foreach (var dependency in dependencyGraph) { var system = dependency.Value; if (system.spawnsJobs) earlyUpdates.Add(system); else if (system.WaitsForJobs) lateUpdates.Add(system); else normalUpdates.Add(system); } var depsToAdd = new List(); while (true) { foreach (var sys in earlyUpdates) foreach (var depType in sys.UpdateAfter) { var depSys = dependencyGraph[depType]; if (normalUpdates.Remove(depSys) || lateUpdates.Remove(depSys)) depsToAdd.Add(depSys); } if (depsToAdd.Count == 0) break; foreach (var dep in depsToAdd) earlyUpdates.Add(dep); depsToAdd.Clear(); } while (true) { foreach (var sys in lateUpdates) foreach (var depType in sys.UpdateBefore) { var depSys = dependencyGraph[depType]; if (normalUpdates.Remove(depSys)) depsToAdd.Add(depSys); } if (depsToAdd.Count == 0) break; foreach (var dep in depsToAdd) lateUpdates.Add(dep); depsToAdd.Clear(); } var defaultPos = 0; foreach (var sys in defaultPlayerLoop.subSystemList) { defaultPos += 1 + sys.subSystemList.Length; if (sys.type == typeof(Update)) break; } var insertionBucketDict = new Dictionary(); // increase the number of dependencies allowed by 1, starting from 0 and add all systems with that many at the first or last possible pos // bucket idx is insertion point << 2 | 0,1,2 // When adding propagate min or max through the chain var processedChainLength = 0; while (earlyUpdates.Count > 0 || lateUpdates.Count > 0) { foreach (var sys in earlyUpdates) { if (sys.LongestSystemsUpdatingBeforeChain != processedChainLength) continue; if (sys.MinInsertPos == 0) sys.MinInsertPos = defaultPos; sys.MaxInsertPos = sys.MinInsertPos; depsToAdd.Add(sys); foreach (var nextSys in sys.UpdateBefore) if (dependencyGraph[nextSys].MinInsertPos < sys.MinInsertPos) dependencyGraph[nextSys].MinInsertPos = sys.MinInsertPos; } foreach (var sys in lateUpdates) { if (sys.LongestSystemsUpdatingAfterChain != processedChainLength) continue; if (sys.MaxInsertPos == 0) sys.MaxInsertPos = defaultPos; sys.MinInsertPos = sys.MaxInsertPos; depsToAdd.Add(sys); foreach (var prevSys in sys.UpdateAfter) if (dependencyGraph[prevSys].MaxInsertPos == 0 || dependencyGraph[prevSys].MaxInsertPos > sys.MaxInsertPos) dependencyGraph[prevSys].MaxInsertPos = sys.MaxInsertPos; } foreach (var sys in depsToAdd) { earlyUpdates.Remove(sys); var isLate = lateUpdates.Remove(sys); var subIndex = isLate ? 2 : 0; // Bucket to insert in is minPos == maxPos var bucketIndex = (sys.MinInsertPos << 2) | subIndex; InsertionBucket bucket; if (!insertionBucketDict.TryGetValue(bucketIndex, out bucket)) { bucket = new InsertionBucket { InsertPos = sys.MinInsertPos, InsertSubPos = subIndex }; insertionBucketDict.Add(bucketIndex, bucket); } bucket.Systems.Add(sys); } depsToAdd.Clear(); ++processedChainLength; } processedChainLength = 0; while (normalUpdates.Count > 0) { foreach (var sys in normalUpdates) { if (sys.LongestSystemsUpdatingBeforeChain != processedChainLength) continue; if (sys.MinInsertPos == 0) sys.MinInsertPos = defaultPos; sys.MaxInsertPos = sys.MinInsertPos; depsToAdd.Add(sys); foreach (var nextSys in sys.UpdateBefore) if (dependencyGraph[nextSys].MinInsertPos < sys.MinInsertPos) dependencyGraph[nextSys].MinInsertPos = sys.MinInsertPos; } foreach (var sys in depsToAdd) { const int subIndex = 1; normalUpdates.Remove(sys); // Bucket to insert in is minPos == maxPos var bucketIndex = (sys.MinInsertPos << 2) | subIndex; InsertionBucket bucket; if (!insertionBucketDict.TryGetValue(bucketIndex, out bucket)) { bucket = new InsertionBucket(); bucket.InsertPos = sys.MinInsertPos; bucket.InsertSubPos = subIndex; insertionBucketDict.Add(bucketIndex, bucket); } bucket.Systems.Add(sys); } depsToAdd.Clear(); ++processedChainLength; } return new List(insertionBucketDict.Values); } private static PlayerLoopSystem CreatePlayerLoop(List insertionBuckets, PlayerLoopSystem defaultPlayerLoop) { insertionBuckets.Sort(); // Insert the buckets at the appropriate place var currentPos = 0; var ecsPlayerLoop = new PlayerLoopSystem { subSystemList = new PlayerLoopSystem[defaultPlayerLoop.subSystemList.Length] }; var currentBucket = 0; for (var i = 0; i < defaultPlayerLoop.subSystemList.Length; ++i) { var firstPos = currentPos + 1; var lastPos = firstPos + defaultPlayerLoop.subSystemList[i].subSystemList.Length; // Find all new things to insert here var systemsToInsert = 0; foreach (var bucket in insertionBuckets) if (bucket.InsertPos >= firstPos && bucket.InsertPos <= lastPos) systemsToInsert += bucket.Systems.Count; ecsPlayerLoop.subSystemList[i] = defaultPlayerLoop.subSystemList[i]; if (systemsToInsert > 0) { ecsPlayerLoop.subSystemList[i].subSystemList = new PlayerLoopSystem[defaultPlayerLoop.subSystemList[i].subSystemList.Length + systemsToInsert]; var dstPos = 0; for (var srcPos = 0; srcPos < defaultPlayerLoop.subSystemList[i].subSystemList.Length; ++srcPos, ++dstPos) { while (currentBucket < insertionBuckets.Count && insertionBuckets[currentBucket].InsertPos <= firstPos + srcPos) { foreach (var insert in insertionBuckets[currentBucket].Systems) { ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].type = insert.Manager.GetType(); var tmp = new DummyDelagateWrapper(insert.Manager); ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].updateDelegate = tmp.TriggerUpdate; ++dstPos; } ++currentBucket; } ecsPlayerLoop.subSystemList[i].subSystemList[dstPos] = defaultPlayerLoop.subSystemList[i].subSystemList[srcPos]; } while (currentBucket < insertionBuckets.Count && insertionBuckets[currentBucket].InsertPos <= lastPos) { foreach (var insert in insertionBuckets[currentBucket].Systems) { ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].type = insert.Manager.GetType(); var tmp = new DummyDelagateWrapper(insert.Manager); ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].updateDelegate = tmp.TriggerUpdate; ++dstPos; } ++currentBucket; } } currentPos = lastPos; } return ecsPlayerLoop; } public static void UpdatePlayerLoop(params World[] worlds) { var defaultLoop = PlayerLoop.GetDefaultPlayerLoop(); if (worlds?.Length > 0) { var ecsLoop = InsertWorldManagersInPlayerLoop(defaultLoop, worlds.Where(x => x != null).ToArray()); SetPlayerLoop(ecsLoop); } else { SetPlayerLoop(defaultLoop); } } public static PlayerLoopSystem CurrentPlayerLoop => currentPlayerLoop; private static PlayerLoopSystem currentPlayerLoop; private static void SetPlayerLoop(PlayerLoopSystem playerLoop) { PlayerLoop.SetPlayerLoop(playerLoop); currentPlayerLoop = playerLoop; } // FIXME: HACK! - mono 4.6 has problems invoking virtual methods as delegates from native, so wrap the invocation in a non-virtual class internal class DummyDelagateWrapper { internal ScriptBehaviourManager Manager => m_Manager; private readonly ScriptBehaviourManager m_Manager; public DummyDelagateWrapper(ScriptBehaviourManager man) { m_Manager = man; } public void TriggerUpdate() { m_Manager.Update(); } } private class ScriptBehaviourGroup { private readonly List m_Groups = new List(); public readonly List Managers = new List(); public readonly HashSet UpdateAfter = new HashSet(); public readonly HashSet UpdateBefore = new HashSet(); private readonly Type m_GroupType; private readonly List m_Parents = new List(); public ScriptBehaviourGroup(Type grpType, IDictionary allGroups, HashSet circularCheck = null) { m_GroupType = grpType; var attribs = grpType.GetCustomAttributes(typeof(UpdateAfterAttribute), true); foreach (var attr in attribs) { var attribDep = attr as UpdateAfterAttribute; UpdateAfter.Add(attribDep.SystemType); } attribs = grpType.GetCustomAttributes(typeof(UpdateBeforeAttribute), true); foreach (var attr in attribs) { var attribDep = attr as UpdateBeforeAttribute; UpdateBefore.Add(attribDep.SystemType); } allGroups.Add(m_GroupType, this); attribs = m_GroupType.GetCustomAttributes(typeof(UpdateInGroupAttribute), true); foreach (var attr in attribs) { if (circularCheck == null) circularCheck = new HashSet {m_GroupType}; var parentGrp = attr as UpdateInGroupAttribute; if (!circularCheck.Add(parentGrp.GroupType)) { // Found circular dependency var msg = "Found circular chain in update groups involving: "; var firstType = true; foreach (var circularType in circularCheck) { msg += (firstType ? "" : ", ") + circularType; firstType = false; } Debug.LogError(msg); } ScriptBehaviourGroup parentGroupData; if (!allGroups.TryGetValue(parentGrp.GroupType, out parentGroupData)) parentGroupData = new ScriptBehaviourGroup(parentGrp.GroupType, allGroups, circularCheck); circularCheck.Remove(parentGrp.GroupType); parentGroupData.m_Groups.Add(this); m_Parents.Add(parentGroupData); foreach (var dep in parentGroupData.UpdateBefore) UpdateBefore.Add(dep); foreach (var dep in parentGroupData.UpdateAfter) UpdateAfter.Add(dep); } } public void AddUpdateBeforeToAllChildBehaviours(DependantBehavior target, IReadOnlyDictionary dependencies) { var dep = target.Manager.GetType(); foreach (var manager in Managers) { DependantBehavior managerDep; if (!dependencies.TryGetValue(manager, out managerDep)) continue; target.UpdateAfter.Add(manager); managerDep.UpdateBefore.Add(dep); } foreach (var group in m_Groups) group.AddUpdateBeforeToAllChildBehaviours(target, dependencies); } public void AddUpdateAfterToAllChildBehaviours(DependantBehavior target, IReadOnlyDictionary dependencies) { var dep = target.Manager.GetType(); foreach (var manager in Managers) { DependantBehavior managerDep; if (!dependencies.TryGetValue(manager, out managerDep)) continue; target.UpdateBefore.Add(manager); managerDep.UpdateAfter.Add(dep); } foreach (var group in m_Groups) group.AddUpdateAfterToAllChildBehaviours(target, dependencies); } } private class DependantBehavior { public readonly ScriptBehaviourManager Manager; public readonly HashSet UpdateAfter = new HashSet(); public readonly HashSet UpdateBefore = new HashSet(); public int LongestSystemsUpdatingAfterChain; public int LongestSystemsUpdatingBeforeChain; public int MaxInsertPos; public int MinInsertPos; public bool spawnsJobs; public int UnvalidatedSystemsUpdatingBefore; public bool WaitsForJobs; public DependantBehavior(ScriptBehaviourManager man) { Manager = man; MinInsertPos = 0; MaxInsertPos = 0; spawnsJobs = false; WaitsForJobs = false; UnvalidatedSystemsUpdatingBefore = 0; LongestSystemsUpdatingBeforeChain = 0; LongestSystemsUpdatingAfterChain = 0; } } private class InsertionBucket : IComparable { public readonly List Systems; public int InsertPos; public int InsertSubPos; public InsertionBucket() { InsertPos = 0; InsertSubPos = 0; Systems = new List(); } public int CompareTo(object other) { var otherBucket = other as InsertionBucket; if (InsertPos == otherBucket.InsertPos) return InsertSubPos - otherBucket.InsertSubPos; return InsertPos - otherBucket.InsertPos; } } } }