Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Were are we supposed to procedurally generate things, if not in OnValidate?

Discussion in 'Scripting' started by methusalah999, Aug 28, 2020.

  1. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    627
    I've always implement procedural generation into the OnValidate method. It seems the best place to me because in the lack of a game loop in edit mode, OnValidate is the only method that is called back when the component data is updated.

    Typically, when I change a data, I want my content to be updated immediatly and automatically.

    But as stated in the doc (https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html) this method should not be used for anything else than data check. Indeed, when I try and mess with the lifcycle of my unity objects like calling
    AddComponent()
    or
    Instanciate()
    , I get warnings into my console

    SendMessage cannot be called during Awake, CheckConsistency, or OnValidate


    I understand that the unity objects are not garanteed to be ready enough during the OnValidate method to call such functions. In practice, and to my knowledge, I've never experienced any bug doing so but ok, let's not do this.

    So. what do I do?
     
    Nefisto likes this.
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,722
    If I'm doing procedural generation in the editor, I usually do it with a button or some other manual method. I can see the appeal of doing it automatically though (e.g. sliders for different generation params).

    It's true there isn't a reliable game loop happening in the editor, but I believe if you use e.g.
    ExecuteInEditMode
    , then you can put your code in Update. It's true Update won't be called 60 times per second like in play mode, but I do believe it is called whenever you change values in the inspector. So if you're modifying procedural parameters, it should trigger update calls. I could be wrong though.
     
  3. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    There's not an actual game loop, because that wouldn't make much sense without playing the game.
    As you probably know, there are ways to force Update to run when the scene changes, using ExecuteInEditMode, but we've all probably seen many cases where that just messes things up. Even worse when it's used in order to add editor stuff in Update.

    However, there are some other ways to achieve something similar. For example, you can set/add your own callbacks with UnityEditor.EditorApplication.update.

    That one is called regularly, but it also requires additional handling.

    I'd say that description doesn't really say you should exclusively use it for validation.
    You can certainly do a few more things than pure data validation.
    When you change values for something that's generated based on these values, you could argue that the generated stuff is no longer "valid"...

    However, for that special case of generating actual GameObjects or adding components, the warning appears, and you're right that one should probably worry a little even if you cannot identify any malfunction.

    With all the above in mind, I'd probably pair OnValidate with the editor update I mentioned earlier.
    There are basically two ways:
    1) add your callback once, and use a flag that you set dirty in OnValidate.
    2) add your callback in OnValidate, and have it remove itself when it's called.

    This should help to avoid the spam of warnings in the console as it's executed in a non-critical part of the ediitor's update cycle.
     
  4. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    627
    ExecuteInEditMode and ExecuteAlways are indeed usefull but are called in a lot of situation.

    However, raising a flag in the OnValidate method and check it during the ApplicationEditor.update callback solves the problem. The second solution is also intersting, because you can get rid of said flag.

    Thanks to both of you, I will play with these solutions.

    Now, wouldn't it be great to have a OnInspectorChanged callback? ^^
     
    orionsyndrome and PraetorBlue like this.
  5. GuiMendel

    GuiMendel

    Joined:
    Oct 12, 2021
    Posts:
    1
    Hey guys,

    I'm trying to implement this dirty flag method, but I can't find any (useful) documentation of help with using EditorApplication.update. The official documentation doesn't say anything over how it should be used.

    My guess is to assign some callback to it like
    EditorApplication.update = () => print("hell yeah");
    , but when should I do this? At which point should I make this assignment?

    Here's how I'm currently tackling this:

    Code (CSharp):
    1.   bool shouldRedraw = false;
    2.  
    3.   private void OnValidate()
    4.   {
    5.     shouldRedraw = true;
    6.  
    7.     EditorApplication.update = () =>
    8.     {
    9.       if (shouldRedraw == false) return;
    10.       shouldRedraw = false;
    11.  
    12.       MeshFilter meshFilter = GetComponent<MeshFilter>();
    13.  
    14.       mesh = new Mesh();
    15.       mesh.vertices = new Vector3[4];
    16.       mesh.triangles = new int[6] { 0, 3, 1, 3, 0, 2 };
    17.  
    18.       DestroyImmediate(meshFilter.sharedMesh);
    19.       meshFilter.sharedMesh = mesh;
    20.  
    21.       UpdateMeshSize(initialSize);
    22.     };
    23.   }
     
    Last edited: Jun 1, 2023
  6. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    627
    OnValidate should not be used for game logic at all since it is not launched in the built game (only in the Unity editor)

    In your case, I don't see why not simply use the Update method of the MonoBehaviour.

    If you connect your logic to the EditorApplication.update event, then it will run even if your game object or component is deactivated/disabled.

    In general, if you need to react to some event in a component, you connecte the function to the event in OnEnable and disconnect it in OnDisable.

    Code (CSharp):
    1. void OnEnable(){
    2.     myEvent += MyCallBack;
    3. }
    4.  
    5. void OnDisable() {
    6.     myEvent -= MyCallBack;
    7. }
    8.  
    9. public void MyCallBack(object sender, EventArgs arg){
    10.     // my logic in reaction to myEvent, if the component is active
    11. }
     
  7. FunRobDev

    FunRobDev

    Joined:
    Jun 3, 2017
    Posts:
    11
    Yes, I faced "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate" a lot of times, because often we need to recreate GameObjects, etc after OnValidate()

    I have a little workaround for the problem, I share with you:
    Code (CSharp):
    1.     private void Refresh()
    2.     {
    3.         // Let's suppose you have this function in your MonoBehaviour class where you recreate
    4.         // your GameObjects and do all the dirty stuff what you need after some property changed.
    5.     }
    6.  
    7. #if UNITY_EDITOR
    8.     void OnValidate()
    9.     {
    10.         base.ExecuteAfter(seconds: 0.01f, ()=> Refresh());
    11.     }
    12.  
    13.     /// <summary>Executes a function after short period of time</summary>
    14.     /// <param name="seconds">delay time from now</param>
    15.     /// <param name="theDelegate">The function which will be called</param>
    16.     public void ExecuteAfter(float seconds, Action theDelegate)
    17.     {
    18.         this.StartCoroutine(ExecuteAfterPrivate(seconds, theDelegate));
    19.     }
    20.  
    21.     private IEnumerator ExecuteAfterPrivate(float seconds, Action theDelegate)
    22.     {
    23.         yield return new WaitForSeconds(seconds);
    24.         theDelegate();
    25.     }
    26. #endif
    27.  
    In this way you execute your refresh function in the next Update loop, and you won't get any error messages about SendMessage.

    Note: you can move the last 2 methods into a subclass and reuse them in other classes.

    God Bless
     
    Last edited: Jun 12, 2023
  8. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    627
    I suggest to have a simple dirty flag that is checked in the update method.
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,848
    The correct answer to this question is still just to write a custom editor for your component type.