Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentation/guides/basic-concepts/Voice/VoiceModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public async Task<string> EchoAsync()
voiceClient.VoiceReceive += args =>
{
// Pass current user voice directly to the output to create echo
if (voiceClient.Cache.Users.TryGetValue(args.Ssrc, out var voiceUserId) && voiceUserId == userId)
if (voiceClient.Cache.SsrcUsers.TryGetValue(args.Ssrc, out var voiceUserId) && voiceUserId == userId)
outStream.Write(args.Frame);
return default;
};
Expand Down
3 changes: 3 additions & 0 deletions NetCord/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: DisableRuntimeMarshalling]
9 changes: 5 additions & 4 deletions NetCord/Gateway/GatewayClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using NetCord.Gateway.Compression;
using NetCord.Gateway.JsonModels;
using NetCord.Gateway.WebSockets;
using NetCord.Logging;

using WebSocketCloseStatus = System.Net.WebSockets.WebSocketCloseStatus;
Expand Down Expand Up @@ -910,7 +911,7 @@ private ValueTask SendIdentifyAsync(ConnectionState connectionState, PresencePro
Intents = _intents,
}).Serialize(Serialization.Default.GatewayPayloadPropertiesGatewayIdentifyProperties);
_latencyTimer.Start();
return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken);
return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalTextPayloadProperties, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -955,17 +956,17 @@ private ValueTask TryResumeAsync(ConnectionState connectionState, string session
{
var serializedPayload = new GatewayPayloadProperties<GatewayResumeProperties>(GatewayOpcode.Resume, new(Token.RawToken, sessionId, sequenceNumber)).Serialize(Serialization.Default.GatewayPayloadPropertiesGatewayResumeProperties);
_latencyTimer.Start();
return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken);
return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalTextPayloadProperties, cancellationToken);
}

private protected override ValueTask HeartbeatAsync(ConnectionState connectionState, CancellationToken cancellationToken = default)
{
var serializedPayload = new GatewayPayloadProperties<int>(GatewayOpcode.Heartbeat, SequenceNumber).Serialize(Serialization.Default.GatewayPayloadPropertiesInt32);
_latencyTimer.Start();
return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken);
return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalTextPayloadProperties, cancellationToken);
}

