Clean up audiosystem (#3251)

This commit is contained in:
Jacob Tong
2022-11-03 15:48:33 -07:00
committed by GitHub
parent 2116230f58
commit adcdb9f736
6 changed files with 737 additions and 815 deletions

View File

@@ -1,21 +1,20 @@
using System;
using Robust.Client.Graphics;
namespace Robust.Client.Audio
{
public sealed class AudioStream
{
public TimeSpan Length { get; }
internal ClydeHandle? ClydeHandle { get; }
public string? Name { get; }
public int ChannelCount { get; }
namespace Robust.Client.Audio;
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null)
{
ClydeHandle = handle;
Length = length;
ChannelCount = channelCount;
Name = name;
}
public sealed class AudioStream
{
public TimeSpan Length { get; }
internal ClydeHandle? ClydeHandle { get; }
public string? Name { get; }
public int ChannelCount { get; }
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null)
{
ClydeHandle = handle;
Length = length;
ChannelCount = channelCount;
Name = name;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,125 +7,121 @@ using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.GameObjects
namespace Robust.Server.GameObjects;
[UsedImplicitly]
public sealed class AudioSystem : SharedAudioSystem
{
[UsedImplicitly]
public sealed class AudioSystem : SharedAudioSystem
private uint _streamIndex;
private sealed class AudioSourceServer : IPlayingAudioStream
{
private uint _streamIndex;
private readonly uint _id;
private readonly AudioSystem _audioSystem;
private readonly IEnumerable<ICommonSession>? _sessions;
private sealed class AudioSourceServer : IPlayingAudioStream
internal AudioSourceServer(AudioSystem parent, uint identifier, IEnumerable<ICommonSession>? sessions = null)
{
private readonly uint _id;
private readonly AudioSystem _audioSystem;
private readonly IEnumerable<ICommonSession>? _sessions;
internal AudioSourceServer(AudioSystem parent, uint identifier, IEnumerable<ICommonSession>? sessions = null)
{
_audioSystem = parent;
_id = identifier;
_sessions = sessions;
}
public void Stop()
{
_audioSystem.InternalStop(_id, _sessions);
}
_audioSystem = parent;
_id = identifier;
_sessions = sessions;
}
private void InternalStop(uint id, IEnumerable<ICommonSession>? sessions = null)
public void Stop()
{
var msg = new StopAudioMessageClient
{
Identifier = id
};
if (sessions == null)
RaiseNetworkEvent(msg);
else
{
foreach (var session in sessions)
{
RaiseNetworkEvent(msg, session.ConnectedClient);
}
}
}
private uint CacheIdentifier()
{
return unchecked(_streamIndex++);
}
/// <inheritdoc />
public override IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, AudioParams? audioParams = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
{
FileName = filename,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid,
AudioParams? audioParams = null)
{
if(!EntityManager.TryGetComponent<TransformComponent>(uid, out var transform))
return null;
var id = CacheIdentifier();
var fallbackCoordinates = GetFallbackCoordinates(transform.MapPosition);
var msg = new PlayAudioEntityMessage
{
FileName = filename,
Coordinates = transform.Coordinates,
FallbackCoordinates = fallbackCoordinates,
EntityUid = uid,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
/// <inheritdoc />
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
var id = CacheIdentifier();
var fallbackCoordinates = GetFallbackCoordinates(coordinates.ToMap(EntityManager));
var msg = new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
FallbackCoordinates = fallbackCoordinates,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
/// <inheritdoc />
public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
{
if (sound == null)
return null;
var filter = Filter.Pvs(source, entityManager: EntityManager).RemoveWhereAttachedEntity(e => e == user);
return Play(sound, filter, source, audioParams);
_audioSystem.InternalStop(_id, _sessions);
}
}
private void InternalStop(uint id, IEnumerable<ICommonSession>? sessions = null)
{
var msg = new StopAudioMessageClient
{
Identifier = id
};
if (sessions == null)
RaiseNetworkEvent(msg);
else
{
foreach (var session in sessions)
{
RaiseNetworkEvent(msg, session.ConnectedClient);
}
}
}
private uint CacheIdentifier()
{
return unchecked(_streamIndex++);
}
/// <inheritdoc />
public override IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, AudioParams? audioParams = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
{
FileName = filename,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, AudioParams? audioParams = null)
{
if(!EntityManager.TryGetComponent<TransformComponent>(uid, out var transform))
return null;
var id = CacheIdentifier();
var fallbackCoordinates = GetFallbackCoordinates(transform.MapPosition);
var msg = new PlayAudioEntityMessage
{
FileName = filename,
Coordinates = transform.Coordinates,
FallbackCoordinates = fallbackCoordinates,
EntityUid = uid,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
/// <inheritdoc />
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
var id = CacheIdentifier();
var fallbackCoordinates = GetFallbackCoordinates(coordinates.ToMap(EntityManager));
var msg = new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
FallbackCoordinates = fallbackCoordinates,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
/// <inheritdoc />
public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
{
if (sound == null)
return null;
var filter = Filter.Pvs(source, entityManager: EntityManager).RemoveWhereAttachedEntity(e => e == user);
return Play(sound, filter, source, audioParams);
}
}

View File

@@ -1,7 +1,5 @@
namespace Robust.Shared.Audio
namespace Robust.Shared.Audio;
public interface IPlayingAudioStream
{
public interface IPlayingAudioStream
{
void Stop();
}
void Stop();
}

View File

@@ -5,42 +5,40 @@ using System;
#nullable disable
namespace Robust.Shared.GameObjects
namespace Robust.Shared.GameObjects;
// TODO: This is quite bandwidth intensive.
// Sending bus names and file names as strings is expensive and can be optimized.
// Also there's redundant fields in AudioParams in most cases.
[Serializable, NetSerializable]
public abstract class AudioMessage : EntityEventArgs
{
// TODO: This is quite bandwidth intensive.
// Sending bus names and file names as strings is expensive and can be optimized.
// Also there's redundant fields in AudioParams in most cases.
[Serializable, NetSerializable]
public abstract class AudioMessage : EntityEventArgs
{
public uint Identifier { get; set; }
public string FileName { get; set; }
public AudioParams AudioParams { get; set; }
}
[Serializable, NetSerializable]
public sealed class StopAudioMessageClient : EntityEventArgs
{
public uint Identifier {get; set;}
}
[Serializable, NetSerializable]
public sealed class PlayAudioGlobalMessage : AudioMessage
{
}
[Serializable, NetSerializable]
public sealed class PlayAudioPositionalMessage : AudioMessage
{
public EntityCoordinates Coordinates { get; set; }
public EntityCoordinates FallbackCoordinates { get; set; }
}
[Serializable, NetSerializable]
public sealed class PlayAudioEntityMessage : AudioMessage
{
public EntityUid EntityUid { get; set; }
public EntityCoordinates Coordinates { get; set; }
public EntityCoordinates FallbackCoordinates { get; set; }
}
public uint Identifier { get; set; }
public string FileName { get; set; }
public AudioParams AudioParams { get; set; }
}
[Serializable, NetSerializable]
public sealed class StopAudioMessageClient : EntityEventArgs
{
public uint Identifier {get; set;}
}
[Serializable, NetSerializable]
public sealed class PlayAudioGlobalMessage : AudioMessage
{
}
[Serializable, NetSerializable]
public sealed class PlayAudioPositionalMessage : AudioMessage
{
public EntityCoordinates Coordinates { get; set; }
public EntityCoordinates FallbackCoordinates { get; set; }
}
[Serializable, NetSerializable]
public sealed class PlayAudioEntityMessage : AudioMessage
{
public EntityUid EntityUid { get; set; }
public EntityCoordinates Coordinates { get; set; }
public EntityCoordinates FallbackCoordinates { get; set; }
}

View File

@@ -6,136 +6,128 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System;
namespace Robust.Shared.GameObjects
namespace Robust.Shared.GameObjects;
public abstract class SharedAudioSystem : EntitySystem
{
public abstract class SharedAudioSystem : EntitySystem
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
public const int DefaultSoundRange = 25;
/// <summary>
/// Used in the PAS to designate the physics collision mask of occluders.
/// </summary>
public int OcclusionCollisionMask { get; set; }
public string GetSound(SoundSpecifier specifier)
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
public const int DefaultSoundRange = 25;
/// <summary>
/// Used in the PAS to designate the physics collision mask of occluders.
/// </summary>
public int OcclusionCollisionMask { get; set; }
public string GetSound(SoundSpecifier specifier)
switch (specifier)
{
switch (specifier)
case SoundPathSpecifier path:
return path.Path == null ? string.Empty : path.Path.ToString();
case SoundCollectionSpecifier collection:
{
case SoundPathSpecifier path:
return path.Path == null ? string.Empty : path.Path.ToString();
if (collection.Collection == null)
return string.Empty;
case SoundCollectionSpecifier collection:
if (collection.Collection == null)
return string.Empty;
var soundCollection = _protoMan.Index<SoundCollectionPrototype>(collection.Collection);
return RandMan.Pick(soundCollection.PickFiles).ToString();
var soundCollection = _protoMan.Index<SoundCollectionPrototype>(collection.Collection);
return RandMan.Pick(soundCollection.PickFiles).ToString();
}
return string.Empty;
}
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public abstract IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, AudioParams? audioParams = null);
return string.Empty;
}
public IPlayingAudioStream? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, AudioParams? audioParams = null)
{
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, audioParams ?? sound.Params);
}
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public abstract IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, AudioParams? audioParams = null);
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public abstract IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, AudioParams? audioParams = null);
public IPlayingAudioStream? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, AudioParams? audioParams = null)
{
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, audioParams ?? sound.Params);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters</param>
public IPlayingAudioStream? Play(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : Play(GetSound(sound), playerFilter, uid, audioParams ?? sound.Params);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public abstract IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, AudioParams? audioParams = null);
/// <summary>
/// Play an audio file following an entity for every entity in PVS range.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters</param>
public IPlayingAudioStream? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : Play(sound, Filter.Pvs(uid, entityManager: EntityManager), uid, audioParams);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters</param>
public IPlayingAudioStream? Play(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : Play(GetSound(sound), playerFilter, uid, audioParams ?? sound.Params);
}
/// <summary>
/// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range,
/// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays
/// this sound as normal
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="source">The UID of the entity "emitting" the audio.</param>
/// <param name="user">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters</param>
public abstract IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null);
/// <summary>
/// Play an audio file following an entity for every entity in PVS range.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters</param>
public IPlayingAudioStream? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : Play(sound, Filter.Pvs(uid, entityManager: EntityManager), uid, audioParams);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public abstract IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates,
AudioParams? audioParams = null);
/// <summary>
/// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range,
/// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays
/// this sound as normal
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="source">The UID of the entity "emitting" the audio.</param>
/// <param name="user">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters</param>
public abstract IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null);
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public IPlayingAudioStream? Play(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
return sound == null ? null : Play(GetSound(sound), playerFilter, coordinates, audioParams ?? sound.Params);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public abstract IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, AudioParams? audioParams = null);
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
{
if (_mapManager.TryFindGridAt(mapCoordinates, out var mapGrid))
{
return new EntityCoordinates(mapGrid.GridEntityId,
mapGrid.WorldToLocal(mapCoordinates.Position));
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public IPlayingAudioStream? Play(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return sound == null ? null : Play(GetSound(sound), playerFilter, coordinates, audioParams ?? sound.Params);
}
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
{
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId),
mapCoordinates.Position);
}
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
{
if (_mapManager.TryFindGridAt(mapCoordinates, out var mapGrid))
return new EntityCoordinates(mapGrid.GridEntityId, mapGrid.WorldToLocal(mapCoordinates.Position));
return EntityCoordinates.Invalid;
}
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates.Position);
return EntityCoordinates.Invalid;
}
}