Using my own Start and Update events instead of Unity's messages

Hello everyone!

So, from the info I’ve gathered, Unity uses SendMessage to fire Start() and Update() (and other events) in each MonoBehaviour script. And SendMessage isn’t as performant as a direct function call, or a delegate, because of reflection and stuff.

So, for that reason, and for having easily more control in all my scripts (like calling Update every other frame instead of every frame or something like that), I’ve been experimenting with implementing my own events. This is what I’ve done:

public class TimingClass : MonoBehaviour
{
    public event System.Action OnUpdate;

    void Update()
    {
        if (OnUpdate != null) OnUpdate();
    }
}

This class uses Unity’s Update() event to fire my own event called OnUpdate().

public class BaseClass : MonoBehaviour
{
    TimingClass Timing;

    void Start()
    {
        Timing = GameObject.FindGameObjectWithTag("Timing").GetComponent<TimingClass>();
        Timing.OnUpdate += OnUpdate;
    }

    protected virtual void OnUpdate() { }

    void OnDestroy()
    {
        Timing.OnUpdate -= OnUpdate;
    }
}

Then this is my BaseClass that will be the parent of all my scripts.

public class MyObject : BaseClass
{
    public float Speed = 50.0f;

    protected override void OnUpdate()
    {
        transform.Rotate(Time.deltaTime * Speed, 0, 0);
    }
}

And this is a script that inherits from BaseClass and rotates the GameObject with my own OnUpdate event.

It works ok. In fact, it works really great. I instanced like 500 rotating cubes with this MyObject script and it performs better than with Unity’s Update(). ~1ms with Update(), and ~0.45ms with my own OnUpdate().

However it’s harder to Deep Profile my scripts because of this mess:

As you can see, if you have many objects, it would be hard to find that sneaky script that is always giving problems. Is there a way for the profiler to show the information in a nicer way in this case?

And what do you think of this event system? Could it give any problems or not be recommended for any possible reason?

Thanks for your attention! :slight_smile:

It seems a little excessive unless you have a really specific need. By using tags like that, it prevents you from using a tag for identifying an object, as the would be using that. Same with extending the script. It really locks you into a single structure with little flexibility and a lot of dependency. Also overriding start like that isn’t going to save you anything of note, and doesn’t really benefit you.

If your goal is to have a tick (update) that you have some external control over, you could use an interface instead, That would make it easy to add to any object and track.

1 Like

If you’re going to treat the ‘Timing’ script like a singleton, might as well make it a singleton, and just have a static getter property to retrieve its instance. That way you’re not wasting the call to ‘FindGameObjectWithTag’, and ‘GetComponent’.

As for the profiler dealio… hrmm, I guess that’s how it displays a delegate. Sort of makes sense, since the way mono implements its delegates in a daisy chain (like a linkedlist). I guess you could take zombiegorilla’s advice and implement some ‘IUpdatable’ interface and add it a collection (I’d use HashSet) on the Timing singleton. Then enumerate that set (use the GetEnumerator directly to avoid GC) and call the IUpdatable.Update method you define.

In the end though… I don’t see this being all that much faster. How many ‘update’ calls are you expecting to have?

This is kind of the key really.

If you have a ton of updates, enough that the appear to be impacting performance, rather than trying to trying to improve the update event, it best to optimize and reduce the need, or handle it differently. That will improve performance much more dramatically.

1 Like

I mean yeah - but we’re already “locked into a single structure with little flexibility and a lot of dependency”, we just have no control over it. I’d argue that overriding start could have benefits - one of my greatest issues with SendMessage code is the lack of compiler semantics. Where does Update/Start etc get called? VS has no idea!

@PacoChan have you considered a UnityEvent? That would be both Editor friendly and probably more profiler friendly.

Right, but that is kind of the point. Start is automatic and called at a specific point in time. It’s useful for having things happen the right order. You don’t need to override it, or even use it, just use your own method and call it when you need it to.

UnityEvent uses reflection which I think is what the OP is trying to avoid though.

I’m using my own awake and update method calls to have better control on how and when everything happens, but I just use singleton with list of scripts that I want to update and I call those updates from singleton using for loop. Works very well.
The only problem I can see is that if I want to check how much time takes specific function I need to use deep profiling, in normal profiling everything is seen as one update from singleton. Also I don’t have this ridiculous call stack in profiler.

First up I’d want some proof that calling Update from Unity is actually slow. It wouldn’t surprise me if it’s pretty well optimised inside the engine. I very much doubt it uses SendMessage.

Seems like you are reinventing the wheel just for the fun of it.

1 Like

That’s pretty much how I do it as well. I use a services base model, but the result is the same. I use awake to register it with the service and often times I create an init() method that serves as my start so I can pass arguments if needed. But all that is for a small subset objects that leverage some particular. The rest is just the usual monobehavior messages.

http://forum.unity3d.com/threads/very-interesting-result-of-testing-unitys-update-performance.362058/
If you want proof just do simple test, it took me 5 minutes to create test with 10k calls every frame and compare results.

1 Like

Sure, just ran some tests.

I made it as simple as I possibly could. It’s all contained in one script, and I have a drop down to select the mode to run the test in.

