Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge flow subgraph implementation #25

Merged
merged 15 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Editor/Core/Annotations/CustomNodeViewAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Ceres.Editor
public sealed class CustomNodeViewAttribute : Attribute
{
/// <summary>
/// View bound node type
/// View bound node type, leave null to treat node view as hidden in factory
/// </summary>
public Type NodeType { get; }

Expand Down
68 changes: 68 additions & 0 deletions Editor/Core/Editors/APIUpdateConfigEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Linq;
using Ceres.Graph;
using UnityEditor;
using UnityEngine;
using UEditor = UnityEditor.Editor;

namespace Ceres.Editor
{
[CustomEditor(typeof(APIUpdateConfig))]
public class APIUpdateConfigEditor : UEditor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Update API"))
{
APIUpdater.UpdateAPI(typeof(ScriptableObject), (APIUpdateConfig)target);
}
}
}

public static class APIUpdater
{
public static void UpdateAPI(Type filterAssetType, APIUpdateConfig updateConfig)
{
var assets = AssetDatabase.FindAssets($"t:{filterAssetType}")
.Select(x => AssetDatabase.LoadAssetAtPath<ScriptableObject>(AssetDatabase.GUIDToAssetPath(x)))
.Where(x => x is ICeresGraphContainer)
.ToList();
foreach (var asset in assets)
{
UpdateAPI(asset, updateConfig);
}
}

private static void UpdateAPI(ScriptableObject asset, APIUpdateConfig updateConfig)
{
var serializeObject = new SerializedObject(asset)
{
forceChildVisibility = true
};
bool isDirty = false;
SerializedProperty iterator = serializeObject.GetIterator();
while (iterator.NextVisible(true))
{
if (iterator.propertyType == SerializedPropertyType.ManagedReference)
{
foreach (var pair in updateConfig.nodeRedirectors)
{
if (iterator.managedReferenceFullTypename == pair.source.GetFullTypeName())
{
iterator.managedReferenceValue = JsonUtility.FromJson(JsonUtility.ToJson(iterator.managedReferenceValue), pair.target.ToType());
isDirty = true;
}
}
}
}
if (isDirty)
{
CeresLogger.Log($"{asset.name} api update succeed");
serializeObject.ApplyModifiedProperties();
EditorUtility.SetDirty(asset);
}
serializeObject.Dispose();
}
}
}
3 changes: 3 additions & 0 deletions Editor/Core/Editors/APIUpdateConfigEditor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 3 additions & 28 deletions Editor/Core/Editors/CeresSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ public enum GraphEditorDisplayMode
Debug
}

public enum GraphSerializeMode
{
FasterRuntime,
SmallerBuilds
}

private static CeresSettings _setting;

[SerializeField, HideInInspector]
Expand All @@ -27,9 +21,6 @@ public enum GraphSerializeMode
[SerializeField, HideInInspector]
private bool disableILPostProcess;

[SerializeField, HideInInspector]
private GraphSerializeMode graphSerializeMode;

/// <summary>
/// Ceres graph editor view display mode
/// </summary>
Expand All @@ -39,16 +30,6 @@ public enum GraphSerializeMode
/// Ceres graph editor will display in debug mode
/// </summary>
public static bool DisplayDebug => DisplayMode == GraphEditorDisplayMode.Debug;

/// <summary>
/// Ceres graph serialization mode
/// </summary>
public static GraphSerializeMode SerializeMode => instance.graphSerializeMode;

/// <summary>
/// Ceres graph will produce a smaller build in serialization
/// </summary>
public static bool SmallerBuilds => SerializeMode == GraphSerializeMode.SmallerBuilds;

public static void SaveSettings()
{
Expand All @@ -71,11 +52,6 @@ private class Styles
"Disable IL Post Process, default Ceres will emit il after syntax analysis step to enhance " +
"runtime performance, disable can speed up editor compilation, recommend to enable in final " +
"production build");

public static readonly GUIContent GraphSerializeModeStyle = new("Serialize Mode",
"Set graph serialize mode. " +
"\nFaster runtime: Deserialization is optimized for runtime performance. This is the default." +
"\nSmaller builds: Produce a smaller build but may have an impact on deserialization performance");
}

