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); } }
//--------------------------------------------------------------------- // 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); }
//----------------------------------------------------- // 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; } } }