Hey there – I will certainly keep your explanation of the ‘unhappy accident’ in mind as we work with the experimental approach we have.
The graph asset actually holds the true serialization – its just that editing the json text file causes unity to parse it instead, update the graph, and reserialze it. If you never edit the Json text file this will never occur. I made a diagram 🙂 This is from memory so might be 100% accurate 😀
Hello,
I think the main difference is that the code doesn’t modify the yaml file directly and instead reads in and out json and lets the serialization methods you provide do the work. I don’t 100% follow you warning regarding 2 sub-graphs – do you have an image of the situation you could share and I could test it out?
Hey Gavalakis!
I’ve adjusted the JSON serialization for diffability. I noticed you turned it off a while ago and I have another hacky way around it. It might be able to be cleaned up and released if its reasonably stable.
It works by writing to a JSON file when a graph asset is imported (it uses a hacky path strategy). That gets the JSON out of the asset file. It also monitors TextAssets that use the same hacky path strategy. When those change the graph serializedGraph field is updated to reflect the JSON file.
—————————————————–
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;namespace NodeCanvas.Editor
{
///Handles post processing of graph assets
public class GraphAssetPostProcessor
{[InitializeOnLoadMethod]
static void PreInit() {
EditorApplication.delayCall -= Init;
EditorApplication.delayCall += Init;
}static void Init(){
#if UNITY_2019_3_OR_NEWER
ParadoxNotion.Design.AssetTracker.onAssetsImported -= OnAssetsImported;
ParadoxNotion.Design.AssetTracker.onAssetsImported += OnAssetsImported;
#endif
//we track graph assets so that we can access them on a diff thread
AssetTracker.BeginTrackingAssetsOfType(typeof(Graph));
AssetTracker.BeginTrackingAssetsOfType(typeof(TextAsset));
}private const string SERIALIZATION_START = “#—GRAPH_START—“;
private const string SERIALIZATION_END = “#—GRAPH_END—“;
private const string IS_YAML = “%YAML”;//Asset Tracker callback
static void OnAssetsImported(string[] paths) {
var willRefresh = false;
foreach ( var path in paths ) {
var asset = AssetDatabase.LoadAssetAtPath(path, typeof(Graph));
if ( asset is Graph graphAsset)
{
// If the graph has changed we should update the json text asset
var localPath = AssetDatabase.GetAssetPath(graphAsset) + “.json”;
var systemPath = EditorUtils.AssetToSystemPath(localPath);
var pretyJson = ParadoxNotion.Serialization.JSONSerializer.PrettifyJson(graphAsset.GetSerializedJsonData());// has the graph actually changed?
var writeJsonFile = File.Exists(systemPath) == false || File.ReadAllText(systemPath) != pretyJson;
if (writeJsonFile)
{
willRefresh = true;
File.WriteAllText(systemPath, pretyJson);
}
}asset = AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset));
if ( asset is TextAsset textAsset) {// If the json has changed we should update the graph asset
var localPath = AssetDatabase.GetAssetPath(textAsset);
var match = Regex.Match(localPath, @”(?.*\.asset)\.json$”);
if (match.Success)
{
var graphPath = match.Groups[“graph”].Value;
var graph = (Graph)AssetDatabase.LoadAssetAtPath(graphPath, typeof(Graph));
if (graph != null)
{
willRefresh = true;
graph.OverwriteSerializedGraphString(textAsset.text);
EditorUtility.SetDirty(graph);
AssetDatabase.ImportAsset(graphPath); // Do I need to do this? I have no idea!
}
}
}
}
if ( willRefresh ) { EditorApplication.delayCall += () => AssetDatabase.Refresh(); }
}///Append prety json to yaml file as comments for version control diff purposes
static void AppendPrettyJSONComments(Graph graph, string path) {
var systemPath = EditorUtils.AssetToSystemPath(path);
var lines = File.ReadAllLines(systemPath);//not a yaml? bail out.
if ( lines.Length == 0 || !lines[0].StartsWith(IS_YAML) ) { return; }var result = new List(lines.Length);
//clear previous. Unity actually does not keep any changes made to the file, but I don’t trust this will always be the case.
var skip = false;
for ( var i = 0; i < lines.Length; i++ ) {
var line = lines;
if ( line.StartsWith(SERIALIZATION_START) ) { skip = true; }
if ( skip ) { continue; }
if ( line.StartsWith(SERIALIZATION_END) ) {
skip = false;
continue;
}
result.Add(line);
}//add new
result.Add(SERIALIZATION_START);
result.Add(“#The pretty formatted json serialization bellow is only a reference to help in version control diff. Other than that it is not used at all.”);
var pretyJson = ParadoxNotion.Serialization.JSONSerializer.PrettifyJson(graph.GetSerializedJsonData());
var split = pretyJson.Split(new string[] { System.Environment.NewLine }, System.StringSplitOptions.None);
for ( var i = 0; i < split.Length; i++ ) {
var newLine = ‘#’ + split;
result.Add(newLine);
}
result.Add(SERIALIZATION_END);File.WriteAllLines(systemPath, result);
}}
}#endif
This allows changes to the JSON file that serialize into the main asset … or you can just ignore it because the original graph asset is the single source of truth in non-editor mode. It seems to generally work but I’m not a very experienced Editor scripter.
Anyways, I thought I would share.
Hello.
I’m in an awkward position because I do really believe in the value of a visual layout of the flow of a program because it allows you to see how spaghetti your code is, understand what is happening at a glance, and also visually see what went wrong with your logic very quickly … but it is difficult to convince a team of programmers as typing what you want to happen is usually less work and more resilient to change.
A big pinch point for us has been refactoring. So in the case of a FSM when you have a repeated connection flow that needs a change you typically need to manually change each use of it. By “template connection” I meant having something like referenced action list that is stored as an asset. Similar to how you can nest graphs etc. That seems like a lot of work.
Perhaps being able to copy+paste entire action lists instead of singular actions would help?
I also started to look into adding “Replace Action” functionality but aborted due to time constraints on the project.
Below are those ideas in a rough visual
I’ll try to put something together for you soon. Thanks!
Hey there.
Sure, I won’t be offended if the zoom level doesn’t increase in official releases.
😀
Yes, I can do that. I’ll get back to you when I have the results.
Hey – thanks for the reply!
I think the popup does look cleaner, but might not be as useful when you just want a birds eye view of how things are running, top-to-bottom.
What I have here is a bit ugly, but its spend up my workflow and debugging a bit so I am personally good with it being a bit clunky. I recently added stepped colouring so that each subtree is a different colour.
When doing these edits to the graph editor I could definitely understand how multiple graph editor windows would be a bit tricky. I would say that for me this “expand children” feature is a bit more useful in the moment then multiple graph editors.
I can share the edits I made to GraphEditor and Editor.Node … but I’m sure you could do a better job of it being the author an all!
I’m on a differnt task at the moment, but when I’m complete I can get a capture of what a big graph look likes for us atm (with the subtree colouring)
gif showing how it works
got something really crude mostly working – I seem to have broken moving nodes when not pressing “shift” and also some subtrees do not render properly
Thanks so much – the FSM note is a bit concerning to me as we had more FSM usage in the past but have since moved to more BTrees for consistency … hmmm
Creating garbage in states and a zero-garbage policy are noted – but perhaps we are too far gone at this point for a complete audit of the project.
Again, thanks for sharing your experience!
Curious about your performance issues. Any best practices that can be gleamed from this?
Thanks! A bit hesitant to use it since this empty state will register as an active state, even withing a single frame.