private protected override ValueTask ProcessPayloadAsync(State state, ConnectionState connectionState, ReadOnlySpan<byte> payload)
private protected override ValueTask ProcessPayloadAsync(State state, ConnectionState connectionState, WebSocketMessageType messageType, ReadOnlySpan<byte> payload)
{
var jsonPayload = JsonSerializer.Deserialize(_compression.Decompress(payload), Serialization.Default.JsonGatewayPayload)!;
return HandlePayloadAsync(state, connectionState, jsonPayload);
Expand Down
14 changes: 14 additions & 0 deletions NetCord/Gateway/Voice/BinaryModels/BinaryVoicePayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Buffers.Binary;

namespace NetCord.Gateway.Voice.BinaryModels;

internal readonly ref struct BinaryVoicePayload(ReadOnlySpan<byte> payload)
{
private readonly ReadOnlySpan<byte> _payload = payload;

public ushort SequencyNumber => BinaryPrimitives.ReadUInt16BigEndian(_payload);

public VoiceOpcode Opcode => (VoiceOpcode)_payload[2];

public ReadOnlySpan<byte> Data => _payload[3..];
}
119 changes: 104 additions & 15 deletions NetCord/Gateway/Voice/ConcurrentVoiceClientCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Collections;
using System.Collections.Concurrent;

using NetCord.Gateway.Voice.JsonModels;

Expand Down Expand Up @@ -39,34 +40,39 @@ public sealed class ConcurrentVoiceClientCache : IVoiceClientCache
{
internal ConcurrentVoiceClientCache()
{
_ssrcs = new();
_users = new();
_users = [];
_userSsrcs = [];
_ssrcUsers = [];
}

internal ConcurrentVoiceClientCache(JsonVoiceClientCache jsonModel)
{
_ssrc = jsonModel.Ssrc;
_ssrcs = new(jsonModel.Ssrcs);
_users = new(jsonModel.Users);
_userSsrcs = new(jsonModel.UserSsrcs);
_ssrcUsers = new(jsonModel.SsrcUsers);
}

public uint Ssrc => _ssrc;
public IReadOnlyDictionary<ulong, uint> Ssrcs => _ssrcs;
public IReadOnlyDictionary<uint, ulong> Users => _users;
public IReadOnlySet<ulong> Users => _users;
public IReadOnlyDictionary<ulong, uint> UserSsrcs => _userSsrcs;
public IReadOnlyDictionary<uint, ulong> SsrcUsers => _ssrcUsers;

#pragma warning disable IDE0032 // Use auto property
private uint _ssrc;
#pragma warning restore IDE0032 // Use auto property
private readonly ConcurrentDictionary<ulong, uint> _ssrcs;
private readonly ConcurrentDictionary<uint, ulong> _users;
private readonly ConcurrentHashSet<ulong> _users;
private readonly ConcurrentDictionary<ulong, uint> _userSsrcs;
private readonly ConcurrentDictionary<uint, ulong> _ssrcUsers;

public JsonVoiceClientCache ToJsonModel()
{
return new()
{
Ssrc = _ssrc,
Ssrcs = _ssrcs.ToDictionary(),
Users = _users.ToDictionary(),
Users = _users.ToArray(),
UserSsrcs = _userSsrcs.ToArray().ToDictionary(),
SsrcUsers = _ssrcUsers.ToArray().ToDictionary(),
};
}

Expand All @@ -77,18 +83,28 @@ public IVoiceClientCache CacheCurrentSsrc(uint ssrc)
return this;
}

public IVoiceClientCache CacheUser(ulong userId, uint ssrc)
public IVoiceClientCache CacheUsers(IReadOnlyList<ulong> userId)
{
_ssrcs[userId] = ssrc;
_users[ssrc] = userId;
int count = userId.Count;
for (int i = 0; i < count; i++)
_users.Add(userId[i]);

return this;
}

public IVoiceClientCache CacheUserSsrc(ulong userId, uint ssrc)
{
_userSsrcs[userId] = ssrc;
_ssrcUsers[ssrc] = userId;

return this;
}

public IVoiceClientCache RemoveUser(ulong userId)
{
if (_ssrcs.TryRemove(userId, out var ssrc))
_users.TryRemove(ssrc, out _);
_users.Remove(userId);
if (_userSsrcs.TryRemove(userId, out var ssrc))
_ssrcUsers.TryRemove(ssrc, out _);

return this;
}
Expand All @@ -103,4 +119,77 @@ public IReadOnlyDictionary<TKey, TValue> CreateDictionary<TSource, TKey, TValue>
public void Dispose()
{
}

private class ConcurrentHashSet<T> : IReadOnlySet<T> where T : notnull
{
private readonly ConcurrentDictionary<T, byte> _storage;

public ConcurrentHashSet()
{
_storage = [];
}

public ConcurrentHashSet(IEnumerable<T> collection)
{
_storage = new(collection.Select(item => new KeyValuePair<T, byte>(item, 0)));
}

public T[] ToArray() => [.. _storage.Keys];

private HashSet<T> HashSet => [.. _storage.Keys];

public int Count => _storage.Count;

public bool Add(T item)
{
return _storage.TryAdd(item, 0);
}

public bool Remove(T item)
{
return _storage.TryRemove(item, out _);
}

public bool Contains(T item)
{
return _storage.ContainsKey(item);
}

public IEnumerator<T> GetEnumerator()
{
return _storage.Select(p => p.Key).GetEnumerator();
}

public bool IsProperSubsetOf(IEnumerable<T> other)
{
return HashSet.IsProperSubsetOf(other);
}

public bool IsProperSupersetOf(IEnumerable<T> other)
{
return HashSet.IsProperSupersetOf(other);
}

public bool IsSubsetOf(IEnumerable<T> other)
{
return HashSet.IsSubsetOf(other);
}

public bool IsSupersetOf(IEnumerable<T> other)
{
return HashSet.IsSupersetOf(other);
}

public bool Overlaps(IEnumerable<T> other)
{
return HashSet.Overlaps(other);
}

public bool SetEquals(IEnumerable<T> other)
{
return HashSet.SetEquals(other);
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace NetCord.Gateway.Voice;

internal class DaveMlsInvalidCommitWelcomeProperties(int transitionId)
{
[JsonPropertyName("transition_id")]
public int TransitionId { get; set; } = transitionId;
}
9 changes: 9 additions & 0 deletions NetCord/Gateway/Voice/DaveTransitionReadyProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace NetCord.Gateway.Voice;

internal class DaveTransitionReadyProperties(ushort transitionId)
{
[JsonPropertyName("transition_id")]
public ushort TransitionId { get; set; } = transitionId;
}
2 changes: 1 addition & 1 deletion NetCord/Gateway/Voice/GatewayClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static async Task<VoiceClient> JoinVoiceChannelAsync(this GatewayClient c
(state, server) = await WaitForEventsAsync(tokenSource.Token).ConfigureAwait(false);
}

return new(userId, state.SessionId, server.Endpoint!, guildId, server.Token, configuration);
return new(userId, state.SessionId, server.Endpoint!, guildId, channelId, server.Token, configuration);

ValueTask HandleVoiceStateUpdateAsync(VoiceState arg)
{
Expand Down
8 changes: 5 additions & 3 deletions NetCord/Gateway/Voice/IVoiceClientCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
public interface IVoiceClientCache : IDictionaryProvider, IDisposable
{
public uint Ssrc { get; }
public IReadOnlyDictionary<ulong, uint> Ssrcs { get; }
public IReadOnlyDictionary<uint, ulong> Users { get; }
public IReadOnlySet<ulong> Users { get; }
public IReadOnlyDictionary<ulong, uint> UserSsrcs { get; }
public IReadOnlyDictionary<uint, ulong> SsrcUsers { get; }

public IVoiceClientCache CacheCurrentSsrc(uint ssrc);
public IVoiceClientCache CacheUser(ulong userId, uint ssrc);
public IVoiceClientCache CacheUsers(IReadOnlyList<ulong> userId);
public IVoiceClientCache CacheUserSsrc(ulong userId, uint ssrc);

public IVoiceClientCache RemoveUser(ulong userId);
}
68 changes: 44 additions & 24 deletions NetCord/Gateway/Voice/ImmutableVoiceClientCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,73 +46,93 @@ internal static ImmutableVoiceClientCache FromJson(JsonVoiceClientCache jsonMode

private ImmutableVoiceClientCache()
{
_ssrcs = ImmutableDictionary<ulong, uint>.Empty;
_users = ImmutableDictionary<uint, ulong>.Empty;
_users = ImmutableHashSet<ulong>.Empty;
_userSsrcs = ImmutableDictionary<ulong, uint>.Empty;
_ssrcUsers = ImmutableDictionary<uint, ulong>.Empty;
}

private ImmutableVoiceClientCache(JsonVoiceClientCache jsonModel)
{
_ssrc = jsonModel.Ssrc;
_ssrcs = jsonModel.Ssrcs.ToImmutableDictionary();
_users = jsonModel.Users.ToImmutableDictionary();
_users = [.. jsonModel.Users];
_userSsrcs = jsonModel.UserSsrcs.ToImmutableDictionary();
_ssrcUsers = jsonModel.SsrcUsers.ToImmutableDictionary();
}

private ImmutableVoiceClientCache(uint ssrc, ImmutableDictionary<ulong, uint> ssrcs, ImmutableDictionary<uint, ulong> users)
private ImmutableVoiceClientCache(uint ssrc, ImmutableHashSet<ulong> users, ImmutableDictionary<ulong, uint> userSsrcs, ImmutableDictionary<uint, ulong> ssrcUsers)
{
_ssrc = ssrc;
_ssrcs = ssrcs;
_users = users;
_userSsrcs = userSsrcs;
_ssrcUsers = ssrcUsers;
}

private static ImmutableVoiceClientCache Create(uint ssrc, ImmutableDictionary<ulong, uint> ssrcs, ImmutableDictionary<uint, ulong> users)
private static ImmutableVoiceClientCache Create(uint ssrc, ImmutableHashSet<ulong> users, ImmutableDictionary<ulong, uint> userSsrcs, ImmutableDictionary<uint, ulong> ssrcUsers)
{
return new(ssrc, ssrcs, users);
return new(ssrc, users, userSsrcs, ssrcUsers);
}

public uint Ssrc => _ssrc;
public IReadOnlyDictionary<ulong, uint> Ssrcs => _ssrcs;
public IReadOnlyDictionary<uint, ulong> Users => _users;
public IReadOnlySet<ulong> Users => _users;
public IReadOnlyDictionary<ulong, uint> UserSsrcs => _userSsrcs;
public IReadOnlyDictionary<uint, ulong> SsrcUsers => _ssrcUsers;

#pragma warning disable IDE0032 // Use auto property
private readonly uint _ssrc;
#pragma warning restore IDE0032 // Use auto property
private readonly ImmutableDictionary<ulong, uint> _ssrcs;
private readonly ImmutableDictionary<uint, ulong> _users;
private readonly ImmutableHashSet<ulong> _users;
private readonly ImmutableDictionary<ulong, uint> _userSsrcs;
private readonly ImmutableDictionary<uint, ulong> _ssrcUsers;

public JsonVoiceClientCache ToJsonModel()
{
return new()
{
Ssrc = _ssrc,
Ssrcs = _ssrcs,
Users = _users,
Users = _users.ToArray(),
UserSsrcs = _userSsrcs,
SsrcUsers = _ssrcUsers,
};
}

public IVoiceClientCache CacheCurrentSsrc(uint ssrc)
{
return Create(ssrc,
_ssrcs,
_users);
_users,
_userSsrcs,
_ssrcUsers);
}

public IVoiceClientCache CacheUser(ulong userId, uint ssrc)
public IVoiceClientCache CacheUsers(IReadOnlyList<ulong> userId)
{
return Create(_ssrc,
_ssrcs.SetItem(userId, ssrc),
_users.SetItem(ssrc, userId));
_users.Union(userId),
_userSsrcs,
_ssrcUsers);
}

public IVoiceClientCache CacheUserSsrc(ulong userId, uint ssrc)
{
return Create(_ssrc,
_users,
_userSsrcs.SetItem(userId, ssrc),
_ssrcUsers.SetItem(ssrc, userId));
}

public IVoiceClientCache RemoveUser(ulong userId)
{
var ssrcs = _ssrcs;
var userSsrcs = _userSsrcs;

if (!ssrcs.TryGetValue(userId, out var ssrc))
return this;
if (!userSsrcs.TryGetValue(userId, out var ssrc))
return Create(_ssrc,
_users.Remove(userId),
userSsrcs,
_ssrcUsers);

return Create(_ssrc,
ssrcs.Remove(userId),
_users.Remove(ssrc));
_users.Remove(userId),
userSsrcs.Remove(userId),
_ssrcUsers.Remove(ssrc));
}

public IReadOnlyDictionary<TKey, TValue> CreateDictionary<TSource, TKey, TValue>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> elementSelector)
Expand Down
Loading