- Add session_start_limit to GetBotGatewayResponse - Add GetBotGatewayAsync to IDiscordClient - Add master/slave semaphores to enable concurrency - Not store semaphore name as static - Clone GatewayLimits when cloning the Configpull/1537/head
| @@ -0,0 +1,18 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public class BotGateway | |||||
| { | |||||
| /// <summary> | |||||
| /// The WSS URL that can be used for connecting to the gateway. | |||||
| /// </summary> | |||||
| public string Url { get; internal set; } | |||||
| /// <summary> | |||||
| /// The recommended number of shards to use when connecting. | |||||
| /// </summary> | |||||
| public int Shards { get; internal set; } | |||||
| /// <summary> | |||||
| /// Information on the current session start limit. | |||||
| /// </summary> | |||||
| public SessionStartLimit SessionStartLimit { get; internal set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public class SessionStartLimit | |||||
| { | |||||
| /// <summary> | |||||
| /// The total number of session starts the current user is allowed. | |||||
| /// </summary> | |||||
| public int Total { get; internal set; } | |||||
| /// <summary> | |||||
| /// The remaining number of session starts the current user is allowed. | |||||
| /// </summary> | |||||
| public int Remaining { get; internal set; } | |||||
| /// <summary> | |||||
| /// The number of milliseconds after which the limit resets. | |||||
| /// </summary> | |||||
| public int ResetAfter { get; internal set; } | |||||
| /// <summary> | |||||
| /// The maximum concurrent identify requests in a time window. | |||||
| /// </summary> | |||||
| public int MaxConcurrency { get; internal set; } | |||||
| } | |||||
| } | |||||
| @@ -274,5 +274,15 @@ namespace Discord | |||||
| /// that represents the number of shards that should be used with this account. | /// that represents the number of shards that should be used with this account. | ||||
| /// </returns> | /// </returns> | ||||
| Task<int> GetRecommendedShardCountAsync(RequestOptions options = null); | Task<int> GetRecommendedShardCountAsync(RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Gets the gateway information related to the bot. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous get operation. The task result contains a <see cref="BotGateway"/> | |||||
| /// that represents the gateway information related to the bot. | |||||
| /// </returns> | |||||
| Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,16 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class SessionStartLimit | |||||
| { | |||||
| [JsonProperty("total")] | |||||
| public int Total { get; set; } | |||||
| [JsonProperty("remaining")] | |||||
| public int Remaining { get; set; } | |||||
| [JsonProperty("reset_after")] | |||||
| public int ResetAfter { get; set; } | |||||
| [JsonProperty("max_concurrency")] | |||||
| public int MaxConcurrency { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| #pragma warning disable CS1591 | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| @@ -9,5 +9,7 @@ namespace Discord.API.Rest | |||||
| public string Url { get; set; } | public string Url { get; set; } | ||||
| [JsonProperty("shards")] | [JsonProperty("shards")] | ||||
| public int Shards { get; set; } | public int Shards { get; set; } | ||||
| [JsonProperty("session_start_limit")] | |||||
| public SessionStartLimit SessionStartLimit { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -152,6 +152,10 @@ namespace Discord.Rest | |||||
| public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null) | public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetRecommendShardCountAsync(this, options); | => ClientHelper.GetRecommendShardCountAsync(this, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null) | |||||
| => ClientHelper.GetBotGatewayAsync(this, options); | |||||
| //IDiscordClient | //IDiscordClient | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ||||
| @@ -176,5 +176,22 @@ namespace Discord.Rest | |||||
| var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false); | var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false); | ||||
| return response.Shards; | return response.Shards; | ||||
| } | } | ||||
| public static async Task<BotGateway> GetBotGatewayAsync(BaseDiscordClient client, RequestOptions options) | |||||
| { | |||||
| var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false); | |||||
| return new BotGateway | |||||
| { | |||||
| Url = response.Url, | |||||
| Shards = response.Shards, | |||||
| SessionStartLimit = new SessionStartLimit | |||||
| { | |||||
| Total = response.SessionStartLimit.Total, | |||||
| Remaining = response.SessionStartLimit.Remaining, | |||||
| ResetAfter = response.SessionStartLimit.ResetAfter, | |||||
| MaxConcurrency = response.SessionStartLimit.MaxConcurrency | |||||
| } | |||||
| }; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -51,7 +51,7 @@ namespace Discord.API | |||||
| internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
| public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||||
| public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RequestQueue requestQueue, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||||
| JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) | JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) | ||||
| { | { | ||||
| _restClientProvider = restClientProvider; | _restClientProvider = restClientProvider; | ||||
| @@ -61,7 +61,7 @@ namespace Discord.API | |||||
| RateLimitPrecision = rateLimitPrecision; | RateLimitPrecision = rateLimitPrecision; | ||||
| UseSystemClock = useSystemClock; | UseSystemClock = useSystemClock; | ||||
| RequestQueue = new RequestQueue(); | |||||
| RequestQueue = requestQueue ?? new RequestQueue(); | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| SetBaseUrl(DiscordConfig.APIUrl); | SetBaseUrl(DiscordConfig.APIUrl); | ||||
| @@ -7,6 +7,11 @@ namespace Discord.Rest | |||||
| /// </summary> | /// </summary> | ||||
| public class GatewayLimits | public class GatewayLimits | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Creates a new <see cref="GatewayLimits"/> with the default values. | |||||
| /// </summary> | |||||
| public static GatewayLimits Default => new GatewayLimits(); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the global limits for the gateway rate limiter. | /// Gets or sets the global limits for the gateway rate limiter. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -15,6 +20,7 @@ namespace Discord.Rest | |||||
| /// and it is per websocket. | /// and it is per websocket. | ||||
| /// </remarks> | /// </remarks> | ||||
| public GatewayLimit Global { get; set; } | public GatewayLimit Global { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the limits of Identify requests. | /// Gets or sets the limits of Identify requests. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -23,6 +29,7 @@ namespace Discord.Rest | |||||
| /// also per account. | /// also per account. | ||||
| /// </remarks> | /// </remarks> | ||||
| public GatewayLimit Identify { get; set; } | public GatewayLimit Identify { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the limits of Presence Update requests. | /// Gets or sets the limits of Presence Update requests. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -31,11 +38,35 @@ namespace Discord.Rest | |||||
| /// and status (online, idle, etc) | /// and status (online, idle, etc) | ||||
| /// </remarks> | /// </remarks> | ||||
| public GatewayLimit PresenceUpdate { get; set; } | public GatewayLimit PresenceUpdate { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the name of the master <see cref="System.Threading.Semaphore"/> | |||||
| /// used by identify. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// It is used to define what slave <see cref="System.Threading.Semaphore"/> | |||||
| /// is free to run for concurrent identify requests. | |||||
| /// </remarks> | |||||
| public string IdentifyMasterSemaphoreName { get; set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the name of the <see cref="System.Threading.Semaphore"/> used by identify. | |||||
| /// Gets or sets the name of the slave <see cref="System.Threading.Semaphore"/> | |||||
| /// used by identify. | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// If the maximum concurrency is higher than one and you are using the sharded client, | |||||
| /// it will be dinamilly renamed to fit the necessary needs. | |||||
| /// </remarks> | |||||
| public string IdentifySemaphoreName { get; set; } | public string IdentifySemaphoreName { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the maximum identify concurrency. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This limit is provided by Discord. | |||||
| /// </remarks> | |||||
| public int IdentifyMaxConcurrency { get; set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Initializes a new <see cref="GatewayLimits"/> with the default values. | /// Initializes a new <see cref="GatewayLimits"/> with the default values. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -44,10 +75,26 @@ namespace Discord.Rest | |||||
| Global = new GatewayLimit(120, 60); | Global = new GatewayLimit(120, 60); | ||||
| Identify = new GatewayLimit(1, 5); | Identify = new GatewayLimit(1, 5); | ||||
| PresenceUpdate = new GatewayLimit(5, 60); | PresenceUpdate = new GatewayLimit(5, 60); | ||||
| IdentifyMasterSemaphoreName = Guid.NewGuid().ToString(); | |||||
| IdentifySemaphoreName = Guid.NewGuid().ToString(); | IdentifySemaphoreName = Guid.NewGuid().ToString(); | ||||
| IdentifyMaxConcurrency = 1; | |||||
| } | } | ||||
| internal static GatewayLimits GetOrCreate(GatewayLimits limits) | |||||
| => limits ?? new GatewayLimits(); | |||||
| internal GatewayLimits(GatewayLimits limits) | |||||
| { | |||||
| Global = new GatewayLimit(limits.Global.Count, limits.Global.Seconds); | |||||
| Identify = new GatewayLimit(limits.Identify.Count, limits.Identify.Seconds); | |||||
| PresenceUpdate = new GatewayLimit(limits.PresenceUpdate.Count, limits.PresenceUpdate.Seconds); | |||||
| IdentifyMasterSemaphoreName = limits.IdentifyMasterSemaphoreName; | |||||
| IdentifySemaphoreName = limits.IdentifySemaphoreName; | |||||
| IdentifyMaxConcurrency = limits.IdentifyMaxConcurrency; | |||||
| } | |||||
| internal static GatewayLimits GetOrCreate(GatewayLimits? limits) | |||||
| => limits ?? Default; | |||||
| public GatewayLimits Clone() | |||||
| => new GatewayLimits(this); | |||||
| } | } | ||||
| } | } | ||||
| @@ -13,7 +13,6 @@ namespace Discord.Net.Queue | |||||
| { | { | ||||
| private static ImmutableDictionary<GatewayBucketType, GatewayBucket> DefsByType; | private static ImmutableDictionary<GatewayBucketType, GatewayBucket> DefsByType; | ||||
| private static ImmutableDictionary<string, GatewayBucket> DefsById; | private static ImmutableDictionary<string, GatewayBucket> DefsById; | ||||
| private static string IdentifySemaphoreName; | |||||
| static GatewayBucket() | static GatewayBucket() | ||||
| { | { | ||||
| @@ -22,7 +21,6 @@ namespace Discord.Net.Queue | |||||
| public static GatewayBucket Get(GatewayBucketType type) => DefsByType[type]; | public static GatewayBucket Get(GatewayBucketType type) => DefsByType[type]; | ||||
| public static GatewayBucket Get(string id) => DefsById[id]; | public static GatewayBucket Get(string id) => DefsById[id]; | ||||
| public static string GetIdentifySemaphoreName() => IdentifySemaphoreName; | |||||
| public static void SetLimits(GatewayLimits limits) | public static void SetLimits(GatewayLimits limits) | ||||
| { | { | ||||
| @@ -50,8 +48,6 @@ namespace Discord.Net.Queue | |||||
| foreach (var bucket in buckets) | foreach (var bucket in buckets) | ||||
| builder2.Add(bucket.Id, bucket); | builder2.Add(bucket.Id, bucket); | ||||
| DefsById = builder2.ToImmutable(); | DefsById = builder2.ToImmutable(); | ||||
| IdentifySemaphoreName = limits.IdentifySemaphoreName; | |||||
| } | } | ||||
| public GatewayBucketType Type { get; } | public GatewayBucketType Type { get; } | ||||
| @@ -23,15 +23,16 @@ namespace Discord.Net.Queue | |||||
| private CancellationTokenSource _requestCancelTokenSource; | private CancellationTokenSource _requestCancelTokenSource; | ||||
| private CancellationToken _requestCancelToken; //Parent token + Clear token | private CancellationToken _requestCancelToken; //Parent token + Clear token | ||||
| private DateTimeOffset _waitUntil; | private DateTimeOffset _waitUntil; | ||||
| private Semaphore _identifySemaphore; | |||||
| private readonly Semaphore _masterIdentifySemaphore; | |||||
| private readonly Semaphore _identifySemaphore; | |||||
| private readonly int _identifySemaphoreMaxConcurrency; | |||||
| private Task _cleanupTask; | private Task _cleanupTask; | ||||
| public RequestQueue() | public RequestQueue() | ||||
| { | { | ||||
| _tokenLock = new SemaphoreSlim(1, 1); | _tokenLock = new SemaphoreSlim(1, 1); | ||||
| int semaphoreCount = GatewayBucket.Get(GatewayBucketType.Identify).WindowCount; | |||||
| _identifySemaphore = new Semaphore(semaphoreCount, semaphoreCount, GatewayBucket.GetIdentifySemaphoreName()); | |||||
| _clearToken = new CancellationTokenSource(); | _clearToken = new CancellationTokenSource(); | ||||
| _cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
| @@ -43,6 +44,14 @@ namespace Discord.Net.Queue | |||||
| _cleanupTask = RunCleanup(); | _cleanupTask = RunCleanup(); | ||||
| } | } | ||||
| public RequestQueue(string masterIdentifySemaphoreName, string slaveIdentifySemaphoreName, int slaveIdentifySemaphoreMaxConcurrency) | |||||
| : this () | |||||
| { | |||||
| _masterIdentifySemaphore = new Semaphore(1, 1, masterIdentifySemaphoreName); | |||||
| _identifySemaphore = new Semaphore(0, GatewayBucket.Get(GatewayBucketType.Identify).WindowCount, slaveIdentifySemaphoreName); | |||||
| _identifySemaphoreMaxConcurrency = slaveIdentifySemaphoreMaxConcurrency; | |||||
| } | |||||
| public async Task SetCancelTokenAsync(CancellationToken cancelToken) | public async Task SetCancelTokenAsync(CancellationToken cancelToken) | ||||
| { | { | ||||
| await _tokenLock.WaitAsync().ConfigureAwait(false); | await _tokenLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -132,8 +141,14 @@ namespace Discord.Net.Queue | |||||
| //Identify is per-account so we won't trigger global until we can actually go for it | //Identify is per-account so we won't trigger global until we can actually go for it | ||||
| if (requestBucket.Type == GatewayBucketType.Identify) | if (requestBucket.Type == GatewayBucketType.Identify) | ||||
| { | { | ||||
| while (!_identifySemaphore.WaitOne(0)) //To not block the thread | |||||
| if (_masterIdentifySemaphore == null || _identifySemaphore == null) | |||||
| throw new InvalidOperationException("Not a RequestQueue with WebSocket data."); | |||||
| bool master; | |||||
| while (!(master = _masterIdentifySemaphore.WaitOne(0)) && !_identifySemaphore.WaitOne(0)) //To not block the thread | |||||
| await Task.Delay(100, request.CancelToken); | await Task.Delay(100, request.CancelToken); | ||||
| if (master && _identifySemaphoreMaxConcurrency > 1) | |||||
| _identifySemaphore.Release(_identifySemaphoreMaxConcurrency - 1); | |||||
| #if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
| Debug.WriteLine($"[{id}] Acquired identify ticket"); | Debug.WriteLine($"[{id}] Acquired identify ticket"); | ||||
| #endif | #endif | ||||
| @@ -149,7 +164,12 @@ namespace Discord.Net.Queue | |||||
| } | } | ||||
| internal void ReleaseIdentifySemaphore(int id) | internal void ReleaseIdentifySemaphore(int id) | ||||
| { | { | ||||
| _identifySemaphore.Release(); | |||||
| if (_masterIdentifySemaphore == null || _identifySemaphore == null) | |||||
| throw new InvalidOperationException("Not a RequestQueue with WebSocket data."); | |||||
| while (_identifySemaphore.WaitOne(0)) //exhaust all tickets before releasing master | |||||
| { } | |||||
| _masterIdentifySemaphore.Release(); | |||||
| #if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
| Debug.WriteLine($"[{id}] Released identify ticket"); | Debug.WriteLine($"[{id}] Released identify ticket"); | ||||
| #endif | #endif | ||||
| @@ -80,7 +80,7 @@ namespace Discord.WebSocket | |||||
| internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | ||||
| : base(config, client) => BaseConfig = config; | : base(config, client) => BaseConfig = config; | ||||
| private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
| => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||||
| => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayLimits, | |||||
| rateLimitPrecision: config.RateLimitPrecision, | rateLimitPrecision: config.RateLimitPrecision, | ||||
| useSystemClock: config.UseSystemClock); | useSystemClock: config.UseSystemClock); | ||||
| @@ -81,29 +81,35 @@ namespace Discord.WebSocket | |||||
| _shardIdsToIndex.Add(_shardIds[i], i); | _shardIdsToIndex.Add(_shardIds[i], i); | ||||
| var newConfig = config.Clone(); | var newConfig = config.Clone(); | ||||
| newConfig.ShardId = _shardIds[i]; | newConfig.ShardId = _shardIds[i]; | ||||
| if (config.GatewayLimits.IdentifyMaxConcurrency != 1) | |||||
| newConfig.GatewayLimits.IdentifySemaphoreName += $"_{i / config.GatewayLimits.IdentifyMaxConcurrency}"; | |||||
| _shards[i] = new DiscordSocketClient(newConfig, _connectionGroupLock, i != 0 ? _shards[0] : null); | _shards[i] = new DiscordSocketClient(newConfig, _connectionGroupLock, i != 0 ? _shards[0] : null); | ||||
| RegisterEvents(_shards[i], i == 0); | RegisterEvents(_shards[i], i == 0); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayLimits, | |||||
| rateLimitPrecision: config.RateLimitPrecision); | rateLimitPrecision: config.RateLimitPrecision); | ||||
| internal override async Task OnLoginAsync(TokenType tokenType, string token) | internal override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
| { | { | ||||
| if (_automaticShards) | if (_automaticShards) | ||||
| { | { | ||||
| var shardCount = await GetRecommendedShardCountAsync().ConfigureAwait(false); | |||||
| _shardIds = Enumerable.Range(0, shardCount).ToArray(); | |||||
| var botGateway = await GetBotGatewayAsync().ConfigureAwait(false); | |||||
| _shardIds = Enumerable.Range(0, botGateway.Shards).ToArray(); | |||||
| _totalShards = _shardIds.Length; | _totalShards = _shardIds.Length; | ||||
| _shards = new DiscordSocketClient[_shardIds.Length]; | _shards = new DiscordSocketClient[_shardIds.Length]; | ||||
| int maxConcurrency = botGateway.SessionStartLimit.MaxConcurrency; | |||||
| _baseConfig.GatewayLimits.IdentifyMaxConcurrency = maxConcurrency; | |||||
| for (int i = 0; i < _shardIds.Length; i++) | for (int i = 0; i < _shardIds.Length; i++) | ||||
| { | { | ||||
| _shardIdsToIndex.Add(_shardIds[i], i); | _shardIdsToIndex.Add(_shardIds[i], i); | ||||
| var newConfig = _baseConfig.Clone(); | var newConfig = _baseConfig.Clone(); | ||||
| newConfig.ShardId = _shardIds[i]; | newConfig.ShardId = _shardIds[i]; | ||||
| newConfig.TotalShards = _totalShards; | newConfig.TotalShards = _totalShards; | ||||
| if (maxConcurrency != 1) | |||||
| newConfig.GatewayLimits.IdentifySemaphoreName += $"_{i / maxConcurrency}"; | |||||
| _shards[i] = new DiscordSocketClient(newConfig, _connectionGroupLock, i != 0 ? _shards[0] : null); | _shards[i] = new DiscordSocketClient(newConfig, _connectionGroupLock, i != 0 ? _shards[0] : null); | ||||
| RegisterEvents(_shards[i], i == 0); | RegisterEvents(_shards[i], i == 0); | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ using Discord.API.Gateway; | |||||
| using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rest; | |||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| @@ -37,11 +38,11 @@ namespace Discord.API | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | |||||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, GatewayLimits limits, | |||||
| string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | ||||
| RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, | RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, | ||||
| bool useSystemClock = true) | bool useSystemClock = true) | ||||
| : base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) | |||||
| : base(restClientProvider, userAgent, new RequestQueue(limits.IdentifyMasterSemaphoreName, limits.IdentifySemaphoreName, limits.IdentifyMaxConcurrency), defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) | |||||
| { | { | ||||
| _gatewayUrl = url; | _gatewayUrl = url; | ||||
| if (url != null) | if (url != null) | ||||
| @@ -182,7 +182,7 @@ namespace Discord.WebSocket | |||||
| _largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
| } | } | ||||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost, | |||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayLimits, config.GatewayHost, | |||||
| rateLimitPrecision: config.RateLimitPrecision); | rateLimitPrecision: config.RateLimitPrecision); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| internal override void Dispose(bool disposing) | internal override void Dispose(bool disposing) | ||||
| @@ -133,7 +133,7 @@ namespace Discord.WebSocket | |||||
| /// This property should only be changed for bots that have special limits provided by Discord. | /// This property should only be changed for bots that have special limits provided by Discord. | ||||
| /// </note> | /// </note> | ||||
| /// </remarks> | /// </remarks> | ||||
| public GatewayLimits GatewayLimits { get; set; } = new GatewayLimits(); | |||||
| public GatewayLimits GatewayLimits { get; set; } = GatewayLimits.Default; | |||||
| /// <summary> | /// <summary> | ||||
| /// Initializes a default configuration. | /// Initializes a default configuration. | ||||
| @@ -144,6 +144,11 @@ namespace Discord.WebSocket | |||||
| UdpSocketProvider = DefaultUdpSocketProvider.Instance; | UdpSocketProvider = DefaultUdpSocketProvider.Instance; | ||||
| } | } | ||||
| internal DiscordSocketConfig Clone() => MemberwiseClone() as DiscordSocketConfig; | |||||
| internal DiscordSocketConfig Clone() | |||||
| { | |||||
| var clone = MemberwiseClone() as DiscordSocketConfig; | |||||
| clone.GatewayLimits = GatewayLimits.Clone(); | |||||
| return clone; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||