Exemplo n.º 1
0
        public void DoEditorGUILayout(bool isFocused)
        {
            if (!isFocused)
            {
                _foldout = EditorGUILayout.Foldout(_foldout, Name);
                if (!_foldout)
                {
                    return;
                }
            }

            string focusedControlName = GUI.GetNameOfFocusedControl();

            for (int i = 0; i < Values.Count; ++i)
            {
                var labelRect = EditorGUILayout.GetControlRect();
                var position  = new Rect(labelRect);
                position.xMin += EditorGUIUtility.labelWidth;
                labelRect.xMax = position.xMin;
                string name             = Values[i].Name;
                string labelControlName = i.ToString("000") + ":" + name;
                GUI.SetNextControlName(labelControlName);
                bool   isSettingFocused = focusedControlName == labelControlName;
                string newName          = EditorGUI.DelayedTextField(labelRect, name, isSettingFocused ? EditorStyles.textField : EditorStyles.label);
                Values[i].Value = GolemEditorUtility.EditorGUIField(position, InspectableTypeExt.GetInspectableTypeOf(Values[i].Type), Values[i].Type, Values[i].Value);
                if (newName != name)
                {
                    Values[i].Name = newName;
                    if (isSettingFocused)
                    {
                        GUI.FocusControl(null);
                    }
                }
            }

            Rect controlRect = EditorGUILayout.GetControlRect();

            if (EditorGUI.DropdownButton(controlRect, new GUIContent("+"), FocusType.Keyboard))
            {
                GenericMenu menu       = new GenericMenu();
                var         enumNames  = Enum.GetNames(typeof(InspectableType));
                var         enumValues = Enum.GetValues(typeof(InspectableType));
                for (int i = 0; i < enumNames.Length; ++i)
                {
                    var value = (InspectableType)enumValues.GetValue(i);
                    if (value == InspectableType.UnityObject || value == InspectableType.VariableRef || value == InspectableType.Enum || value == InspectableType.TriggerRef || value == InspectableType.Invalid)
                    {
                        continue;
                    }
                    menu.AddItem(new GUIContent(enumNames[i]), false, addSettingMenuCallback, InspectableTypeExt.GetRepresentedType(value));
                }
                menu.DropDown(controlRect);
            }
        }
