Search Unity

Trick: Start and Update without Start() and Update() function

Discussion in 'Scripting' started by Frenzy07, Jun 19, 2014.

  1. Frenzy07

    Frenzy07

    Joined:
    Nov 21, 2012
    Posts:
    12
    Hi all. i have found some undocumented functionality in с#.
    If you call the function Main() then it is performed automatically at startup. It works with the MonoBehaviour and ScriptableObject and can also automatically run Coroutine.
    This feature allows you to create a very elegant code : :cool:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class TMain : ScriptableObject  {
    5.  
    6.     IEnumerator Main () {
    7.         Debug.Log ("Start");
    8.         while(true){
    9.             Debug.Log ("Update");
    10.             yield return null;
    11.         }
    12.     }
    13. }
     
  2. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Well now, that's interesting...

    (Though I don't really see much point using it to re-implement existing functionality via coroutines...)

    Edit:
    A little playing around of my own. A quick test shows that it's called independently of Awake and Start, with the call order such that this code...
    Code (csharp):
    1.  using UnityEngine;
    2. using System.Collections;
    3.  
    4. publicclass MainTest : MonoBehaviour {
    5.  
    6. void Awake(){
    7.  Debug.Log ("Awake");
    8. }
    9.  
    10. void Start(){
    11.  Debug.Log ("Start");
    12. }
    13.  
    14. void Main(){
    15.  Debug.Log ("Main");
    16. }
    17. }
    ... produces this output:
    Code (csharp):
    1.  
    2. Awake
    3. Main
    4. Start
    5.  
    It should be noted that since this was a single test and that I can find no documentation on this whatsoever, the above can't be relied upon.
     
    Last edited: Jun 19, 2014
  3. Frenzy07

    Frenzy07

    Joined:
    Nov 21, 2012
    Posts:
    12
    I had exactly the same result. Main() performed between Awake() and Start()
     
  4. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Results with multiple copies of the component in a scene:
    Code (csharp):
    1.  
    2. Awake
    3. Awake
    4. Awake
    5. Awake
    6. Awake
    7. Main
    8. Start
    9. Main
    10. Start
    11. Main
    12. Start
    13. Main
    14. Start
    15. Main
    16. Start
    17.  
    I'm not assuming anything about which of those Mains goes with which of those Starts. You could test that easily enough by naming your objects if you wanted to find out.
     
  5. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Thats quiet interesting.

    Infact, first Main() of an object gets called, than Start() and OnEnable().
     
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    It's really odd considering the context in which the term "Main" is used in traditional programming. It's typically the entry point to a program that kicks off one or more loops. While I'm aware that some people use Start or Awake in combination with coroutines to get similar behaviour it's always struck me as a hack, not to mention being contrary to Unity's intended design where Update et. al. are where we're meant to make the guts of the magic happen.
     
  7. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Effectivly thats only up to one's code design. I personally think that using Start as a coroutine is perfeectly legit, especially in FSM's.
     
  8. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Well, ScriptableObject can't attach to GameObject.

    We need instantiate it in the code(in MonoBehaviour class attached to GameObject).

    For example;

    Code (CSharp):
    1. public class script : MonoBehaviour{
    2.  
    3. void Start(){
    4.  
    5. Tmain independentScript = new Tmain();
    6.  
    7. }
    8.  
    9. }
    Really, this isn't a Trick, is another corroutine.
     
  9. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    ScriptableObjects need to be instantiated by ScriptableObject.CreateInstance<T>(). Just sayin. ;)
     
    v01pe_ likes this.
  10. mangax

    mangax

    Joined:
    Jul 17, 2013
    Posts:
    336
    interesting.. i was thinking about using scriptable objects in rpg-like game to store buff information.. and expire time, this loop can be handy for time checks.

    (sorry this is unrelated but am curious since am using scriptableobjects and i was surprised that they can have corotines)

    from performance perspective...

    is it good idea, to have scriptableobjects have their own loops to detect when it expires so it removes it self from referenced player?? lets say if there are 100 active.

    or, i should add these objects into a "status" List inside the player, and use for loop to check every object if it is expired, then remove/destroy it from list.

    am currently using the second approach, which is also good if i want to clear player from all it's current debuffs.

    but i feel using lists is annoying, and not as fun as having the buff manages it self out.
     
    Last edited: Jun 19, 2014
  11. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    In terms of a MonoBehaviour this isn't any more or less elegant than using Start as a coroutine.
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Agreed.

    Worse is that it's undocumented and subject to change at any moment at Unity's whim.

    And also may have code that runs in an existing 'Main' method in the inherited class that isn't getting ran now that you've shadowed it with a new 'Main' method.
     
    StarManta likes this.
  13. Frenzy07

    Frenzy07

    Joined:
    Nov 21, 2012
    Posts:
    12
    It is not Coroutine because it not required yield instruction
     
  14. Frenzy07

    Frenzy07

    Joined:
    Nov 21, 2012
    Posts:
    12
    There is another trick - Create MonoBehaviour class and attach it to GameObject. Then change inheritance to ScriptableObject. You'll get ScriptableObject as attached Component.

    I agree with you should avoid use this feature but first of all you need to know about it.
    I spent a lot of time before find this bug in my code. You can imagine this frustration when i see a function worked, but i not call it anywhere. So i hope i saved somebody nerve. :)
     
    IsGreen likes this.
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Wait, why do we need to know about it?

    What bug in your code?

    What frustration?

    How does the existence of 'Main' resolve this bug and frustration?
     
  16. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    I feel like using these undocumented features is a great way to booby trap your code. This is great! ...if you want an unexpected surprise in Unity 5.1.7f, or some other future version.
     
  17. Frenzy07

    Frenzy07

    Joined:
    Nov 21, 2012
    Posts:
    12
    If you assign this name to any of your methods then will get unpredictable flow of your program.
    unpredictable flow == "bug" == frustration, is not it?
     
  18. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    Ah, yeah so don't name functions "Main" or else Unity will try to run them. Good to know!
     
  19. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Better not, since if you dont know about this being called just the same as Start() or OnEnable() you might have a hard time tracking down why the game suddenly blows up. ;)
     
  20. Frenzy07

    Frenzy07

    Joined:
    Nov 21, 2012
    Posts:
    12
    Or maybe it's an Easter Egg from the developers?:)
     
  21. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Not seeing the elegance.
     
  22. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Generally speaking, having anything in a polled loop is a bad idea if you can avoid it. If you do need polled loops, batch them together (which is what it seems like you're suggesting with your last sentence there).

    Smart use of your data is important here. If you make sure that all of your Buffs can be sorted based on their expiry time or something like that then you shouldn't need to loop at all - sort the list when you add to it, check the earliest item in the list, done. (As an aside, I'd still implement that as a loop... I just wouldn't expect it to iterate more than once except to catch when multiple things expire in a single frame delta.)


    Well, there are designs that would allow you to have the best of both worlds. Just riffing off the top of my head, if you had a BuffTracker that kept a sorted list of all existing Buffs, each one could register itself with the tracker upon creation and get a callback upon expiry. The BuffTracker would be no slower than if each entity tracked its own Buffs, but now there's only one of them.

    Bonus points: this could be used for more than just expiring the buffs. If they have internal timed effects it would be almost trivial to have this hook into callbacks for that as well.

    Going further, I'd encourage figuring out a way that the Buffs can work without Update or a similar method. For instance, ApplyEffect could be called upon creation and RemoveEffect could be called upon expiry/destruction.

    Combining all of that together, aside from creating/destroying Buffs and the (very simple) centralised time tracking, this would mean that you could implement a Buff system that is almost free as far as performance is concerned. (Creating and destroying the Buffs can also be negligibly cheap if you pool and reuse them...)
     
  23. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I agree entirely. Aside from having a more old-school name, it doesn't seem to be any nicer than using Start. Actually, it's less elegant than that, because it means you have to manually call Start, and it's still using a "hack" to hook into the update loop (by which I simply mean there's a documented and expected way to get Update functionality, and this circumvents it to re-implement it its own way - adding complexity for what I see as no practical gain. I know there's a performance boost to be achieved by avoiding the invoked update call, but if that matters I'd look at a different approach entirely.).
     
  24. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Theoretically, in the documentation, that's right.

    But in practice, you can instantiate a ScriptableObjec using "new".

    For example:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class scriptableObject : ScriptableObject {
    5.  
    6.     void OnEnable(){ Debug.Log(Time.time); }
    7.  
    8. }
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class NewBehaviourScript : MonoBehaviour {
    5.    
    6.     void Update(){
    7.  
    8.         if(Input.GetKeyDown(KeyCode.Space)) ScriptableObject.CreateInstance<scriptableObject>();
    9.         if(Input.GetKeyDown(KeyCode.Return)){
    10.  
    11.             scriptableObject so = new scriptableObject();
    12.  
    13.         }
    14.  
    15.     }
    16.    
    17. }
    Anyway, I still think that this is make other coroutine as it needs to be instantiated from the code of a class type MonoBehaviour.
     
  25. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    I refer to code of first post, no to ScriptableObject, that in my opinion use a ScriptableObject as corroutine.

    That's right. Thanks for advice.
     
  26. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
  27. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    You need to know about it so that you avoid running into it by accident (quite the opposite of the OP's original assertion that it's "elegant"). It's easy to imagine someone creating a coroutine named "Main" and then getting confused and frustrated when that coroutine gets called at inappropriate times.

    I actually would file this as a bug report.
     
    angrypenguin likes this.
  28. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I think I did, back when the thread first came up. Can't hurt to do it again if it concerns you as well, though.
     
  29. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    The context of that post in question to need to know about as OP frames it. As a feature that can be used. Not as a thing to be avoided.

    Note the 4th line in the statement is about the 'usefulness' of the method. Not about the bug creep of the method.