private CeresSettingsProvider(string path, SettingsScope scope = SettingsScope.User) : base(path, scope) { }
Expand All @@ -102,26 +78,25 @@ public override void OnGUI(string searchContext)
GUILayout.Label("Runtime Settings", titleStyle);
GUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.PropertyField(disableILPostProcessProp, Styles.DisableILPostProcessStyle);
EditorGUILayout.PropertyField(_serializedObject.FindProperty("graphSerializeMode"), Styles.GraphSerializeModeStyle);
GUILayout.EndVertical();
if (_serializedObject.ApplyModifiedPropertiesWithoutUndo())
{
if (disableILPostProcessProp.boolValue && !ScriptingSymbol.ContainsScriptingSymbol(DisableILPostProcessSymbol))
{
CeresAPI.Log("Disable ILPP");
CeresLogger.Log("Disable ILPP");
ScriptingSymbol.AddScriptingSymbol(DisableILPostProcessSymbol);
}
else if (ScriptingSymbol.ContainsScriptingSymbol(DisableILPostProcessSymbol))
{
CeresAPI.Log("Enable ILPP");
CeresLogger.Log("Enable ILPP");
ScriptingSymbol.RemoveScriptingSymbol(DisableILPostProcessSymbol);
}
CeresSettings.SaveSettings();
}
}

[SettingsProvider]
public static SettingsProvider CreateMyCustomSettingsProvider()
public static SettingsProvider CreateCeresSettingsProvider()
{
var provider = new CeresSettingsProvider("Project/Ceres Settings", SettingsScope.Project)
{
Expand Down
4 changes: 2 additions & 2 deletions Editor/Core/UIElements/Graph/CeresGraphEditorWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,9 @@ public TContainer GetContainer()

protected override void Reload()
{
if (!Identifier.IsValid()) return;
if (!Identifier.IsValid() || !this) return;

CeresAPI.Log($"Reload graph from identifier [{Identifier}]");
CeresLogger.Log($"Reload graph from identifier [{Identifier}]");
Container = GetContainer();
OnReloadGraphView();
Repaint();
Expand Down
69 changes: 48 additions & 21 deletions Editor/Core/UIElements/Graph/CeresGraphView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ public abstract class CeresGraphView: GraphView, IVariableSource

public HashSet<ICeresNodeView> NodeViews { get; } = new();

public CeresNodeSearchWindow SearchWindow { get; private set; }
protected CeresNodeSearchWindow SearchWindow { get; private set; }

private static readonly Dictionary<string, StyleSheet> StyleSheetsCache = new();

private bool _dirtyFlag;

protected CeresGraphView(CeresGraphEditorWindow editorWindow)
{
EditorWindow = editorWindow;
Expand All @@ -43,6 +45,7 @@ protected CeresGraphView(CeresGraphEditorWindow editorWindow)
serializeGraphElements = OnCopySerializedGraph;
unserializeAndPaste = OnPasteSerializedGraph;
nodeCreationRequest = OnNodeCreationRequest;
graphViewChanged = OnGraphViewChanged;
RegisterCallback<DetachFromPanelEvent>(OnGraphViewDestroy);
}

Expand Down Expand Up @@ -74,6 +77,23 @@ protected virtual void OnPasteSerializedGraph(string operationName, string seria

}

protected virtual GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
{
if (graphViewChange.movedElements != null && graphViewChange.movedElements.Any())
{
SetDirty();
}
if (graphViewChange.elementsToRemove != null && graphViewChange.elementsToRemove.Any())
{
SetDirty();
}
if (graphViewChange.edgesToCreate != null && graphViewChange.edgesToCreate.Any())
{
SetDirty();
}
return graphViewChange;
}

/// <summary>
/// Add custom blackboard to graph
/// </summary>
Expand Down Expand Up @@ -165,6 +185,7 @@ public virtual void AddNodeView(ICeresNodeView nodeView)
{
NodeViews.Add(nodeView);
AddElement(nodeView.NodeElement);
SetDirty();

/* Sync node view lifetime scope with NodeElement */
nodeView.NodeElement.RegisterCallback<DetachFromPanelEvent>(_ =>
Expand Down Expand Up @@ -202,7 +223,7 @@ public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAd
var compatiblePorts = new List<Port>();
if (startPort is not CeresPortElement startPortView)
{
CeresAPI.LogWarning($"{startPort.GetType()} is not supported in Ceres default graph view");
CeresLogger.LogWarning($"{startPort.GetType()} is not supported in Ceres default graph view");
return compatiblePorts;
}

Expand All @@ -217,25 +238,6 @@ public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAd
return compatiblePorts;
}

/// <summary>
/// Serialize graph to container
/// </summary>
/// <param name="container"></param>
/// <returns></returns>
public virtual bool SerializeGraph(ICeresGraphContainer container)
{
return true;
}

/// <summary>
/// Deserialize graph from container
/// </summary>
/// <param name="container"></param>
public virtual void DeserializeGraph(ICeresGraphContainer container)
{

}

/// <summary>
/// Add shared variables to graph's blackboard
/// </summary>
Expand Down Expand Up @@ -286,6 +288,31 @@ public virtual void OpenSearch(Vector2 screenPosition, CeresPortView portView)
USearchWindow.Open(new SearchWindowContext(screenPosition), SearchWindow);
}

/// <summary>
/// Marks graph view as dirty
/// </summary>
public void SetDirty()
{
_dirtyFlag = true;
}

/// <summary>
/// Clear graph view's dirty flag.
/// </summary>
public void ClearDirty()
{
_dirtyFlag = false;
}

/// <summary>
/// Whether graph view is dirty
/// </summary>
/// <returns></returns>
public bool IsDirty()
{
return _dirtyFlag;
}

private void OnGraphViewDestroy(DetachFromPanelEvent evt)
{
OnDestroy();
Expand Down
4 changes: 2 additions & 2 deletions Editor/Core/UIElements/Graph/Nodes/CeresNodeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ protected void FillDefaultNodeProperties()
}
catch(Exception e)
{
CeresAPI.LogWarning($"Can not draw property {NodeType.Name}.{p.Name}, {e}");
CeresLogger.LogWarning($"Can not draw property {NodeType.Name}.{p.Name}, {e}");
}
});
NodeElement.mainContainer.Add(fieldContainer);
Expand Down Expand Up @@ -281,7 +281,7 @@ protected void FillDefaultNodePorts()
}
catch (Exception e)
{
CeresAPI.LogWarning($"Can not draw port {NodeType.Name}.{p.Name}, {e}");
CeresLogger.LogWarning($"Can not draw port {NodeType.Name}.{p.Name}, {e}");
}
});
}
Expand Down
36 changes: 19 additions & 17 deletions Editor/Core/UIElements/Graph/Nodes/NodeViewFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ private NodeViewFactory()
private static bool IsValidType(Type type)
{
if (type.IsAbstract) return false;
if (type.GetCustomAttribute<CustomNodeViewAttribute>() != null) return true;
var customViewAttribute = type.GetCustomAttribute<CustomNodeViewAttribute>();
if (customViewAttribute is { NodeType: not null }) return true;
return type.GetInterfaces().Any(t => t == typeof(INodeViewResolver));
}