Exemplo n.º 2
0
        //---------------------------------------------------------------------
        // Save
        //---------------------------------------------------------------------
        public void Save()
        {
            // // Only write if this is a prefab instance
            // bool isPrefab = PrefabUtility.GetPrefabType(Entity) == PrefabType.PrefabInstance;
            // bool isRoot = PrefabUtility.FindRootGameObjectWithSameParentPrefab(Entity.gameObject) == Entity.gameObject;
            // if (!isPrefab || !isRoot)
            // {
            //     // TODO: allow non-root to premit template GameObject style instancing?
            //     return;
            // }

            // Holds data to be written to the entity asset
            Dictionary <string, object> serialized = new Dictionary <string, object>();

            //-------------------------------------------------------------
            // Write Variables
            //-------------------------------------------------------------
            {
                var variables = new Dictionary <string, object>();
                for (int i = 0; i < EditorVariables.Count; ++i)
                {
                    object initialValue = EditorVariables[i].InitialValue;
                    Type   type         = EditorVariables[i].Type;
                    Debug.Assert(type.IsValueType ? initialValue.GetType() == type : (initialValue == null || type.IsAssignableFrom(initialValue.GetType())));
                    variables.Add(EditorVariables[i].Name, EditorVariables[i].InitialValue);
                }
                serialized["Variables"] = variables;
            }

            //-------------------------------------------------------------
            // Write Aspects
            //-------------------------------------------------------------
            {
                var aspects = new Dictionary <string, Aspect>();
                for (int i = 0; i < EditorAspects.Count; ++i)
                {
                    var editorAspect = EditorAspects[i];
                    var aspect       = editorAspect.Aspect;
                    ApplyFieldsUsingSettings(aspect, editorAspect.FieldsUsingSettings, Settings);
                    aspects[editorAspect.Field.Name] = aspect;
                }
                serialized["Aspects"] = aspects;
            }

            //-------------------------------------------------------------
            // Compile the circuit
            //-------------------------------------------------------------
            {
                // Do some housekeeping
                for (int i = 0; i < EditorCells.Count; ++i)
                {
                    EditorCells[i].Index = (EditorCellIndex)i;
                }

                //-----------------------------------------------------
                // Compute the correct topographically-sorted traversal
                // order from the wire connectivity graph.
                //-----------------------------------------------------
                int[] cellRemap;
                {
                    int[] longestPath = new int[EditorCells.Count];
                    for (int i = 0; i < longestPath.Length; ++i)
                    {
                        longestPath[i] = -1;
                    }

                    //-------------------------------------------------
                    // Prepare a dependency map so we can traverse this
                    // graph. Find all cells without inputs and mark them
                    // as sources.
                    //-------------------------------------------------
                    var hasInput     = new bool[EditorCells.Count];
                    var dependencies = new int[EditorCells.Count][];
                    {
                        List <int> cellDependencies = new List <int>();
                        for (int i = 0; i < EditorCells.Count; ++i)
                        {
                            for (int j = 0; j < EditorWires.Count; ++j)
                            {
                                if ((int)EditorWires[j].ReadCell.Index != i)
                                {
                                    continue;
                                }
                                int writeCell = (int)EditorWires[j].WriteCell.Index;
                                cellDependencies.Add(writeCell);
                                hasInput[writeCell] = true;
                            }
                            dependencies[i] = cellDependencies.ToArray();
                            cellDependencies.Clear();
                        }
                    }

                    //-------------------------------------------------
                    // Iterate through all of the cells and trace distance
                    // to their dependencies. Set the longest path index at
                    // each location to the max of its current value and
                    // the new value. In worst case, we traverse the tree
                    // O(n^2) times. However, in practice, this step should
                    // be run rarely on graphs that are flat and small.
                    //-------------------------------------------------
                    var worklist        = new int[EditorCells.Count];
                    var depths          = new int[EditorCells.Count];
                    int worklistPointer = 0;
                    for (int i = 0; i < hasInput.Length; ++i)
                    {
                        if (!hasInput[i])
                        {
                            worklist[worklistPointer] = i;
                            depths[worklistPointer]   = 0;
                            ++worklistPointer;
                        }
                    }
                    if (worklistPointer == 0 && EditorCells.Count > 0)
                    {
                        // We never incremented the worklist pointer so there are no input cells
                        goto CycleExit;
                    }
                    --worklistPointer;
                    while (worklistPointer >= 0)
                    {
                        int cell  = worklist[worklistPointer];
                        int depth = depths[worklistPointer];
                        --worklistPointer;

                        // Determine if the path to this cell is longer than the current one
                        if (longestPath[cell] >= depth)
                        {
                            continue;
                        }

                        // Update the longest path to this cell
                        longestPath[cell] = depth;

                        // Put all children on the worklist at +1 depth
                        var children = dependencies[cell];
                        ++depth;
                        for (int j = children.Length - 1; j >= 0; --j)
                        {
                            ++worklistPointer;
                            if (worklistPointer >= worklist.Length)
                            {
                                // This can only happen if there is a cycle, since the longest possible
                                // path to a node must be shorter than the path through every other node
                                goto CycleExit;
                            }
                            if (depth >= worklist.Length)
                            {
                                throw new InvalidProgramException("circuit graph isn't built correctly");
                            }
                            worklist[worklistPointer] = children[j];
                            depths[worklistPointer]   = depth;
                        }
                    }
CycleExit:
                    if (worklistPointer == -1)
                    {
                        // reuse arrays that are already the right size :)
                        cellRemap = worklist;
                        var originalCellIndex = depths;
                        for (int i = 0; i < EditorCells.Count; ++i)
                        {
                            originalCellIndex[i] = i;
                        }
                        System.Array.Sort(longestPath, originalCellIndex);
                        for (int i = 0; i < EditorCells.Count; ++i)
                        {
                            cellRemap[originalCellIndex[i]] = i;
                        }
                    }
                    else
                    {
                        Debug.LogError("Cycle in circuit graph detected; circuit will not function");
                        cellRemap = new int[0];
                    }
                }

                //---------------------------------------------------------
                // Build mappings required by the wires. Registers store values
                // on cell outputs. These registers then dirty other cells.
                //---------------------------------------------------------
                Debug.Log("TODO: map registers in an order that better matches the cell update order");
                List <object> registers = new List <object>();
                Dictionary <string, RegisterPtr>  cellOutputToRegister  = new Dictionary <string, RegisterPtr>();
                List <HashSet <EditorCellIndex> > cellsThatReadRegister = new List <HashSet <EditorCellIndex> >();
                for (int i = 0; i < EditorWires.Count; ++i)
                {
                    var         wire        = EditorWires[i];
                    string      registerKey = wire.ReadCell.Index + ".{" + wire.ReadField + "}";
                    RegisterPtr register;
                    if (cellOutputToRegister.TryGetValue(registerKey, out register))
                    {
                        wire.Register = register;
                        cellsThatReadRegister[(int)register].Add(wire.WriteCell.Index);
                    }
                    else
                    {
                        var readCellType = wire.ReadCell.Cell.GetType();
                        var readField    = readCellType.GetField(wire.ReadField);
                        var fieldType    = readField.FieldType;

                        object[] fieldAttributes = readField.GetCustomAttributes(typeof(OutAttribute), false);
                        object   defaultValue;
                        if (fieldAttributes.Length == 1)
                        {
                            var outAttribute = fieldAttributes[0] as OutAttribute;
                            defaultValue = outAttribute.Type.IsValueType ? Activator.CreateInstance(outAttribute.Type) : null;
                        }
                        else
                        {
                            Debug.LogError("Output field must have [Out] attribute... how did we even get here??");
                            defaultValue = new int[0, 0, 0, 0, 0]; // use something bizarre
                        }

                        // TODO: save the register's type here

                        register = (RegisterPtr)registers.Count;
                        cellOutputToRegister[registerKey] = register;
                        registers.Add(defaultValue);
                        wire.Register = register;
                        cellsThatReadRegister.Add(new HashSet <EditorCellIndex>()
                        {
                            wire.WriteCell.Index
                        });
                    }
                }

                int[][] cellsThatReadRegisterOutput = new int[cellsThatReadRegister.Count][];
                for (int i = 0; i < cellsThatReadRegister.Count; ++i)
                {
                    var editorCellIndicesThatReadThisRegister = cellsThatReadRegister[i];
                    var outputArray = new int[editorCellIndicesThatReadThisRegister.Count];
                    cellsThatReadRegisterOutput[i] = outputArray;
                    int j = 0;
                    foreach (var cellIndex in editorCellIndicesThatReadThisRegister)
                    {
                        outputArray[j++] = cellRemap[(int)cellIndex];
                    }
                }

                //---------------------------------------------------------
                // Write out all of the cells in the traversal order
                //---------------------------------------------------------
                Cell[] cells = new Cell[EditorCells.Count];
                for (int i = 0; i < EditorCells.Count; ++i)
                {
                    var editorCell = EditorCells[i];

                    var cell = editorCell.Cell.Clone();
                    cells[cellRemap[i]] = cell;

                    var cellType            = cell.GetType();
                    var inspectableCellType = InspectableCellType.GetInspectableCellType(cellType);

                    //-------------------------------------------------
                    // Default all registers on the cell to no-op
                    //-------------------------------------------------
                    foreach (var input in inspectableCellType.Inputs)
                    {
                        input.Field.SetValue(cell, RegisterPtr.Invalid);
                    }
                    foreach (var output in inspectableCellType.Outputs)
                    {
                        output.Field.SetValue(cell, RegisterPtr.Invalid);
                    }

                    //-------------------------------------------------
                    // Set the register indices for every active I/O
                    //-------------------------------------------------
                    List <EditorWire> inputWires = editorCell.Inputs;
                    for (int j = 0; j < inputWires.Count; ++j)
                    {
                        EditorWire wire        = inputWires[j];
                        string     registerKey = wire.ReadCell.Index + ".{" + wire.ReadField + "}";
                        cellType.GetField(wire.WriteField).SetValue(cell, cellOutputToRegister[registerKey]);
                        // TODO: check here to warn about multiple writers to the same input
                    }
                    List <EditorWire> outputWires = editorCell.Outputs;
                    for (int j = 0; j < outputWires.Count; ++j)
                    {
                        EditorWire wire        = outputWires[j];
                        string     registerKey = wire.ReadCell.Index + ".{" + wire.ReadField + "}";
                        cellType.GetField(wire.ReadField).SetValue(cell, cellOutputToRegister[registerKey]);
                    }

                    //-------------------------------------------------
                    // Apply settings values
                    //-------------------------------------------------
                    ApplyFieldsUsingSettings(cell, editorCell.FieldsUsingSettings, Settings);
                }

                serialized["Registers"]             = registers.ToArray();
                serialized["CellsThatReadRegister"] = cellsThatReadRegisterOutput;
                serialized["Cells"] = cells;
            }

            //-------------------------------------------------------------
            // Compile the program
            //-------------------------------------------------------------
            {
                // Identify entrypoints and assign them to layers
                List <HashSet <EditorStateIndex> > layersBuilder = new List <HashSet <EditorStateIndex> >();
                for (int i = 0; i < EditorStates.Count; ++i)
                {
                    var editorState = EditorStates[i];
                    Debug.Assert(editorState.Index == (EditorStateIndex)i);
                    if (editorState.Index != (EditorStateIndex)i)
                    {
                        Debug.Log("Wrong editor state index; set to " + editorState.Index + " expecting " + i);
                    }
                    editorState.Layer = EditorLayerIndex.Invalid;
                    if (editorState.SpecialState == EditorSpecialStateType.LayerEnter)
                    {
                        editorState.Layer = (EditorLayerIndex)layersBuilder.Count;
                        layersBuilder.Add(new HashSet <EditorStateIndex>()
                        {
                            editorState.Index
                        });
                    }
                }

                // Propagate the layer index from entrypoint to all connected states
                int removedLayersAboveIndex = layersBuilder.Count;
                var worklist = new List <EditorStateIndex>();
                for (int layerIndex = layersBuilder.Count - 1; layerIndex >= 0; --layerIndex)
                {
                    var layerStates = layersBuilder[layerIndex];
                    worklist.Add(layerStates.First());
                    while (worklist.Count > 0)
                    {
                        var lastIndex   = worklist.Count - 1;
                        var editorState = EditorStates[(int)worklist[lastIndex]];
                        worklist.RemoveAt(lastIndex);
                        var transitions = editorState.TransitionsOut;
                        for (int j = 0; j < transitions.Count; ++j)
                        {
                            var toIndex = EditorTransitions[(int)transitions[j]].To;
                            if (layerStates.Contains(toIndex))
                            {
                                continue;
                            }
                            var toState = EditorStates[(int)toIndex];
                            if (toState.Layer != EditorLayerIndex.Invalid)
                            {
                                Debug.LogError("State " + toState.Name + " is accessible from more than one entrypoint so one of the entries is being disabled");
                                layersBuilder.RemoveAt(layerIndex);
                                removedLayersAboveIndex = layerIndex;
                                goto NextLayer;
                            }
                            toState.Layer = (EditorLayerIndex)layerIndex;
                            layerStates.Add(toIndex);
                            worklist.Add(toIndex);
                        }
                    }
NextLayer:
                    worklist.Clear();
                }

                // Reassign layer indices to cells if any layers were removed
                for (int layerIndex = removedLayersAboveIndex; layerIndex < layersBuilder.Count; ++layerIndex)
                {
                    foreach (var editorState in layersBuilder[layerIndex])
                    {
                        EditorStates[(int)editorState].Layer = (EditorLayerIndex)layerIndex;
                    }
                }

                // Gather states and scripts into layers
                Layer[]       layers       = new Layer[layersBuilder.Count];
                List <Script> scripts      = new List <Script>();
                List <State>  states       = new List <State>();
                var           exprWorklist = new List <EditorTransitionExpression>();
                for (int layerIndex = 0; layerIndex < layersBuilder.Count; ++layerIndex)
                {
                    var editorStates = layersBuilder[layerIndex];
                    var layerStates  = new List <StateIndex>();
                    foreach (var editorStateIndex in editorStates)
                    {
                        var editorState = EditorStates[(int)editorStateIndex];

                        if (editorState.SpecialState != EditorSpecialStateType.Normal)
                        {
                            // if (editorState.SpecialState == EditorSpecialStateType.LayerEnter)
                            // {
                            //     editorState.CompiledIndex = (StateIndex)states.Count;
                            //     states.Add(new State { Scripts = new int[0] });
                            //     layerStates.Add(editorState.CompiledIndex);
                            // }
                            continue;
                        }

                        var   editorScripts = editorState.Scripts;
                        int[] stateScripts  = new int[editorScripts.Count];
                        for (int j = 0; j < editorScripts.Count; ++j)
                        {
                            int scriptIndex = scripts.Count;
                            stateScripts[j] = scriptIndex;
                            var editorScript = editorScripts[j];
                            var script       = editorScript.Script.Clone();
                            ApplyFieldsUsingSettings(script, editorScript.FieldsUsingSettings, Settings);
                            scripts.Add(script);
                        }
                        int stateIndex = states.Count;
                        editorState.CompiledIndex = (StateIndex)states.Count;
                        states.Add(new State {
                            Scripts = stateScripts
                        });
                        layerStates.Add((StateIndex)(int)stateIndex);
                    }
                    layers[layerIndex] = new Layer
                    {
                        States = layerStates.ToArray(),
                    };
                }

                // Compile all the transitions now that we have state indices in place
                for (int layerIndex = 0; layerIndex < layersBuilder.Count; ++layerIndex)
                {
                    var editorStates     = layersBuilder[layerIndex];
                    var layerTransitions = new List <Transition>();
                    foreach (var editorStateIndex in editorStates)
                    {
                        var editorState = EditorStates[(int)editorStateIndex];
                        foreach (var editorTransitionIndex in editorState.TransitionsOut)
                        {
                            var editorTransition     = EditorTransitions[(int)editorTransitionIndex];
                            var transitionTriggers   = new List <Trigger>();
                            var transitionExpression = new List <Transition.Operator>();
                            exprWorklist.Clear();
                            exprWorklist.Add(editorTransition.Expression);
                            while (exprWorklist.Count > 0)
                            {
                                int exprIndex = exprWorklist.Count - 1;
                                var expr      = exprWorklist[exprIndex];
                                exprWorklist.RemoveAt(exprIndex);
                                exprWorklist.AddRange(expr.Subexpressions);
                                switch (expr.Type)
                                {
                                case EditorTransitionExpressionType.False:
                                    transitionExpression.Insert(0, Transition.Operator.False);
                                    break;

                                case EditorTransitionExpressionType.True:
                                    transitionExpression.Insert(0, Transition.Operator.True);
                                    break;

                                case EditorTransitionExpressionType.Trigger:
                                {
                                    transitionTriggers.Insert(0, expr.Trigger);
                                    transitionExpression.Insert(0, Transition.Operator.Push);
                                }
                                break;

                                case EditorTransitionExpressionType.And:
                                    for (int i = 1; i < expr.Subexpressions.Count; ++i)
                                    {
                                        transitionExpression.Insert(0, Transition.Operator.And);
                                    }
                                    break;

                                case EditorTransitionExpressionType.Or:
                                    for (int i = 1; i < expr.Subexpressions.Count; ++i)
                                    {
                                        transitionExpression.Insert(0, Transition.Operator.Or);
                                    }
                                    break;
                                }
                            }

                            var transition = new Transition
                            {
                                Expression = transitionExpression.ToArray(),
                                Triggers   = transitionTriggers.ToArray(),
                            };
                            var fromState = EditorStates[(int)editorTransition.From];
                            switch (fromState.SpecialState)
                            {
                            case EditorSpecialStateType.Normal:
                                transition.FromState = fromState.CompiledIndex;
                                break;

                            case EditorSpecialStateType.LayerAny:
                                transition.FromState = StateIndex.Any;
                                break;

                            case EditorSpecialStateType.LayerEnter:
                                transition.FromState = StateIndex.Idle;
                                break;

                            case EditorSpecialStateType.LayerExit:
                                Debug.LogError("Can't transition from a state with the Exit type");
                                transition.FromState = StateIndex.Invalid;
                                break;
                            }
                            var toState = EditorStates[(int)editorTransition.To];
                            switch (toState.SpecialState)
                            {
                            case EditorSpecialStateType.Normal:
                                transition.ToState = toState.CompiledIndex;
                                break;

                            case EditorSpecialStateType.LayerAny:
                                Debug.LogError("Can't transition to a state with the Any type");
                                transition.ToState = StateIndex.Invalid;
                                break;

                            case EditorSpecialStateType.LayerEnter:
                                Debug.LogError("Can't transition to a state with the Enter type (should be Exit)");
                                transition.ToState = StateIndex.Invalid;
                                break;

                            case EditorSpecialStateType.LayerExit:
                                transition.ToState = StateIndex.Idle;
                                break;
                            }
                            layerTransitions.Add(transition);
                        }
                    }
                    layers[layerIndex].Transitions = layerTransitions.ToArray();
                }

                serialized["Layers"]  = layers;
                serialized["Scripts"] = scripts.ToArray();
                serialized["States"]  = states.ToArray();
            }

            //-----------------------------------------------------------------
            // Write the JSON for the golem into the golem asset
            //-----------------------------------------------------------------
            {
                Golem.References.Clear();
                var    serializer = Serialization.GetSerializer(Golem.References);
                fsData data;
                serializer.TrySerialize(serialized, out data);
                Golem.Asset.Json = fsJsonPrinter.PrettyJson(data);
                Golem.Asset.Data = data;
                // Debug.Log("References: " + Entity.References.Count + " @ " + Entity.References.GetHashCode());
            }

            //-----------------------------------------------------------------
            // Write the editor JSON
            //
            // This is done second so that register assignments get saved into
            // the wires. We can use this to inspect a running circuit!
            //-----------------------------------------------------------------
            {
                // By not clearing, the serializer will reuse the previous set of references
                // _circuitContainer.References.Clear();

                var serializer = Serialization.GetSerializer(Golem.References);
                serialized.Clear();
                serialized["EditorCells"]       = EditorCells;
                serialized["EditorWires"]       = EditorWires;
                serialized["EditorStates"]      = EditorStates;
                serialized["EditorTransitions"] = EditorTransitions;
                serialized["EditorVariables"]   = EditorVariables;
                serialized["Settings"]          = Settings.Values;

                fsData data;
                serializer.TrySerialize(serialized, out data);
                EditorAsset.Json = fsJsonPrinter.PrettyJson(data);
                EditorAsset.Data = data;
                // Debug.Log("References: " + Golem.References.Count + " @ " + Golem.References.GetHashCode());
            }

            //-----------------------------------------------------------------
            // Mark everything we changed as needing to be saved
            //-----------------------------------------------------------------
            GolemEditorUtility.SetDirty(Golem);
        }