I’m using the profiler to determine the speeds, considering that it’s the only way to get a handle on the ‘update’ call speed when you let unity do it.

I use both a ‘HashSet’ and a ‘List’ to store the values in. Personally I would end up using a HashSet if I actually implemented this because 1) it allows only unique values, 2) testing if something is in it already and/or removing it is O(1). That’s just personal preference.

So the code:

using UnityEngine;
using System.Collections.Generic;

public class CreatorScript : MonoBehaviour {

   
    public enum Command
    {
        Nothing = 0,
        CreateSystemUpdater = 1,
        CreateSelfUpdaterSet = 2,
        CreateSelfUpdaterList = 3
    }

    public Command OnStartCommand;
    public int GameObjectCount;



    private void Start()
    {
        switch(this.OnStartCommand)
        {
            case Command.CreateSystemUpdater:
                {
                    for(int i = 0; i < this.GameObjectCount; i++)
                    {
                        var go = new GameObject();
                        go.AddComponent<SystemUpdater>();
                    }
                }
                break;
            case Command.CreateSelfUpdaterSet:
                {
                    var manager = this.gameObject.AddComponent<SelfUpdaterSetManager>();

                    for (int i = 0; i < this.GameObjectCount; i++)
                    {
                        var go = new GameObject();
                        var c = go.AddComponent<SelfUpdater>();
                        manager.Register(c);
                    }
                }
                break;
            case Command.CreateSelfUpdaterList:
                {
                    var manager = this.gameObject.AddComponent<SelfUpdaterListManager>();

                    for (int i = 0; i < this.GameObjectCount; i++)
                    {
                        var go = new GameObject();
                        var c = go.AddComponent<SelfUpdater>();
                        manager.Register(c);
                    }
                }
                break;
        }
    }










    public class SelfUpdaterSetManager : MonoBehaviour
    {

        private HashSet<SelfUpdater> _set = new HashSet<SelfUpdater>();

        public void Register(SelfUpdater obj)
        {
            _set.Add(obj);
        }

        public void Unregister(SelfUpdater obj)
        {
            _set.Remove(obj);
        }


        private void Update()
        {
            var e = _set.GetEnumerator();
            while(e.MoveNext())
            {
                e.Current.DoUpdate();
            }
        }

    }

    public class SelfUpdaterListManager : MonoBehaviour
    {

        private List<SelfUpdater> _lst = new List<SelfUpdater>();

        public void Register(SelfUpdater obj)
        {
            _lst.Add(obj);
        }

        public void Unregister(SelfUpdater obj)
        {
            _lst.Remove(obj);
        }


        private void Update()
        {
            int cnt = _lst.Count;
            for(int i = 0; i < cnt; i++)
            {
                _lst[i].DoUpdate();
            }
        }

    }

    public class SelfUpdater : MonoBehaviour
    {

        public void DoUpdate()
        {

        }

    }

    public class SystemUpdater : MonoBehaviour
    {

        private void Update()
        {

        }

    }

}

And the results when I do 10,000. I tried getting a screenshot in their average. I include the ranges I saw after watching for some time.

Using Unity Update (values ranged from 0.04 - 0.17 ms):

Using Self Update w/ HashSet (values ranged from 0.8 - 1.25 ms):

Using Self Update w/ List (values ranged from 0.6 - 1.0 ms):

Now, there’s one discrepancy. In the unity system update, the actual 10,000 calls to Update take a mere 0.1 ms (beating the others almost 10 fold). BUT the overall BehaviourUpdate takes a whopping 6.75 ms (this too ranged from 3ms to 7 ms, and not really in parity with the SystemUpdater range).

Thing is, @StarManta points out why that is in your other thread a couple weeks ago.

That time comes from overhead in the debug system. It’s stuff that is mostly stripped out at run time.

And I’m willing to go farther and say that it wouldn’t bring it into parity either. Considering that the actual update calls (not the overall BehaviourUpdate) is nearly 10 fold slower for the collections.

1 Like

Hi again! Thanks to everyone for all the answers.

I’ll consider some of the options that you guys mentioned, using interfaces seems interesting. And I didn’t know about UnityEvent, I’ll take a look at it. Also I didn’t have in mind the overhead of the profiler.

This is just for experimenting and learning, I’m not starting a new project right now. But I thought it would be interesting to see possible other ways of doing Updates. Since I read that if you have an empty Update it is better to remove it, I got interested in how all that worked.

And of course, as someone said, it is better try to optimize the content of the Updates or even trying to not to use them if possible. That is what I always try to do. But for when I need Update, I asked myself if there would be better ways of doing things. I’ve never had so many Updates at the same time, so in a real project I guess there wouldn’t be much difference.

Thanks again for your interesting answers.

Greetings! :slight_smile:

The only time you ever want to replace Unity’s update and start functions is if you have a manager class which has a single update and loops through. ie custom particle system. Lots of enemies.

Otherwise Unity’s update will be orders of magnitude faster every time, and how quick they are will also depend on what variables need loading from the class at the time and what is accessed (ie a manager that loops through a lot of items will always beat it in speed, but that’s just common sense).

And it doesn’t use SendMessage.

Are you really having a thousand Update calls (where it would actually begin to affect framerate) ? In any case it would be nice if this part was optimised. It may well be with IL2CPP.