Questions about performance: Ambient sounds

This is a sub-topic of this topic. Please keep this topic clean and only talk about this one specific issue. Balance will not be changed - it is just an aesthetical (sound-related) change.

This specific topic is about ambient (background) sound. To prevent ambiguity, this topic is not about the following type of sounds:

  • Impact sounds
  • Gunfire sounds

The problem

When turrets rotate they create an ambient sound. When buildings just exist, they have an ambient sound. A lot of things create ambient sounds.

To give you an idea:

  • Almost all extractors and power generators have ambient sounds.
  • All units with turrets have (additional) ambient sounds (when the turret moves, not when it is firing).
  • Almost all props have ambient sounds (when trees are burning, for example).
  • All units create ambient sounds (when moving, or when changing the state of moving).
  • ....

To get a better idea, this is the number of loop audio files defined in blueprints:

  • ActiveLoop: 100+
  • ConstructLoop: 180+
  • CaptureLoop: 40+
  • ReclaimLoop: 40+
  • AmbientMove: 380+
  • AmbientMoveSub: 20+

In order to actually experience these audio files you need to turn off your music and set Fx sound to full. Slowly but surely, you'll start hearing them. My problem with these loops is how they are made.

-- as defined in unit.lua

GetSoundEntity = function(self, type)
    if not self.Sounds then self.Sounds = {} end

    if not self.Sounds[type] then
        local sndEnt
        if self.SoundEntities[1] then
            sndEnt = table.remove(self.SoundEntities, 1)
        else
            sndEnt = Entity()
            Warp(sndEnt, self:GetPosition())
            sndEnt:AttachTo(self, -1)
            self.Trash:Add(sndEnt)
        end
        self.Sounds[type] = sndEnt
    end

    return self.Sounds[type]
end,

PlayUnitAmbientSound = function(self, sound)
    local bp = self.Blueprint
    if not bp.Audio[sound] then return end

    local entity = self:GetSoundEntity('Ambient' .. sound)
    entity:SetAmbientSound(bp.Audio[sound], nil)
end,

StopUnitAmbientSound = function(self, sound)
    local bp = self.Blueprint
    if not bp.Audio[sound] then return end

    local type = 'Ambient' .. sound
    local entity = self:GetSoundEntity(type)
    if entity and not entity:BeenDestroyed() then
        self.Sounds[type] = nil
        entity:SetAmbientSound(nil, nil)
        self.SoundEntities = self.SoundEntities or {}
        table.insert(self.SoundEntities, entity)
    end
end,

Whenever an ambient sound is made a new entity is made. And that entity plays the sound. When the ambient sound is no longer required the sound is removed but the entity is kept. In turn, every unit can easily carry 2 - 3 additional entities because there are various ambient sounds that can be played at once.

To make it clear: the playing of the sounds is not the issue. Sound is not processed by the sim thread. The calls to the engine hurt because they are engine calls - but that isn't the issue either. Remember that the sim runs at 10 ticks a second, therefore we have 100ms for each tick

To visualize the issue, say we have a 1000 mantis in-game that are:

  • All idle and all ambient sound enabled, stabilizes at 10.5 - 11.0 ms.
  • All idle and all ambient sound disabled, stabilizes at 8.5 - 9.0 ms.

This means that just having the entities cached for the sound is taking up about 2 ms of the entire sim. As a game can have thousands of units that may have several ambient sounds at once it can easily eat up 5 - 10 ms late game.

To solidify the issue I kept track how often sound is being stopped with the profiler.

e50743f4-6ab3-4b13-a622-cdf3d0d9f1db-image.png

That is 363K calls to one function in a game with 4 AIx that lasted 26 minutes. It is by far the most common function called.

A possible solution

The later the game gets, the less the ambient sound of an individual unit matters. As an example, if you have an army of 200 mantis then hearing the ambient sound of 20 of them will be sufficient, for two reasons:

  • The sound thread can not 'render' all the 200 sounds anyhow.
  • The sound is typically so low in volume that you don't hear it all (try hear the t1 aeon mass extractor with music on).

Therefore I suggest to have units that do not play ambient sounds at all as the game continues. For example, we could say the following:

  • For each t1 unit, if there are less than 100 in-game then all sounds remain as is.
  • For each t1 unit, if there are more than 100 in-game then every 2nd unit does not make ambient sound.
  • For each t1 unit, if there are more than 300 in-game then every 2nd and 3rd unit does not make ambient sound.
  • For each ...

This way there will always be some units in your army that can make ambient sounds. But, not all of them will be and that benefits the sim in the long run. We could specialize it on specific units or tech levels. For example, I think experimental units and the ACU should always make ambient sounds.

Solutions considered, but not worth it

One solution the original game used was trashing the entity when it was no longer required. This will introduce garbage that we may not notice immediately, but once the garbage collector kicks in you can get a stutter.

Disclaimer: the times are relative to your processor / RAM. It may be slower or faster on your computer, but the relative time difference (in %) will remain the same.

A work of art is never finished, merely abandoned

You could also tie the ambient sound processing to the SIM speed, turning it off when it falls below 0. We do this with a number of subsystems in LOUD (primarily ancillary video effects such as contrails, flying debris particles) but for the same purpose - maintaining SIM performance.

This might be practical, since the ambient audio effects, and the direct audio effects are handled by different functions.

@sprouto said in Questions about performance: Ambient sounds:

You could also tie the ambient sound processing to the SIM speed, turning it off when it falls below 0. We do this with a number of subsystems in LOUD (primarily ancillary video effects such as contrails, flying debris particles) but for the same purpose - maintaining SIM performance.

This might be practical, since the ambient audio effects, and the direct audio effects are handled by different functions.

This seems like the most practical approach and could be carried over to other performance sinks.