Performance Optimization - How to call one shots that are used very often?

Hi,
we’re using StudioEventEmitter Play() method and the code seems to always run through the PlayInstance path:

if (is3D && !isOneshot && Settings.Instance.StopEventsOutsideMaxDistance)
            {
                RuntimeManager.RegisterActiveEmitter(this);
                RuntimeManager.UpdateActiveEmitter(this, true);
            }
            else
            {
                PlayInstance();
            }

Which then always goes through this bit, because somehow instance.IsValid is false every frame:

if (!instance.isValid())
            {
                eventDescription.createInstance(out instance);

                // Only want to update if we need to set 3D attributes
                if (is3D)
                {
                    var rigidBody = GetComponent<Rigidbody>();
                    var rigidBody2D = GetComponent<Rigidbody2D>();
                    var transform = GetComponent<Transform>();
                    if (rigidBody)
                    {
                        instance.set3DAttributes(RuntimeUtils.To3DAttributes(gameObject, rigidBody));
                        RuntimeManager.AttachInstanceToGameObject(instance, transform, rigidBody);
                    }
                    else
                    {
                        instance.set3DAttributes(RuntimeUtils.To3DAttributes(gameObject, rigidBody2D));
                        RuntimeManager.AttachInstanceToGameObject(instance, transform, rigidBody2D);
                    }
                }
            }

and as you can see that bit has a lot of overhead with 3 GetComponent calls and AttachInstanceToGameObject that also allocates.

Are we doing this wrong? Why does the isValid function always return false? It seems like there should be an option to cache the 3 components since I doubt in most use cases the rigidbody or transform component gets destroyed.

Thanks for any help!

Bump, because when post got approved, it was already at the end of the page.

1 Like

It doesn’t sound like you are doing anything incorrectly but I might need some more context to answer accurately.

The reason instance.isValid() is always returning false for one shots is because any currently playing one shots need to be released and cleared so they can continue playing out before creating a new one shot instance. This results in an invalid handle, which we then reassign to the new one shot instance. An invalid instance might sound like an error has ocurred, but in this situation it’s just a way of making sure we aren’t creating new instances on top of already playing instances.

The overhead of the 3 GetComponent calls are likely to be negligible in most use cases of one shot events, I think you are right that those 3 components are unlikely to change, but I’m not sure the loss in utility if one of them did change would be worth the performance gains of removing periodic calls to GetComponent. I’ll raise it with the Dev team and see what their thoughts on it are.
If you think StudioEventEmitter.Play() is introducing bottlenecks in your game then feel free to send over any examples or snippets of how you are using it and I’ll have a look into it!

I’m trying to optimize our game for Switch / Mobile so any improvement helps.

The worst use case I’ve found is that we have a web of wires (traps) that run electric signals (sometimes in bursts of 3) about every 0.5 - 1 seconds, but you can be in the range of many of them at the same time which at 30 fps means that there is 1-2 new sounds playing every frame. A lot of the sounds are distant and it doesn’t sound like a great use case, so there might be a better solution than just playing a ton of one shots.

But in general, it’s just an unnecessary overhead. I can’t really think of valid reasons why someone would remove / add transform or rigidbody components during play time, but even then a checkbox setting to turn this on/off would be nice.

var rigidBody = GetComponent<Rigidbody>();
                    var rigidBody2D = GetComponent<Rigidbody2D>();
                    var transform = GetComponent<Transform>();
                    if (rigidBody)
                    {
                        instance.set3DAttributes(RuntimeUtils.To3DAttributes(gameObject, rigidBody));
                        RuntimeManager.AttachInstanceToGameObject(instance, transform, rigidBody);
                    }
                    else
                    {
                        instance.set3DAttributes(RuntimeUtils.To3DAttributes(gameObject, rigidBody2D));
                        RuntimeManager.AttachInstanceToGameObject(instance, transform, rigidBody2D);
                    }

Also looking at this snippet, I’m not sure how often people put sounds directly on objects that have rigidbodies. I think that most of the time the best practice is to do these things on child objects in the hierarchy - i.e. if an enemy has 10 sounds (attack, run, jump etc.), you wouldn’t just place 10 emitters on the root object. So it looks like you have to manually set the 3d attributes after calling Play() to get the info from a rigidbody properly, which also makes this snippet run completely unnecessarily as you overwrite the attributes anyway. Might be worth considering GetComponentInParent() but this is additional overhead or just being able to pass the components directly into the Play() function perhaps?

It sounds like a pretty chaotic auditory experience- you could probably get away with a single event emitter playing events with randomised pan and volume settings to mimic the distant sounds and decrease the range at which Play() is called on the rest of the event emitters to the closest couple of wires.

That is a good point- I’ve created a task to look into optional optimisations on the studio event emitter and compare its design against current best practices.

Other than that, feel free to hack the script and move the GetComponent calls into Start(), or maybe look into making your own event emitter class specialised to your use case. The Basic Scripting Example would be a good starting point.

Can you share the performance numbers you are seeing from the getComponent overhead? It would be good to get an idea of the memory vs CPU tradeoff for your use-case.

Thanks for the help!

@jeff_fmod Yeah it doesn’t really make much sense to play that many sounds that often and I’ll definitely optimize it on our end and discuss with our sound guy what the intentions were.

@mathew Here’s the performance on PC. Just one call to Play looks like 1.3 kB of garbage. It’s not the end of the world but it just seems unnecessary. I can send the Switch measurement when I get around to it, if you’d like.

Thanks, if you could send the Switch measurements when you get a chance that would be useful too.