Expand All @@ -105,6 +106,13 @@ public ICeresNodeView CreateInstance(Type type, CeresGraphView graphView)
.Where(resolver => resolver.CustomNodeViewAttribute != null
&& TryAcceptNodeEditor(resolver.CustomNodeViewAttribute, type))
.ToList();

foreach (var resolver in _resolvers.Except(customNodeResolvers))
{
if (!resolver.IsAcceptable(type)) continue;
return ((INodeViewResolver)Activator.CreateInstance(resolver.Type)).CreateNodeView(type, graphView);
}

customNodeResolvers.Sort(new ResolverStructure.Comparer());
var customNodeResolver = customNodeResolvers.FirstOrDefault();
if(customNodeResolver != null)
Expand All @@ -113,12 +121,6 @@ public ICeresNodeView CreateInstance(Type type, CeresGraphView graphView)
/* Must have (Type, CeresGraphView) constructor */
return (ICeresNodeView)Activator.CreateInstance(viewType, type, graphView);
}

foreach (var resolver in _resolvers)
{
if (!resolver.IsAcceptable(type)) continue;
return ((INodeViewResolver)Activator.CreateInstance(resolver.Type)).CreateNodeView(type, graphView);
}
return null;
}

Expand All @@ -135,6 +137,16 @@ public ICeresNodeView CreateInstanceResolved(Type type, CeresGraphView graphView
.Where(resolver => resolver.CustomNodeViewAttribute != null
&& TryAcceptNodeEditor(resolver.CustomNodeViewAttribute, type))
.ToList();
foreach (var resolver in _resolvers.Except(customNodeResolvers))
{
if (type.IsGenericTypeDefinition)
{
type = type.MakeGenericType(genericArguments);
}
if (!resolver.IsAcceptable(type)) continue;
return ((INodeViewResolver)Activator.CreateInstance(resolver.Type)).CreateNodeView(type, graphView);
}

customNodeResolvers.Sort(new ResolverStructure.Comparer());
var customNodeResolver = customNodeResolvers.FirstOrDefault();
if(customNodeResolver != null)
Expand All @@ -153,16 +165,6 @@ public ICeresNodeView CreateInstanceResolved(Type type, CeresGraphView graphView
/* Must have (Type, CeresGraphView) constructor */
return (ICeresNodeView)Activator.CreateInstance(viewType, type, graphView);
}

foreach (var resolver in _resolvers)
{
if (type.IsGenericTypeDefinition)
{
type = type.MakeGenericType(genericArguments);
}
if (!resolver.IsAcceptable(type)) continue;
return ((INodeViewResolver)Activator.CreateInstance(resolver.Type)).CreateNodeView(type, graphView);
}
return null;
}

Expand Down
Loading