using Barotrauma.Extensions; using OpenAL; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Barotrauma.Sounds { sealed class SoundBuffers : IDisposable { private static readonly HashSet bufferPool = new HashSet(); #if OSX public const int MaxBuffers = 400; //TODO: check that this value works for macOS #else public const int MaxBuffers = 32000; #endif public static int BuffersGenerated { get; private set; } = 0; private readonly Sound sound; public uint AlBuffer { get; private set; } = 0; public uint AlMuffledBuffer { get; private set; } = 0; public SoundBuffers(Sound sound) { this.sound = sound; } public void Dispose() { if (AlBuffer != 0) { lock (bufferPool) { bufferPool.Add(AlBuffer); } } if (AlMuffledBuffer != 0) { lock (bufferPool) { bufferPool.Add(AlMuffledBuffer); } } AlBuffer = 0; AlMuffledBuffer = 0; } public static void ClearPool() { lock (bufferPool) { bufferPool.ForEach(b => Al.DeleteBuffer(b)); bufferPool.Clear(); } BuffersGenerated = 0; } public bool RequestAlBuffers() { if (AlBuffer != 0) { return false; } int alError; lock (bufferPool) { while (bufferPool.Count < 2 && BuffersGenerated < MaxBuffers) { Al.GenBuffer(out uint newBuffer); alError = Al.GetError(); if (alError != Al.NoError) { DebugConsole.AddWarning($"Error when generating sound buffer: {Al.GetErrorString(alError)}. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); BuffersGenerated = MaxBuffers; } else if (!Al.IsBuffer(newBuffer)) { DebugConsole.AddWarning($"Error when generating sound buffer: result is not a valid buffer. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); BuffersGenerated = MaxBuffers; } else { bufferPool.Add(newBuffer); BuffersGenerated++; if (BuffersGenerated >= MaxBuffers) { DebugConsole.AddWarning($"{BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); } } } if (bufferPool.Count >= 2) { AlBuffer = bufferPool.First(); bufferPool.Remove(AlBuffer); AlMuffledBuffer = bufferPool.First(); bufferPool.Remove(AlMuffledBuffer); return true; } } //can't generate any more OpenAL buffers! we'll have to steal a buffer from someone... foreach (var otherSound in sound.Owner.LoadedSounds) { if (otherSound == sound) { continue; } if (otherSound.IsPlaying()) { continue; } if (otherSound.Buffers == null) { continue; } if (otherSound.Buffers.AlBuffer == 0) { continue; } // Dispose all channels that are holding // a reference to these buffers, otherwise // an INVALID_OPERATION error will be thrown // when attempting to set the buffer data later. // Having the sources not play is not enough, // as OpenAL assumes that you may want to call // alSourcePlay without reassigning the buffer. otherSound.Owner.KillChannels(otherSound); AlBuffer = otherSound.Buffers.AlBuffer; AlMuffledBuffer = otherSound.Buffers.AlMuffledBuffer; otherSound.Buffers.AlBuffer = 0; otherSound.Buffers.AlMuffledBuffer = 0; // For performance reasons, sift the current sound to // the end of the loadedSounds list, that way it'll // be less likely to have its buffers stolen, which // means less reuploads for frequently played sounds. sound.Owner.MoveSoundToPosition(sound, sound.Owner.LoadedSoundCount-1); if (!Al.IsBuffer(AlBuffer)) { throw new Exception(sound.Filename + " has an invalid buffer!"); } if (!Al.IsBuffer(AlMuffledBuffer)) { throw new Exception(sound.Filename + " has an invalid muffled buffer!"); } return true; } return false; } } }