Seneral / Node_Editor_Framework

A flexible and modular Node Editor Framework for creating node based displays and editors in Unity
https://nodeeditor.seneral.dev
MIT License
2.01k stars 414 forks source link

Saving existing NodeCanvas without losing references to existing NodeCavas.asset file #151

Closed kormyen closed 6 years ago

kormyen commented 6 years ago

Hello!

I am currently using this Node Editor to define connections between level nodes for game worlds.

What is annoying is that after loading and editing a node canvas then saving the file (replacing/"updating" it's existing NodeCanvas.asset file) all scripts in the project referencing that NodeCanvas.asset file loose their reference to it.

This is due to UnityEditor.AssetDatabase.CreateAsset(nodeCanvas, path); in NodeEditorSaveManager.cs. The method's documentation states "...If an asset already exists at path it will be deleted prior to creating a new asset...".

A messy attempt to fix this:

/// <summary>
/// Saves the the given NodeCanvas along with the given NodeEditorStates if specified as a new asset, optionally as working copies
/// </summary>
public static void SaveNodeCanvas (string path, NodeCanvas nodeCanvas, bool createWorkingCopy, bool overwrite = false) 
{
#if !UNITY_EDITOR
    throw new System.NotImplementedException ();
#else

    if (string.IsNullOrEmpty (path)) throw new UnityException ("Cannot save NodeCanvas: No spath specified to save the NodeCanvas " + (nodeCanvas != null? nodeCanvas.name : "") + " to!");
    if (nodeCanvas == null) throw new UnityException ("Cannot save NodeCanvas: The specified NodeCanvas that should be saved to path " + path + " is null!");
    if (nodeCanvas.livesInScene)
        Debug.LogWarning ("Attempting to save scene canvas " + nodeCanvas.name + " to an asset, scene object references will be broken!" + (!createWorkingCopy? " No workingCopy is going to be created, so your scene save is broken, too!" : ""));
#if UNITY_EDITOR
    if (!createWorkingCopy && UnityEditor.AssetDatabase.Contains (nodeCanvas) && UnityEditor.AssetDatabase.GetAssetPath (nodeCanvas) != path) { Debug.LogError ("Trying to create a duplicate save file for '" + nodeCanvas.name + "'! Forcing to create a working copy!"); createWorkingCopy = true; }
#endif

#if UNITY_EDITOR
    nodeCanvas.BeforeSavingCanvas();

    // Preprocess the canvas
    ProcessCanvas (ref nodeCanvas, createWorkingCopy);
    nodeCanvas.livesInScene = false;

    /////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////// NEW ///////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////

    var canvasFile = UnityEditor.AssetDatabase.LoadMainAssetAtPath(path) as NodeCanvas;
    if (overwrite && canvasFile != null)
    {
        // Overwrite existing NodeCanvas to not break references to existing file.

        Object[] allAssets = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(path);
        for (int i = allAssets.Length-1; i >= 0; i--)
        {
            // Clear content of existing asset file
            if (allAssets[i] != canvasFile)
            {
                // If not the main asset, delete it. The main asset is replaced below (CopySerialized). These sub assets are re-added below also (AddSubAsset calls).
                Object.DestroyImmediate(allAssets[i], true);
            }
        }

        UnityEditor.EditorUtility.CopySerialized(nodeCanvas, canvasFile);
        nodeCanvas = canvasFile;
    }
    else
    {
        // Write canvas and editorStates
        UnityEditor.AssetDatabase.CreateAsset(nodeCanvas, path);
    }

    AddSubAssets(nodeCanvas.editorStates, nodeCanvas);

    ///////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////// END NEW /////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////

    // Write nodes + contents
    foreach (Node node in nodeCanvas.nodes)
    { // Write node and additional scriptable objects
        AddSubAsset (node, nodeCanvas);
        AddSubAssets (node.GetScriptableObjects (), node);
        foreach (NodeKnob knob in node.nodeKnobs)
        { // Write knobs and their additional scriptable objects
            AddSubAsset (knob, node);
            AddSubAssets (knob.GetScriptableObjects (), knob);
        }
    }

    UnityEditor.AssetDatabase.SaveAssets ();
    UnityEditor.AssetDatabase.Refresh ();
#else
    // TODO: Node Editor: Need to implement ingame-saving (Resources, AsssetBundles, ... won't work)
#endif

    NodeEditorCallbacks.IssueOnSaveCanvas (nodeCanvas);

#endif
}

We call our updated from NodeEditorUserCache.cs line 259 with NodeEditorSaveManager.SaveNodeCanvas (path, nodeCanvas, true, true);.

The optional argument bool overwrite = false is because we only care about overwriting when saving over our existing NodeCanvas.asset file (pressing the save button from the NodeEditor GUI). Doing the overwrite with LastSession/NodeEditorUserCache saves is causing errors:

Posting to see opinions/advice and if anyone else has run into this problem also. Completely open to feedback and suggestions!

This project has been a big help btw. Cheers.

Seneral commented 6 years ago

Hey, yep good points, this is actually implemented in the develop branch already, pretty similar to what you've done:)

kormyen commented 6 years ago

Oh cool!

I should have checked there first.

Seneral commented 6 years ago

Oh forgot:)