Exemplo n.º 3
0
        //-----------------------------------------------------
        // OnInspectorGUI
        //-----------------------------------------------------
        public override void OnInspectorGUI()
        {
            if (_editable == null)
            {
                EditorGUILayout.Space();
                EditorGUILayout.LabelField("Golem failed to load");
                EditorGUILayout.Space();
                return;
            }

            //-------------------------------------------------
            // Graph editor link
            //-------------------------------------------------
            EditorGUILayout.Space();
            if (GUILayout.Button("Open Editor"))
            {
                GolemEditorWindow.Open(_editable);
            }
            EditorGUILayout.Space();

            EditorGUI.BeginChangeCheck();

            //-------------------------------------------------
            // Aspects
            //-------------------------------------------------
            EditorGUILayout.LabelField("Aspects", EditorStyles.boldLabel);

            // Dropdown for adding an aspect of a new type
            {
                var labelRect    = EditorGUILayout.GetControlRect();
                var dropdownRect = new Rect(labelRect);
                dropdownRect.xMin += EditorGUIUtility.labelWidth;
                labelRect.xMax     = dropdownRect.xMin;
                if (EditorGUI.DropdownButton(dropdownRect, new GUIContent("New Aspect..."), FocusType.Passive))
                {
                    var menu        = new GenericMenu();
                    var aspectTypes = Assembly.GetAssembly(typeof(Aspect))
                                      .GetTypes()
                                      .Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(Aspect)))
                                      .Where(myType => !_editable.EditorAspects.Any(existing => existing.Aspect.GetType() == myType))
                                      .ToList();
                    aspectTypes.Sort((a, b) => a.Name.CompareTo(b.Name));
                    if (aspectTypes.Count == 0)
                    {
                        menu.AddDisabledItem(new GUIContent("No other aspects exist!"));
                    }
                    foreach (var type in aspectTypes)
                    {
                        menu.AddItem(
                            new GUIContent(ObjectNames.NicifyVariableName(type.Name)),
                            false,
                            (object _type) => this.addAspectType((Type)_type),
                            type
                            );
                    }

                    menu.DropDown(dropdownRect);
                }
            }

            // Each existing aspect
            {
                var aspects = _editable.EditorAspects;
                for (int j = aspects.Count - 1; j >= 0; --j)
                {
                    GolemAspectEditorData             editableAspect  = aspects[j];
                    InspectableFieldInfo[]            aspectFields    = editableAspect.AspectFields;
                    InspectableVariablePropertyInfo[] aspectVariables = editableAspect.AspectVariables;

                    EditorGUILayout.Space();
                    Rect foldoutRect  = EditorGUILayout.GetControlRect();
                    Rect rhsToolsRect = foldoutRect;
                    foldoutRect.xMax       = foldoutRect.xMax - EditorGUIUtility.singleLineHeight;
                    rhsToolsRect.xMin      = foldoutRect.xMax;
                    editableAspect.Foldout = EditorGUI.Foldout(foldoutRect, editableAspect.Foldout, editableAspect.Field.Name);
                    {
                        if (GUI.Button(rhsToolsRect, "X"))
                        {
                            _editable.RemoveAspect(editableAspect);
                        }
                    }
                    if (!editableAspect.Foldout)
                    {
                        continue;
                    }
                    EditorGUI.indentLevel++;

                    // Fields
                    for (int i = 0; i < aspectFields.Length; ++i)
                    {
                        InspectableFieldInfo fieldInfo = aspectFields[i];
                        GolemEditorUtility.EditorGUILayoutGolemField(
                            fieldInfo.InspectableType,
                            fieldInfo.SpecificType,
                            fieldInfo.FieldInfo,
                            editableAspect.Aspect,
                            editableAspect.FieldsUsingSettings,
                            _editable
                            );
                    }

                    // Variables, if they exist
                    if (aspectVariables.Length > 0)
                    {
                        EditorGUILayout.Space();
                        EditorGUILayout.LabelField("Variables", EditorStyles.boldLabel);
                        EditorGUI.BeginDisabledGroup(!EditorApplication.isPlaying);
                        var variables = _editable.Golem.Variables;
                        for (int i = 0; i < aspectVariables.Length; ++i)
                        {
                            var labelRect = EditorGUILayout.GetControlRect();
                            var position  = new Rect(labelRect);
                            position.xMin += EditorGUIUtility.labelWidth;
                            labelRect.xMax = position.xMin;
                            var name = aspectVariables[i].VariableAttribute.Name;
                            EditorGUI.LabelField(labelRect, new GUIContent(aspectVariables[i].PropertyInfo.Name, aspectVariables[i].VariableAttribute.Tooltip));
                            var variableType = aspectVariables[i].PropertyInfo.PropertyType;
                            variables.InspectorSet(name, variableType, GolemEditorUtility.EditorGUIField(position, aspectVariables[i].Type, variableType, variables.InspectorGet(name, variableType)));
                        }
                        EditorGUI.EndDisabledGroup();
                    }

                    EditorGUI.indentLevel--;
                }
            }

            //-------------------------------------------------
            // Variables
            //-------------------------------------------------
            if (EditorApplication.isPlaying)
            {
                var variables = _editable.Golem.Variables;
                variables.EditorGUIInspectVariables();
            }
            else
            {
                var variables = _editable.EditorVariables;
                EditorGUILayout.Space();
                EditorGUILayout.LabelField(new GUIContent("Variable Initialization", "Derived from Aspects with properties that have the [Variable] attribute"), EditorStyles.boldLabel);

                for (int i = 0; i < variables.Count; ++i)
                {
                    var variable = variables[i];

                    var labelRect = EditorGUILayout.GetControlRect();
                    var position  = new Rect(labelRect);
                    position.xMin += EditorGUIUtility.labelWidth;
                    labelRect.xMax = position.xMin;

                    EditorGUI.LabelField(labelRect, new GUIContent(variable.Name, variable.Tooltip));
                    variable.InitialValue = GolemEditorUtility.EditorGUIField(position, variable.InspectableType, variable.Type, variable.InitialValue);
                }
            }

            if (EditorGUI.EndChangeCheck())
            {
                _editable.Save();
            }

            //-------------------------------------------------
            // Settings
            //-------------------------------------------------
            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
            Settings settings = _editable.Settings;

            if (settings != null)
            {
                EditorGUI.BeginChangeCheck();
                settings.DoEditorGUILayout(true);
                if (EditorGUI.EndChangeCheck())
                {
                    _editable.Save();
                }
                EditorGUILayout.Space();
                _editable.EditorAsset.InheritSettingsFrom = EditorGUILayout.ObjectField("Inherit From", _editable.EditorAsset.InheritSettingsFrom, typeof(SettingsAsset), false) as SettingsAsset;
                Settings current = settings.Parent;
                while (current != null)
                {
                    EditorGUI.BeginChangeCheck();
                    current.DoEditorGUILayout(false);
                    if (EditorGUI.EndChangeCheck() && current.SettingsOwner)
                    {
                        EditorUtility.SetDirty(current.SettingsOwner);
                    }
                    current = current.Parent;
                }
            }
        }