├── README.md ├── SystemVisualizer.cs └── ss.png /README.md: -------------------------------------------------------------------------------- 1 | # Unity System Visualizer 2 | 3 | ![screenshot](ss.png) 4 | 5 | It does not work yet just some proof of concept. But the code might be interesting so I put it up. 6 | 7 | ## Concept 8 | 9 | I found interesting `private` method named `CreateSystemDependencyList` in `ScriptBehaviourUpdateOrder.cs` so I tried using reflection and take `List` out to experiment. 10 | 11 | Each item is a system and all of its before and after systems. I want to visualize this as a graph. My idea is to start from those without afters as root nodes on the first column. 12 | 13 | ## Problems 14 | - Very messy code, just screw around for a quick result. 15 | - Barriers are considered root nodes because they don't have any update afters. But in the actual game barrier runs after it gets a command from some system that runs way later. 16 | - I don't know graph theory, how to arrange a graph nicely based on previous/next relationship between nodes without any depth assigned on them? Basically I tried to calculate depth for each one but those depths are bad. 17 | - The traversal code put nodes at some depth way down below. (I intended for each column to start at the top) 18 | - There are some lines that travels backward to the previous column. 19 | - Some line goes under nodes and skips depth. 20 | - The lines are so messy that it barely provide any benefits from looking at them. 21 | - Some "reactive" type system are at the first column. In the actual code they does not have any before/afters so they do go to the first column, but the intention for these systems is to react after some other system put out a message. Before/after dependency could not account for this. (No automatic system could account for this, I think.) -------------------------------------------------------------------------------- /SystemVisualizer.cs: -------------------------------------------------------------------------------- 1 | /// 5argon - Exceed7 Experiments 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | using Unity.Entities; 7 | using System.Reflection; 8 | using System; 9 | using System.Linq; 10 | using UnityEngine.Experimental.LowLevel; 11 | using UnityEngine.Experimental.PlayerLoop; 12 | 13 | using UnityEditor; 14 | 15 | public class SystemVisualizer : EditorWindow 16 | { 17 | [MenuItem("Window/System Visualizer")] 18 | static void OpenWindow() 19 | { 20 | SystemVisualizer window = (SystemVisualizer)EditorWindow.GetWindow(typeof(SystemVisualizer)); 21 | window.titleContent = new GUIContent("Systems"); 22 | window.Init(); 23 | window.Show(); 24 | } 25 | 26 | Dictionary linkDict; 27 | List rootNodes; 28 | 29 | public void Init() 30 | { 31 | MakeLinkDictRootNodes(out linkDict, out rootNodes); 32 | //rects = new Rect[dependencies.Count]; 33 | style = new GUIStyle(EditorStyles.toolbarTextField); 34 | style.alignment = TextAnchor.MiddleCenter; 35 | SystemVisualizerNode.depthMaxWidth.Clear(); 36 | SystemVisualizerNode.depthCurrentOrder.Clear(); 37 | foreach(var n in rootNodes) 38 | { 39 | n.Traverse(linkDict); 40 | } 41 | foreach(var n in linkDict.Values) 42 | { 43 | n.UpdateRect(); 44 | } 45 | 46 | } 47 | 48 | public void OnEnable() 49 | { 50 | Init(); 51 | } 52 | 53 | GUIStyle style; 54 | float scrollValue; 55 | 56 | void OnGUI() 57 | { 58 | scrollValue = GUI.HorizontalScrollbar(new Rect(0, position.height-20, position.width, 20), scrollValue, 500, 0, 2000); 59 | BeginWindows(); 60 | foreach(var nodes in linkDict.Values) 61 | { 62 | GUI.color = Color.yellow; 63 | GUI.Window(nodes.id, new Rect( nodes.rect.x - scrollValue, nodes.rect.y, nodes.rect.width, nodes.rect.height), DrawNodeWindow, nodes.text, style); 64 | foreach (var n in rootNodes) 65 | { 66 | n.TraverseDrawLine(linkDict,scrollValue); 67 | } 68 | } 69 | EndWindows(); 70 | } 71 | 72 | void DrawNodeWindow(int id) 73 | { 74 | GUI.DragWindow(); 75 | } 76 | 77 | public static void DrawNodeCurve(Rect start, Rect end, float scroll) 78 | { 79 | Vector3 startPos = new Vector3(start.x + start.width - scroll, start.y + start.height / 2, 0); 80 | Vector3 endPos = new Vector3(end.x- scroll, end.y + end.height / 2, 0); 81 | Vector3 startTan = startPos + Vector3.right * 50; 82 | Vector3 endTan = endPos + Vector3.left * 50; 83 | Color shadowCol = new Color(0, 0, 0, 0.06f); 84 | Handles.DrawBezier(startPos, endPos, startTan, endTan, Color.black, null, 0.5f); 85 | } 86 | 87 | private class SystemVisualizerNode 88 | { 89 | public ScriptBehaviourUpdateOrder.DependantBehavior dependant; 90 | public Rect rect; 91 | public int depth; 92 | public int orderInDepth; 93 | public int textWidth => WidthOf(dependant.Manager.ToString()); 94 | public string text => this.dependant.Manager.ToString(); 95 | public bool root; 96 | public int id; 97 | private static int runningId; 98 | 99 | public SystemVisualizerNode(ScriptBehaviourUpdateOrder.DependantBehavior dependant) 100 | { 101 | this.dependant = dependant; 102 | this.root = dependant.UpdateAfter.Count == 0; 103 | this.id = runningId; 104 | this.depth = 0; 105 | runningId++; 106 | } 107 | 108 | public void UpdateRect() 109 | { 110 | int xNow = 0; 111 | for (int i = 0; i < this.depth; i++) 112 | { 113 | xNow += depthMaxWidth[i]; 114 | } 115 | 116 | this.rect = new Rect(xNow, orderInDepth * 20, textWidth, 20); 117 | } 118 | 119 | public static Dictionary depthMaxWidth = new Dictionary(); 120 | public static Dictionary depthCurrentOrder= new Dictionary(); 121 | 122 | public void TraverseDrawLine(Dictionary linkDict, float scroll) 123 | { 124 | foreach (var next in this.dependant.UpdateBefore) 125 | { 126 | var nextNode = linkDict[next.FullName]; 127 | SystemVisualizer.DrawNodeCurve(this.rect, nextNode.rect,scroll); 128 | nextNode.TraverseDrawLine(linkDict,scroll); 129 | } 130 | } 131 | 132 | public void Traverse(Dictionary linkDict) 133 | { 134 | int maxDepth; 135 | if (depthMaxWidth.TryGetValue(this.depth, out maxDepth)) 136 | { 137 | if (textWidth > maxDepth) 138 | { 139 | depthMaxWidth.Remove(this.depth); 140 | depthMaxWidth.Add(this.depth, textWidth); 141 | } 142 | } 143 | else 144 | { 145 | depthMaxWidth.Add(this.depth, textWidth); 146 | } 147 | 148 | int currentOrder; 149 | if (depthCurrentOrder.TryGetValue(this.depth, out currentOrder)) 150 | { 151 | this.orderInDepth = currentOrder + 1; 152 | depthCurrentOrder.Remove(this.depth); 153 | depthCurrentOrder.Add(this.depth, this.orderInDepth); 154 | } 155 | else 156 | { 157 | depthCurrentOrder.Add(this.depth, 0); 158 | this.orderInDepth = 0; 159 | } 160 | 161 | foreach (var next in this.dependant.UpdateBefore) 162 | { 163 | var nextNode = linkDict[next.FullName]; 164 | nextNode.depth = this.depth + 1; 165 | nextNode.Traverse(linkDict); 166 | } 167 | } 168 | 169 | private int WidthOf(string text) 170 | { 171 | int maxX = 0; 172 | EditorStyles.toolbarTextField.font.RequestCharactersInTexture(text, EditorStyles.toolbarTextField.fontSize, FontStyle.Normal); 173 | foreach (char c in text) 174 | { 175 | CharacterInfo ci; 176 | if (EditorStyles.toolbarTextField.font.GetCharacterInfo(c, out ci)) 177 | { 178 | maxX += ci.maxX; 179 | } 180 | } 181 | return maxX; 182 | } 183 | } 184 | 185 | private static void MakeLinkDictRootNodes(out Dictionary linkDict, out List rootNodes) 186 | { 187 | var deps = GetAllDepsList(); 188 | rootNodes = new List(); 189 | linkDict = new Dictionary(); 190 | foreach (var d in deps) 191 | { 192 | var newNode = new SystemVisualizerNode(d); 193 | if (newNode.root) 194 | { 195 | rootNodes.Add(newNode); 196 | } 197 | linkDict.Add(d.Manager.ToString(), newNode); 198 | } 199 | } 200 | 201 | static List GetAllDepsList() 202 | { 203 | var world = new World("Visualizer"); 204 | IEnumerable allTypes; 205 | foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) 206 | { 207 | try 208 | { 209 | allTypes = ass.GetTypes(); 210 | } 211 | catch (ReflectionTypeLoadException e) 212 | { 213 | allTypes = e.Types.Where(t => t != null); 214 | Debug.LogWarning("DefaultWorldInitialization failed loading assembly: " + ass.Location); 215 | } 216 | 217 | // Create all ComponentSystem 218 | CreateBehaviourManagersForMatchingTypes(false, allTypes, world); 219 | } 220 | 221 | var method = typeof(ScriptBehaviourUpdateOrder).GetMethod("CreateSystemDependencyList", BindingFlags.NonPublic | BindingFlags.Static); 222 | Type insertionBucketType = typeof(ScriptBehaviourUpdateOrder).GetNestedType("InsertionBucket", BindingFlags.NonPublic); 223 | var systems = insertionBucketType.GetField("Systems", BindingFlags.Public | BindingFlags.Instance); 224 | var insertPos = insertionBucketType.GetField("InsertPos", BindingFlags.Public | BindingFlags.Instance); 225 | var insertPos2 = insertionBucketType.GetField("InsertSubPos", BindingFlags.Public | BindingFlags.Instance); 226 | var bucket = (IEnumerable)method.Invoke(null, new object[] { world.BehaviourManagers, PlayerLoop.GetDefaultPlayerLoop() }); 227 | 228 | List all = new List(); 229 | foreach (object obj in bucket) 230 | { 231 | var returnSystems = (List)systems.GetValue(obj); 232 | var returnInsertPos = (int)insertPos.GetValue(obj); 233 | var returnInsertSubPos = (int)insertPos2.GetValue(obj); 234 | all.AddRange(returnSystems); 235 | } 236 | return all; 237 | } 238 | 239 | static void CreateBehaviourManagersForMatchingTypes(bool editorWorld, IEnumerable allTypes, World world) 240 | { 241 | var systemTypes = allTypes.Where(t => 242 | t.IsSubclassOf(typeof(ComponentSystemBase)) && 243 | !t.IsAbstract && 244 | !t.ContainsGenericParameters && 245 | t.GetCustomAttributes(typeof(DisableAutoCreationAttribute), true).Length == 0); 246 | foreach (var type in systemTypes) 247 | { 248 | if (editorWorld && type.GetCustomAttributes(typeof(ExecuteInEditMode), true).Length == 0) 249 | continue; 250 | 251 | GetBehaviourManagerAndLogException(world, type); 252 | } 253 | } 254 | 255 | static void GetBehaviourManagerAndLogException(World world, Type type) 256 | { 257 | try 258 | { 259 | world.GetOrCreateManager(type); 260 | } 261 | catch (Exception e) 262 | { 263 | Debug.LogException(e); 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5argon/UnitySystemVisualizer/19f508b27175d6e7599a17dc3051e3f32a72630b/ss.png --------------------------------------------------------------------------------