Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Question AssetDatabase.Refresh Question with InitializeOnLoad script

Discussion in 'Scripting' started by DreamNoms, Jun 10, 2024.

  1. DreamNoms

    DreamNoms

    Joined:
    Jul 7, 2017
    Posts:
    2
    Hello people!

    I am writing a script that runs in the editor (it is not attached to any object). The script detects if the "Scenes in Build" (in Unity's Build Settings) have changed. If they have changed since the last Editor update frame, this script will write the names of the scenes to a file (deleting the old contents of the file first).

    Here is a snippet of the code:

    Code (CSharp):
    1. [InitializeOnLoad]
    2. class UpdateScenesEnum
    3. {
    4.     //an array of the scenes in the build
    5.     private static string[] scenes=new string[0];
    6.  
    7.     static UpdateScenesEnum()
    8.     {
    9.         EditorApplication.update += Update;
    10.     }
    11.  
    12.     //This function is continously called while in the editor
    13.     static void Update()
    14.     {
    15.         if (arraysEqual(scenes, getAllBuildScenes()) == false)
    16.         {
    17.             //This code write all the scenes to an enum called "Scenes".
    18.             Debug.Log("Scenes in Build changed. Updating \"Scenes\" enum.");
    19.  
    20.             //Set the scenes array to be equal to the names of scenes in build
    21.             scenes = getAllBuildScenes();
    22.  
    23.             //writes an enum with the list of scenes as each enum item into the given file
    24.             string path = "Assets/Scripts/ScenesEnum.cs";
    25.             string textToWrite = "public enum Scenes\n{\n" + convertArrayToString(getAllBuildScenes()) + "}";
    26.             File.WriteAllText(path, textToWrite);
    27.  
    28.             //Refreshes all files that have changed (including also resetting this file)
    29.             AssetDatabase.Refresh();
    30.         }
    31.        
    32.     }
    While the code works mostly as intended, I noticed a small hiccup. When AssetDatabase.Refresh has finished its loading bar in Unity, this script also resets. That is, the scenes string array is set back to have 0 items causing the if statement to run a second time.

    So I have exactly two Debug.Logs that show up in the inspector: one shows up after I change something about the scenes in the build settings (a good thing), and another shows up after the refresh has completed (not good).

    I don't understand why the if statement is running twice and also why is it running only twice and not stuck in a forever loop. It seems that this script only resets when the contents of the other file change. Why is that?
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,263
    You are writing a script file. By changing a script, domain reload occurs. This means InitializeOnLoad will run all over again.

    First, use ScriptableSingleton for editor classes which should persist between domain reloads.

    Secondly, you do NOT (!!!!!!!!) want to run an update method in the editor that may, at any given time, change a script file and cause a domain reload. This is not the way to write editor scripts.

    Instead, find the appropriate event callback that you can hook into. If there is no fitting event, consider the time you need to apply that change. Is it when making builds only? Then hook this into the build process. If neither is appropriate, find another solution .. there's always an alternative or workaround.

    Here and generally you ought to avoid modifying script files whenever possible. It's easy to avoid: rather than writing an enum write a simple .txt file where each line contains a scene name. Then write your code so that it will read the scene names from that txt file rather than using an enum. You could also write json if you need to store more complex information.


    Finally, you wouldn't call AssetDatabase.Refresh when changing a file - this doesn't just import your script, it reimports everything that may have changed and also does a whole lot of other stuff (unloading cached resources). The proper way here would be to simply call AssetDatabase.ImportAsset(path) to import only the file that you changed. It would still reload the domain though because it's a script file.
     
  3. DreamNoms

    DreamNoms

    Joined:
    Jul 7, 2017
    Posts:
    2
    Thank you so much for your reply! Now I understand why my script is running twice in a row. I am still pretty new at editor scripting, and it seems I have a lot to learn about it. I have experimented a little bit with custom editors for gameobject scripts, but not so much on editor extensions. Are there any good tutorial series you would recommend for editor scripting?


    Even though the actual execution of writing to the file is fairly tightly controlled by an if statement, perhaps it would be best to not make it run automatically. You mentioned "event callback" but I do not know what that is. I want this event to trigger whenever the user has changed something about the Build settings (but not necessarily having built the project). I think I am looking for an event callback that triggers when the user closes the Build Settings window. Does such an event exist or how could I go about triggering an event like a public void OnCloseBuildSettingsWindow?

    I am looking at this forum on the topic and it is mentioning something about an event that is triggered when the window closes: https://forum.unity.com/threads/firing-an-event-whenever-an-editor-window-is-closed.1434517/
    Is this what you had in mind regarding "event callback" or is that about the same as using the update and if statement that I did?

    It is good to know that writing to a .txt file does not refresh all scripts. Your idea of writing the scenes to a text file is a good idea for most situations. But unfortunately I think my only option is to write to a script file. Enums allow the options to auto-fill while scripting (preventing spelling mistakes, and also will throw an error if that scene does not exist). That is my purpose for writing this script in the first place; I want the data to be in an enum so the names of the scenes are easily accessible while scripting. Refreshing the assets does not seem like such a bad thing because I do want the scripts to recompile (so they will throw an error) if the enum item they were referencing no longer exists.

    This asset I am working on is actually for the Asset Store. I don't want to break too many editor protocols and your reply has definitely made some things clearer. I think that I should definitely more tightly control when the script executes so the user isn't surprised by an asset refresh. I am thinking maybe when the user closes the Build settings, I should show a popup to prompt the user if they want to refresh the Scenes enum (and therefore their assets). That way it is easy for the user to update their Scenes enum. Is this acceptable editor script behaviour or should I only rely on using Menu buttons? Thank you again for your reply!