您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
859 行
36 KiB
859 行
36 KiB
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<Type, DependantBehavior> dependencies,
|
|
IReadOnlyDictionary<Type, ScriptBehaviourGroup> 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<ScriptBehaviourManager> activeManagers,
|
|
out Dictionary<Type, ScriptBehaviourGroup> allGroups, out Dictionary<Type, DependantBehavior> dependencies)
|
|
{
|
|
allGroups = new Dictionary<Type, ScriptBehaviourGroup>();
|
|
dependencies = new Dictionary<Type, DependantBehavior>();
|
|
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<Type, DependantBehavior> BuildSystemGraph(
|
|
IEnumerable<ScriptBehaviourManager> activeManagers, PlayerLoopSystem defaultPlayerLoop)
|
|
{
|
|
// Collect all groups and create empty dependency data
|
|
Dictionary<Type, ScriptBehaviourGroup> allGroups;
|
|
Dictionary<Type, DependantBehavior> 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<Type, DependantBehavior> 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<Type, DependantBehavior> 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<Type, DependantBehavior> 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<Type, DependantBehavior> dependencyGraph)
|
|
{
|
|
// @TODO: sync rules for read-only
|
|
var schedulers = new HashSet<DependantBehavior>();
|
|
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<int>();
|
|
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<int>();
|
|
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<InsertionBucket>();
|
|
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<ScriptBehaviourManager> activeManagers,
|
|
PlayerLoopSystem defaultPlayerLoop)
|
|
{
|
|
if (activeManagers.Count() == 0)
|
|
return defaultPlayerLoop;
|
|
|
|
var list = CreateSystemDependencyList(activeManagers, defaultPlayerLoop);
|
|
return CreatePlayerLoop(list, defaultPlayerLoop);
|
|
}
|
|
|
|
private static List<InsertionBucket> CreateSystemDependencyList(
|
|
IEnumerable<ScriptBehaviourManager> activeManagers, PlayerLoopSystem defaultPlayerLoop)
|
|
{
|
|
var dependencyGraph = BuildSystemGraph(activeManagers, defaultPlayerLoop);
|
|
|
|
MarkSchedulingAndWaitingJobs(dependencyGraph);
|
|
|
|
// Figure out which systems should be inserted early or late
|
|
var earlyUpdates = new HashSet<DependantBehavior>();
|
|
var normalUpdates = new HashSet<DependantBehavior>();
|
|
var lateUpdates = new HashSet<DependantBehavior>();
|
|
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<DependantBehavior>();
|
|
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<int, InsertionBucket>();
|
|
// 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<InsertionBucket>(insertionBucketDict.Values);
|
|
}
|
|
|
|
private static PlayerLoopSystem CreatePlayerLoop(List<InsertionBucket> 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<ScriptBehaviourGroup> m_Groups = new List<ScriptBehaviourGroup>();
|
|
public readonly List<Type> Managers = new List<Type>();
|
|
public readonly HashSet<Type> UpdateAfter = new HashSet<Type>();
|
|
public readonly HashSet<Type> UpdateBefore = new HashSet<Type>();
|
|
|
|
private readonly Type m_GroupType;
|
|
private readonly List<ScriptBehaviourGroup> m_Parents = new List<ScriptBehaviourGroup>();
|
|
|
|
public ScriptBehaviourGroup(Type grpType, IDictionary<Type, ScriptBehaviourGroup> allGroups,
|
|
HashSet<Type> 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<Type> {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<Type, DependantBehavior> 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<Type, DependantBehavior> 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<Type> UpdateAfter = new HashSet<Type>();
|
|
public readonly HashSet<Type> UpdateBefore = new HashSet<Type>();
|
|
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<DependantBehavior> Systems;
|
|
public int InsertPos;
|
|
public int InsertSubPos;
|
|
|
|
public InsertionBucket()
|
|
{
|
|
InsertPos = 0;
|
|
InsertSubPos = 0;
|
|
Systems = new List<DependantBehavior>();
|
|
}
|
|
|
|
public int CompareTo(object other)
|
|
{
|
|
var otherBucket = other as InsertionBucket;
|
|
if (InsertPos == otherBucket.InsertPos)
|
|
return InsertSubPos - otherBucket.InsertSubPos;
|
|
return InsertPos - otherBucket.InsertPos;
|
|
}
|
|
}
|
|
}
|
|
}
|