From 5f18d3901142bd8936cdd0ac977f8880b178604d Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 24 Jun 2016 02:28:43 -0300 Subject: [PATCH] Reworked event system, fixed presence and voice state updates --- src/Discord.Net/API/DiscordAPIClient.cs | 28 +-- src/Discord.Net/DiscordClient.cs | 17 +- src/Discord.Net/DiscordSocketClient.cs | 183 ++++++++++++------ src/Discord.Net/DiscordSocketConfig.cs | 2 - .../Entities/WebSocket/CachedGuild.cs | 24 +-- src/Discord.Net/Extensions/EventExtensions.cs | 56 ------ src/Discord.Net/Logging/LogManager.cs | 18 +- src/Discord.Net/Utilities/AsyncEvent.cs | 74 +++++++ 8 files changed, 242 insertions(+), 160 deletions(-) delete mode 100644 src/Discord.Net/Extensions/EventExtensions.cs create mode 100644 src/Discord.Net/Utilities/AsyncEvent.cs diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index 78396697a..e71dcccc6 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -1,6 +1,5 @@ using Discord.API.Gateway; using Discord.API.Rest; -using Discord.Extensions; using Discord.Net; using Discord.Net.Converters; using Discord.Net.Queue; @@ -24,10 +23,17 @@ namespace Discord.API { public class DiscordApiClient : IDisposable { - public event Func SentRequest; - public event Func SentGatewayMessage; - public event Func ReceivedGatewayEvent; - public event Func Disconnected; + private object _eventLock = new object(); + + public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } + private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); + public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } + private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); + + public event Func ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } + private readonly AsyncEvent> _receivedGatewayEvent = new AsyncEvent>(); + public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } + private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); private readonly RequestQueue _requestQueue; private readonly JsonSerializer _serializer; @@ -67,19 +73,19 @@ namespace Discord.API using (var reader = new StreamReader(decompressed)) { var msg = JsonConvert.DeserializeObject(reader.ReadToEnd()); - await ReceivedGatewayEvent.RaiseAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } } }; _gatewayClient.TextMessage += async text => { var msg = JsonConvert.DeserializeObject(text); - await ReceivedGatewayEvent.RaiseAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); }; _gatewayClient.Closed += async ex => { await DisconnectAsync().ConfigureAwait(false); - await Disconnected.RaiseAsync(ex).ConfigureAwait(false); + await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); }; } @@ -311,7 +317,7 @@ namespace Discord.API stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - await SentRequest.RaiseAsync(method, endpoint, milliseconds).ConfigureAwait(false); + await _sentRequestEvent.InvokeAsync(method, endpoint, milliseconds).ConfigureAwait(false); return responseStream; } @@ -324,7 +330,7 @@ namespace Discord.API stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - await SentRequest.RaiseAsync(method, endpoint, milliseconds).ConfigureAwait(false); + await _sentRequestEvent.InvokeAsync(method, endpoint, milliseconds).ConfigureAwait(false); return responseStream; } @@ -344,7 +350,7 @@ namespace Discord.API if (payload != null) bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); await _requestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false); - await SentGatewayMessage.RaiseAsync((int)opCode).ConfigureAwait(false); + await _sentGatewayMessageEvent.InvokeAsync((int)opCode).ConfigureAwait(false); } //Auth diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index eafc06810..bc5601bb1 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -15,8 +15,15 @@ namespace Discord { public class DiscordClient : IDiscordClient { - public event Func Log; - public event Func LoggedIn, LoggedOut; + private readonly object _eventLock = new object(); + + public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } + private readonly AsyncEvent> _logEvent = new AsyncEvent>(); + + public event Func LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } + private readonly AsyncEvent> _loggedInEvent = new AsyncEvent>(); + public event Func LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } + private readonly AsyncEvent> _loggedOutEvent = new AsyncEvent>(); internal readonly Logger _discordLogger, _restLogger, _queueLogger; internal readonly SemaphoreSlim _connectionLock; @@ -35,7 +42,7 @@ namespace Discord public DiscordClient(DiscordConfig config) { _log = new LogManager(config.LogLevel); - _log.Message += async msg => await Log.RaiseAsync(msg).ConfigureAwait(false); + _log.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); _discordLogger = _log.CreateLogger("Discord"); _restLogger = _log.CreateLogger("Rest"); _queueLogger = _log.CreateLogger("Queue"); @@ -96,7 +103,7 @@ namespace Discord throw; } - await LoggedIn.RaiseAsync().ConfigureAwait(false); + await _loggedInEvent.InvokeAsync().ConfigureAwait(false); } protected virtual Task OnLoginAsync() => Task.CompletedTask; @@ -123,7 +130,7 @@ namespace Discord LoginState = LoginState.LoggedOut; - await LoggedOut.RaiseAsync().ConfigureAwait(false); + await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); } protected virtual Task OnLogoutAsync() => Task.CompletedTask; diff --git a/src/Discord.Net/DiscordSocketClient.cs b/src/Discord.Net/DiscordSocketClient.cs index ac3a33116..b8f2f1afb 100644 --- a/src/Discord.Net/DiscordSocketClient.cs +++ b/src/Discord.Net/DiscordSocketClient.cs @@ -21,22 +21,74 @@ namespace Discord //TODO: Add resume logic public class DiscordSocketClient : DiscordClient, IDiscordClient { - public event Func Connected, Disconnected, Ready; - //public event Func VoiceConnected, VoiceDisconnected; - public event Func ChannelCreated, ChannelDestroyed; - public event Func ChannelUpdated; - public event Func MessageReceived, MessageDeleted; - public event Func MessageUpdated; - public event Func RoleCreated, RoleDeleted; - public event Func RoleUpdated; - public event Func JoinedGuild, LeftGuild, GuildAvailable, GuildUnavailable, GuildDownloadedMembers; - public event Func GuildUpdated; - public event Func UserJoined, UserLeft, UserBanned, UserUnbanned; - public event Func UserUpdated; - public event Func CurrentUserUpdated; - public event Func UserIsTyping; - public event Func LatencyUpdated; - //TODO: Add PresenceUpdated? VoiceStateUpdated? + private object _eventLock = new object(); + + //General + public event Func Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } } + private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); + public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } + private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + public event Func Ready { add { _readyEvent.Add(value); } remove { _readyEvent.Remove(value); } } + private readonly AsyncEvent> _readyEvent = new AsyncEvent>(); + + public event Func LatencyUpdated { add { _latencyUpdatedEvent.Add(value); } remove { _latencyUpdatedEvent.Remove(value); } } + private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); + + //Channels + public event Func ChannelCreated { add { _channelCreatedEvent.Add(value); } remove { _channelCreatedEvent.Remove(value); } } + private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); + public event Func ChannelDestroyed { add { _channelDestroyedEvent.Add(value); } remove { _channelDestroyedEvent.Remove(value); } } + private readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); + public event Func ChannelUpdated { add { _channelUpdatedEvent.Add(value); } remove { _channelUpdatedEvent.Remove(value); } } + private readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); + + //Messages + public event Func MessageReceived { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } + private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); + public event Func, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } + private readonly AsyncEvent, Task>> _messageDeletedEvent = new AsyncEvent, Task>>(); + public event Func, IMessage, Task> MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } + private readonly AsyncEvent, IMessage, Task>> _messageUpdatedEvent = new AsyncEvent, IMessage, Task>>(); + + //Roles + public event Func RoleCreated { add { _roleCreatedEvent.Add(value); } remove { _roleCreatedEvent.Remove(value); } } + private readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); + public event Func RoleDeleted { add { _roleDeletedEvent.Add(value); } remove { _roleDeletedEvent.Remove(value); } } + private readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); + public event Func RoleUpdated { add { _roleUpdatedEvent.Add(value); } remove { _roleUpdatedEvent.Remove(value); } } + private readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); + + //Guilds + public event Func JoinedGuild { add { _joinedGuildEvent.Add(value); } remove { _joinedGuildEvent.Remove(value); } } + private AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); + public event Func LeftGuild { add { _leftGuildEvent.Add(value); } remove { _leftGuildEvent.Remove(value); } } + private AsyncEvent> _leftGuildEvent = new AsyncEvent>(); + public event Func GuildAvailable { add { _guildAvailableEvent.Add(value); } remove { _guildAvailableEvent.Remove(value); } } + private AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); + public event Func GuildUnavailable { add { _guildUnavailableEvent.Add(value); } remove { _guildUnavailableEvent.Remove(value); } } + private AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); + public event Func GuildDownloadedMembers { add { _guildDownloadedMembersEvent.Add(value); } remove { _guildDownloadedMembersEvent.Remove(value); } } + private AsyncEvent> _guildDownloadedMembersEvent = new AsyncEvent>(); + public event Func GuildUpdated { add { _guildUpdatedEvent.Add(value); } remove { _guildUpdatedEvent.Remove(value); } } + private AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + + //Users + public event Func UserJoined { add { _userJoinedEvent.Add(value); } remove { _userJoinedEvent.Remove(value); } } + private readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); + public event Func UserLeft { add { _userLeftEvent.Add(value); } remove { _userLeftEvent.Remove(value); } } + private readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); + public event Func UserBanned { add { _userBannedEvent.Add(value); } remove { _userBannedEvent.Remove(value); } } + private readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); + public event Func UserUnbanned { add { _userUnbannedEvent.Add(value); } remove { _userUnbannedEvent.Remove(value); } } + private readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); + public event Func, IGuildUser, Task> UserUpdated { add { _userUpdatedEvent.Add(value); } remove { _userUpdatedEvent.Remove(value); } } + private readonly AsyncEvent, IGuildUser, Task>> _userUpdatedEvent = new AsyncEvent, IGuildUser, Task>>(); + public event Func CurrentUserUpdated { add { _selfUpdatedEvent.Add(value); } remove { _selfUpdatedEvent.Remove(value); } } + private readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); + public event Func UserIsTyping { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } + private readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); + + //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; private readonly ConcurrentQueue _largeGuilds; private readonly Logger _gatewayLogger; @@ -46,7 +98,6 @@ namespace Discord private readonly DataStoreProvider _dataStoreProvider; private readonly JsonSerializer _serializer; private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; - private readonly bool _enablePreUpdateEvents; private readonly int _largeThreshold; private readonly int _totalShards; @@ -78,8 +129,7 @@ namespace Discord internal IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); /// Creates a new REST/WebSocket discord client. - public DiscordSocketClient() - : this(new DiscordSocketConfig()) { } + public DiscordSocketClient() : this(new DiscordSocketConfig()) { } /// Creates a new REST/WebSocket discord client. public DiscordSocketClient(DiscordSocketConfig config) : base(config) @@ -93,7 +143,6 @@ namespace Discord _dataStoreProvider = config.DataStoreProvider; MessageCacheSize = config.MessageCacheSize; - _enablePreUpdateEvents = config.EnablePreUpdateEvents; _largeThreshold = config.LargeThreshold; _gatewayLogger = _log.CreateLogger("Gateway"); @@ -173,7 +222,7 @@ namespace Discord _connectTask = new TaskCompletionSource(); _cancelToken = new CancellationTokenSource(); await ApiClient.ConnectAsync().ConfigureAwait(false); - await Connected.RaiseAsync().ConfigureAwait(false); + await _connectedEvent.InvokeAsync().ConfigureAwait(false); await _connectTask.Task.ConfigureAwait(false); @@ -228,7 +277,7 @@ namespace Discord ConnectionState = ConnectionState.Disconnected; await _gatewayLogger.InfoAsync("Disconnected").ConfigureAwait(false); - await Disconnected.RaiseAsync().ConfigureAwait(false); + await _disconnectedEvent.InvokeAsync().ConfigureAwait(false); } private async Task StartReconnectAsync() { @@ -463,12 +512,14 @@ namespace Discord var heartbeatTime = _heartbeatTime; if (heartbeatTime != 0) { - var latency = (int)(Environment.TickCount - _heartbeatTime); + int latency = (int)(Environment.TickCount - _heartbeatTime); _heartbeatTime = 0; await _gatewayLogger.VerboseAsync($"Latency = {latency} ms").ConfigureAwait(false); + + int before = Latency; Latency = latency; - await LatencyUpdated.RaiseAsync(latency).ConfigureAwait(false); + await _latencyUpdatedEvent.InvokeAsync(before, latency).ConfigureAwait(false); } } break; @@ -523,7 +574,7 @@ namespace Discord _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token); - await Ready.RaiseAsync().ConfigureAwait(false); + await _readyEvent.InvokeAsync().ConfigureAwait(false); _connectTask.TrySetResult(true); //Signal the .Connect() call to complete await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); @@ -546,7 +597,7 @@ namespace Discord if (data.Unavailable != false) { guild = AddGuild(data, DataStore); - await JoinedGuild.RaiseAsync(guild).ConfigureAwait(false); + await _joinedGuildEvent.InvokeAsync(guild).ConfigureAwait(false); await _gatewayLogger.InfoAsync($"Joined {data.Name}").ConfigureAwait(false); } else @@ -568,7 +619,7 @@ namespace Discord if (data.Unavailable != true) { await _gatewayLogger.VerboseAsync($"Connected to {data.Name}").ConfigureAwait(false); - await GuildAvailable.RaiseAsync(guild).ConfigureAwait(false); + await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); } } break; @@ -580,9 +631,9 @@ namespace Discord var guild = DataStore.GetGuild(data.Id); if (guild != null) { - var before = _enablePreUpdateEvents ? guild.Clone() : null; + var before = guild.Clone(); guild.Update(data, UpdateSource.WebSocket); - await GuildUpdated.RaiseAsync(before, guild).ConfigureAwait(false); + await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); } else { @@ -604,11 +655,11 @@ namespace Discord foreach (var member in guild.Members) member.User.RemoveRef(this); - await GuildUnavailable.RaiseAsync(guild).ConfigureAwait(false); + await _guildUnavailableEvent.InvokeAsync(guild).ConfigureAwait(false); await _gatewayLogger.VerboseAsync($"Disconnected from {data.Name}").ConfigureAwait(false); if (data.Unavailable != true) { - await LeftGuild.RaiseAsync(guild).ConfigureAwait(false); + await _leftGuildEvent.InvokeAsync(guild).ConfigureAwait(false); await _gatewayLogger.InfoAsync($"Left {data.Name}").ConfigureAwait(false); } else @@ -644,7 +695,7 @@ namespace Discord else channel = AddDMChannel(data, DataStore); if (channel != null) - await ChannelCreated.RaiseAsync(channel).ConfigureAwait(false); + await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); } break; case "CHANNEL_UPDATE": @@ -655,9 +706,9 @@ namespace Discord var channel = DataStore.GetChannel(data.Id); if (channel != null) { - var before = _enablePreUpdateEvents ? channel.Clone() : null; + var before = channel.Clone(); channel.Update(data, UpdateSource.WebSocket); - await ChannelUpdated.RaiseAsync(before, channel).ConfigureAwait(false); + await _channelUpdatedEvent.InvokeAsync(before, channel).ConfigureAwait(false); } else { @@ -686,7 +737,7 @@ namespace Discord else channel = RemoveDMChannel(data.Recipient.Value.Id); if (channel != null) - await ChannelDestroyed.RaiseAsync(channel).ConfigureAwait(false); + await _channelDestroyedEvent.InvokeAsync(channel).ConfigureAwait(false); else { await _gatewayLogger.WarningAsync("CHANNEL_DELETE referenced an unknown channel.").ConfigureAwait(false); @@ -705,7 +756,7 @@ namespace Discord if (guild != null) { var user = guild.AddUser(data, DataStore); - await UserJoined.RaiseAsync(user).ConfigureAwait(false); + await _userJoinedEvent.InvokeAsync(user).ConfigureAwait(false); } else { @@ -725,9 +776,9 @@ namespace Discord var user = guild.GetUser(data.User.Id); if (user != null) { - var before = _enablePreUpdateEvents ? user.Clone() : null; + var before = user.Clone(); user.Update(data, UpdateSource.WebSocket); - await UserUpdated.RaiseAsync(before, user).ConfigureAwait(false); + await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); } else { @@ -754,7 +805,7 @@ namespace Discord if (user != null) { user.User.RemoveRef(this); - await UserLeft.RaiseAsync(user).ConfigureAwait(false); + await _userLeftEvent.InvokeAsync(user).ConfigureAwait(false); } else { @@ -783,7 +834,7 @@ namespace Discord if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there { guild.CompleteDownloadMembers(); - await GuildDownloadedMembers.RaiseAsync(guild).ConfigureAwait(false); + await _guildDownloadedMembersEvent.InvokeAsync(guild).ConfigureAwait(false); } } else @@ -804,7 +855,7 @@ namespace Discord if (guild != null) { var role = guild.AddRole(data.Role); - await RoleCreated.RaiseAsync(role).ConfigureAwait(false); + await _roleCreatedEvent.InvokeAsync(role).ConfigureAwait(false); } else { @@ -824,9 +875,9 @@ namespace Discord var role = guild.GetRole(data.Role.Id); if (role != null) { - var before = _enablePreUpdateEvents ? role.Clone() : null; + var before = role.Clone(); role.Update(data.Role, UpdateSource.WebSocket); - await RoleUpdated.RaiseAsync(before, role).ConfigureAwait(false); + await _roleUpdatedEvent.InvokeAsync(before, role).ConfigureAwait(false); } else { @@ -851,7 +902,7 @@ namespace Discord { var role = guild.RemoveRole(data.RoleId); if (role != null) - await RoleDeleted.RaiseAsync(role).ConfigureAwait(false); + await _roleDeletedEvent.InvokeAsync(role).ConfigureAwait(false); else { await _gatewayLogger.WarningAsync("GUILD_ROLE_DELETE referenced an unknown role.").ConfigureAwait(false); @@ -874,7 +925,7 @@ namespace Discord var data = (payload as JToken).ToObject(_serializer); var guild = DataStore.GetGuild(data.GuildId); if (guild != null) - await UserBanned.RaiseAsync(new User(data.User)).ConfigureAwait(false); + await _userBannedEvent.InvokeAsync(new User(data.User), guild).ConfigureAwait(false); else { await _gatewayLogger.WarningAsync("GUILD_BAN_ADD referenced an unknown guild.").ConfigureAwait(false); @@ -889,7 +940,7 @@ namespace Discord var data = (payload as JToken).ToObject(_serializer); var guild = DataStore.GetGuild(data.GuildId); if (guild != null) - await UserUnbanned.RaiseAsync(new User(data.User)).ConfigureAwait(false); + await _userUnbannedEvent.InvokeAsync(new User(data.User), guild).ConfigureAwait(false); else { await _gatewayLogger.WarningAsync("GUILD_BAN_REMOVE referenced an unknown guild.").ConfigureAwait(false); @@ -912,7 +963,7 @@ namespace Discord if (author != null) { var msg = channel.AddMessage(author, data); - await MessageReceived.RaiseAsync(msg).ConfigureAwait(false); + await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); } else { @@ -939,7 +990,7 @@ namespace Discord CachedMessage cachedMsg = channel.GetMessage(data.Id); if (cachedMsg != null) { - before = _enablePreUpdateEvents ? cachedMsg.Clone() : null; + before = cachedMsg.Clone(); cachedMsg.Update(data, UpdateSource.WebSocket); after = cachedMsg; } @@ -951,7 +1002,12 @@ namespace Discord after = new Message(channel, author, data); } if (after != null) - await MessageUpdated.RaiseAsync(before, after).ConfigureAwait(false); + await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); + else + { + await _gatewayLogger.WarningAsync("MESSAGE_UPDATE was unable to build an updated message.").ConfigureAwait(false); + return; + } } else { @@ -969,7 +1025,7 @@ namespace Discord if (channel != null) { var msg = channel.RemoveMessage(data.Id); - await MessageDeleted.RaiseAsync(msg).ConfigureAwait(false); + await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create(msg)).ConfigureAwait(false); } else { @@ -989,7 +1045,7 @@ namespace Discord foreach (var id in data.Ids) { var msg = channel.RemoveMessage(id); - await MessageDeleted.RaiseAsync(msg).ConfigureAwait(false); + await _messageDeletedEvent.InvokeAsync(msg.Id, Optional.Create(msg)).ConfigureAwait(false); } } else @@ -1014,7 +1070,20 @@ namespace Discord await _gatewayLogger.WarningAsync("PRESENCE_UPDATE referenced an unknown guild.").ConfigureAwait(false); break; } - guild.UpdatePresence(data, DataStore); + + var user = guild.GetUser(data.User.Id); + if (user != null) + { + var before = user.Clone(); + user.Update(data, UpdateSource.WebSocket); + await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); + } + else + { + user = guild.AddOrUpdateUser(data, DataStore); + user.Update(data, UpdateSource.WebSocket); + await _userUpdatedEvent.InvokeAsync(Optional.Create(), user).ConfigureAwait(false); + } } else { @@ -1034,7 +1103,7 @@ namespace Discord { var user = channel.GetUser(data.UserId, true); if (user != null) - await UserIsTyping.RaiseAsync(channel, user).ConfigureAwait(false); + await _userIsTypingEvent.InvokeAsync(user, channel).ConfigureAwait(false); } } break; @@ -1057,7 +1126,11 @@ namespace Discord var user = guild.GetUser(data.UserId); if (user != null) + { + var before = user.Clone(); user.Update(data, UpdateSource.WebSocket); + await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); + } } else { @@ -1076,9 +1149,9 @@ namespace Discord var data = (payload as JToken).ToObject(_serializer); if (data.Id == CurrentUser.Id) { - var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null; + var before = CurrentUser.Clone(); CurrentUser.Update(data, UpdateSource.WebSocket); - await CurrentUserUpdated.RaiseAsync(before, CurrentUser).ConfigureAwait(false); + await _selfUpdatedEvent.InvokeAsync(before, CurrentUser).ConfigureAwait(false); } else { diff --git a/src/Discord.Net/DiscordSocketConfig.cs b/src/Discord.Net/DiscordSocketConfig.cs index 04c3a0828..c1ddaa90d 100644 --- a/src/Discord.Net/DiscordSocketConfig.cs +++ b/src/Discord.Net/DiscordSocketConfig.cs @@ -24,8 +24,6 @@ namespace Discord /// This makes operations such as User.GetPermissions(Channel), User.GuildPermissions, Channel.GetUser, and Channel.Members much faster at the expense of increased memory usage. /// public bool UsePermissionsCache { get; set; } = false;*/ - /// Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. - public bool EnablePreUpdateEvents { get; set; } = true; /// /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. /// Decreasing this may reduce CPU usage while increasing login time and network usage. diff --git a/src/Discord.Net/Entities/WebSocket/CachedGuild.cs b/src/Discord.Net/Entities/WebSocket/CachedGuild.cs index 5bd9ae06b..a86557f02 100644 --- a/src/Discord.Net/Entities/WebSocket/CachedGuild.cs +++ b/src/Discord.Net/Entities/WebSocket/CachedGuild.cs @@ -90,11 +90,7 @@ namespace Discord _downloaderPromise.SetResult(true); for (int i = 0; i < model.Presences.Length; i++) - { - var presence = model.Presences[i]; - UpdatePresence(presence, dataStore, members); - //AddUser(presence, dataStore, members); - } + AddOrUpdateUser(model.Presences[i], dataStore, members); } _members = members; @@ -163,7 +159,7 @@ namespace Discord } return member; } - public CachedGuildUser AddUser(PresenceModel model, DataStore dataStore, ConcurrentDictionary members = null) + public CachedGuildUser AddOrUpdateUser(PresenceModel model, DataStore dataStore, ConcurrentDictionary members = null) { members = members ?? _members; @@ -194,22 +190,6 @@ namespace Discord return member; return null; } - public void UpdatePresence(PresenceModel model, DataStore dataStore, ConcurrentDictionary members = null) - { - members = members ?? _members; - - CachedGuildUser member; - if (members.TryGetValue(model.User.Id, out member)) - member.Update(model, UpdateSource.WebSocket); - else - { - var user = Discord.GetOrAddUser(model.User, dataStore); - member = new CachedGuildUser(this, user, model); - members[user.Id] = member; - user.AddRef(); - DownloadedMemberCount++; - } - } public async Task DownloadMembersAsync() { if (!HasAllMembers) diff --git a/src/Discord.Net/Extensions/EventExtensions.cs b/src/Discord.Net/Extensions/EventExtensions.cs deleted file mode 100644 index 4467af55c..000000000 --- a/src/Discord.Net/Extensions/EventExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Discord.Extensions -{ - internal static class EventExtensions - { - //TODO: Optimize these for if there is only 1 subscriber (can we do this?) - //TODO: Could we maintain our own list instead of generating one on every invocation? - public static async Task RaiseAsync(this Func eventHandler) - { - var subscriptions = eventHandler?.GetInvocationList(); - if (subscriptions != null) - { - for (int i = 0; i < subscriptions.Length; i++) - await (subscriptions[i] as Func).Invoke().ConfigureAwait(false); - } - } - public static async Task RaiseAsync(this Func eventHandler, T arg) - { - var subscriptions = eventHandler?.GetInvocationList(); - if (subscriptions != null) - { - for (int i = 0; i < subscriptions.Length; i++) - await (subscriptions[i] as Func).Invoke(arg).ConfigureAwait(false); - } - } - public static async Task RaiseAsync(this Func eventHandler, T1 arg1, T2 arg2) - { - var subscriptions = eventHandler?.GetInvocationList(); - if (subscriptions != null) - { - for (int i = 0; i < subscriptions.Length; i++) - await (subscriptions[i] as Func).Invoke(arg1, arg2).ConfigureAwait(false); - } - } - public static async Task RaiseAsync(this Func eventHandler, T1 arg1, T2 arg2, T3 arg3) - { - var subscriptions = eventHandler?.GetInvocationList(); - if (subscriptions != null) - { - for (int i = 0; i < subscriptions.Length; i++) - await (subscriptions[i] as Func).Invoke(arg1, arg2, arg3).ConfigureAwait(false); - } - } - public static async Task RaiseAsync(this Func eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - var subscriptions = eventHandler?.GetInvocationList(); - if (subscriptions != null) - { - for (int i = 0; i < subscriptions.Length; i++) - await (subscriptions[i] as Func).Invoke(arg1, arg2, arg3, arg4).ConfigureAwait(false); - } - } - } -} diff --git a/src/Discord.Net/Logging/LogManager.cs b/src/Discord.Net/Logging/LogManager.cs index 2d1ef53c7..d428ae59f 100644 --- a/src/Discord.Net/Logging/LogManager.cs +++ b/src/Discord.Net/Logging/LogManager.cs @@ -1,5 +1,4 @@ -using Discord.Extensions; -using System; +using System; using System.Threading.Tasks; namespace Discord.Logging @@ -8,7 +7,8 @@ namespace Discord.Logging { public LogSeverity Level { get; } - public event Func Message; + public event Func Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } + private readonly AsyncEvent> _messageEvent = new AsyncEvent>(); public LogManager(LogSeverity minSeverity) { @@ -18,32 +18,32 @@ namespace Discord.Logging public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) { if (severity <= Level) - await Message.RaiseAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); } public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) { if (severity <= Level) - await Message.RaiseAsync(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false); } public async Task LogAsync(LogSeverity severity, string source, Exception ex) { if (severity <= Level) - await Message.RaiseAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); } async Task ILogger.LogAsync(LogSeverity severity, string message, Exception ex) { if (severity <= Level) - await Message.RaiseAsync(new LogMessage(severity, "Discord", message, ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, "Discord", message, ex)).ConfigureAwait(false); } async Task ILogger.LogAsync(LogSeverity severity, FormattableString message, Exception ex) { if (severity <= Level) - await Message.RaiseAsync(new LogMessage(severity, "Discord", message.ToString(), ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, "Discord", message.ToString(), ex)).ConfigureAwait(false); } async Task ILogger.LogAsync(LogSeverity severity, Exception ex) { if (severity <= Level) - await Message.RaiseAsync(new LogMessage(severity, "Discord", null, ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, "Discord", null, ex)).ConfigureAwait(false); } public Task ErrorAsync(string source, string message, Exception ex = null) diff --git a/src/Discord.Net/Utilities/AsyncEvent.cs b/src/Discord.Net/Utilities/AsyncEvent.cs new file mode 100644 index 000000000..0a4d55ed7 --- /dev/null +++ b/src/Discord.Net/Utilities/AsyncEvent.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Discord +{ + internal class AsyncEvent + { + private readonly object _subLock = new object(); + internal ImmutableArray _subscriptions; + + public IReadOnlyList Subscriptions => _subscriptions; + + public AsyncEvent() + { + _subscriptions = ImmutableArray.Create(); + } + + public void Add(T subscriber) + { + lock (_subLock) + _subscriptions = _subscriptions.Add(subscriber); + } + public void Remove(T subscriber) + { + lock (_subLock) + _subscriptions = _subscriptions.Remove(subscriber); + } + } + + internal static class EventExtensions + { + public static async Task InvokeAsync(this AsyncEvent> eventHandler) + { + var subscribers = eventHandler.Subscriptions; + if (subscribers.Count > 0) + { + for (int i = 0; i < subscribers.Count; i++) + await subscribers[i].Invoke().ConfigureAwait(false); + } + } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T arg) + { + var subscribers = eventHandler.Subscriptions; + for (int i = 0; i < subscribers.Count; i++) + await subscribers[i].Invoke(arg).ConfigureAwait(false); + } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2) + { + var subscribers = eventHandler.Subscriptions; + for (int i = 0; i < subscribers.Count; i++) + await subscribers[i].Invoke(arg1, arg2).ConfigureAwait(false); + } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3) + { + var subscribers = eventHandler.Subscriptions; + for (int i = 0; i < subscribers.Count; i++) + await subscribers[i].Invoke(arg1, arg2, arg3).ConfigureAwait(false); + } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + var subscribers = eventHandler.Subscriptions; + for (int i = 0; i < subscribers.Count; i++) + await subscribers[i].Invoke(arg1, arg2, arg3, arg4).ConfigureAwait(false); + } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + var subscribers = eventHandler.Subscriptions; + for (int i = 0; i < subscribers.Count; i++) + await subscribers[i].Invoke(arg1, arg2, arg3, arg4, arg5).ConfigureAwait(false); + } + } +}