Several memory and performance improvements
This commit is contained in:
@@ -12,7 +12,7 @@ namespace Discord.API
|
||||
[JsonProperty("roles")]
|
||||
public ulong[] Roles { get; set; }
|
||||
[JsonProperty("joined_at")]
|
||||
public DateTime JoinedAt { get; set; }
|
||||
public DateTimeOffset JoinedAt { get; set; }
|
||||
[JsonProperty("deaf")]
|
||||
public bool Deaf { get; set; }
|
||||
[JsonProperty("mute")]
|
||||
|
||||
@@ -26,6 +26,6 @@ namespace Discord.API
|
||||
[JsonProperty("account")]
|
||||
public IntegrationAccount Account { get; set; }
|
||||
[JsonProperty("synced_at")]
|
||||
public DateTime SyncedAt { get; set; }
|
||||
public DateTimeOffset SyncedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Discord.API
|
||||
[JsonProperty("temporary")]
|
||||
public bool Temporary { get; set; }
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
[JsonProperty("revoked")]
|
||||
public bool Revoked { get; set; }
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ namespace Discord.API
|
||||
[JsonProperty("content")]
|
||||
public Optional<string> Content { get; set; }
|
||||
[JsonProperty("timestamp")]
|
||||
public Optional<DateTime> Timestamp { get; set; }
|
||||
public Optional<DateTimeOffset> Timestamp { get; set; }
|
||||
[JsonProperty("edited_timestamp")]
|
||||
public Optional<DateTime?> EditedTimestamp { get; set; }
|
||||
public Optional<DateTimeOffset?> EditedTimestamp { get; set; }
|
||||
[JsonProperty("tts")]
|
||||
public Optional<bool> IsTextToSpeech { get; set; }
|
||||
[JsonProperty("mention_everyone")]
|
||||
|
||||
@@ -19,6 +19,6 @@ namespace Discord.API.Gateway
|
||||
[JsonProperty("channels")]
|
||||
public Channel[] Channels { get; set; }
|
||||
[JsonProperty("joined_at")]
|
||||
public DateTime JoinedAt { get; set; }
|
||||
public DateTimeOffset JoinedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -38,6 +37,7 @@ namespace Discord
|
||||
public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated;
|
||||
public event Func<IChannel, IUser, Task> UserIsTyping;
|
||||
public event Func<int, Task> LatencyUpdated;
|
||||
//TODO: Add PresenceUpdated? VoiceStateUpdated?
|
||||
|
||||
private readonly ConcurrentQueue<ulong> _largeGuilds;
|
||||
private readonly Logger _gatewayLogger;
|
||||
@@ -50,6 +50,7 @@ namespace Discord
|
||||
private readonly bool _enablePreUpdateEvents;
|
||||
private readonly int _largeThreshold;
|
||||
private readonly int _totalShards;
|
||||
private ConcurrentHashSet<ulong> _dmChannels;
|
||||
private string _sessionId;
|
||||
private int _lastSeq;
|
||||
private ImmutableDictionary<string, VoiceRegion> _voiceRegions;
|
||||
@@ -71,20 +72,14 @@ namespace Discord
|
||||
internal DataStore DataStore { get; private set; }
|
||||
|
||||
internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser;
|
||||
internal IReadOnlyCollection<CachedGuild> Guilds
|
||||
{
|
||||
get
|
||||
{
|
||||
var guilds = DataStore.Guilds;
|
||||
return guilds.ToReadOnlyCollection(guilds);
|
||||
}
|
||||
}
|
||||
internal IReadOnlyCollection<CachedGuild> Guilds => DataStore.Guilds;
|
||||
internal IReadOnlyCollection<CachedDMChannel> DMChannels
|
||||
{
|
||||
get
|
||||
{
|
||||
var users = DataStore.Users;
|
||||
return users.Select(x => x.DMChannel).Where(x => x != null).ToReadOnlyCollection(users);
|
||||
var dmChannels = _dmChannels;
|
||||
var store = DataStore;
|
||||
return dmChannels.Select(x => store.GetChannel(x) as CachedDMChannel).Where(x => x != null).ToReadOnlyCollection(dmChannels);
|
||||
}
|
||||
}
|
||||
internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();
|
||||
@@ -136,6 +131,7 @@ namespace Discord
|
||||
|
||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
||||
_largeGuilds = new ConcurrentQueue<ulong>();
|
||||
_dmChannels = new ConcurrentHashSet<ulong>();
|
||||
}
|
||||
|
||||
protected override async Task OnLoginAsync()
|
||||
@@ -305,11 +301,16 @@ namespace Discord
|
||||
{
|
||||
return Task.FromResult<IChannel>(DataStore.GetChannel(id));
|
||||
}
|
||||
internal CachedDMChannel AddDMChannel(API.Channel model, DataStore dataStore)
|
||||
public override Task<IReadOnlyCollection<IDMChannel>> GetDMChannelsAsync()
|
||||
{
|
||||
return Task.FromResult<IReadOnlyCollection<IDMChannel>>(DMChannels);
|
||||
}
|
||||
internal CachedDMChannel AddDMChannel(API.Channel model, DataStore dataStore, ConcurrentHashSet<ulong> dmChannels)
|
||||
{
|
||||
var recipient = GetOrAddUser(model.Recipient.Value, dataStore);
|
||||
var channel = recipient.AddDMChannel(model);
|
||||
dataStore.AddChannel(channel);
|
||||
dmChannels.TryAdd(model.Id);
|
||||
return channel;
|
||||
}
|
||||
internal CachedDMChannel RemoveDMChannel(ulong id)
|
||||
@@ -317,6 +318,7 @@ namespace Discord
|
||||
var dmChannel = DataStore.RemoveChannel(id) as CachedDMChannel;
|
||||
var recipient = dmChannel.Recipient;
|
||||
recipient.RemoveDMChannel(id);
|
||||
_dmChannels.TryRemove(id);
|
||||
return dmChannel;
|
||||
}
|
||||
|
||||
@@ -455,6 +457,7 @@ namespace Discord
|
||||
|
||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
|
||||
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length);
|
||||
var dmChannels = new ConcurrentHashSet<ulong>();
|
||||
|
||||
var currentUser = new CachedSelfUser(this, data.User);
|
||||
//dataStore.GetOrAddUser(data.User.Id, _ => currentUser);
|
||||
@@ -462,10 +465,11 @@ namespace Discord
|
||||
for (int i = 0; i < data.Guilds.Length; i++)
|
||||
AddGuild(data.Guilds[i], dataStore);
|
||||
for (int i = 0; i < data.PrivateChannels.Length; i++)
|
||||
AddDMChannel(data.PrivateChannels[i], dataStore);
|
||||
AddDMChannel(data.PrivateChannels[i], dataStore, dmChannels);
|
||||
|
||||
_sessionId = data.SessionId;
|
||||
_currentUser = currentUser;
|
||||
_dmChannels = dmChannels;
|
||||
DataStore = dataStore;
|
||||
|
||||
await Ready.RaiseAsync().ConfigureAwait(false);
|
||||
@@ -577,7 +581,7 @@ namespace Discord
|
||||
}
|
||||
}
|
||||
else
|
||||
channel = AddDMChannel(data, DataStore);
|
||||
channel = AddDMChannel(data, DataStore, _dmChannels);
|
||||
if (channel != null)
|
||||
await ChannelCreated.RaiseAsync(channel).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@ namespace Discord
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal class GuildIntegration : Entity<ulong>, IGuildIntegration
|
||||
{
|
||||
private long _syncedAtTicks;
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string Type { get; private set; }
|
||||
public bool IsEnabled { get; private set; }
|
||||
public bool IsSyncing { get; private set; }
|
||||
public ulong ExpireBehavior { get; private set; }
|
||||
public ulong ExpireGracePeriod { get; private set; }
|
||||
public DateTime SyncedAt { get; private set; }
|
||||
|
||||
public Guild Guild { get; private set; }
|
||||
public Role Role { get; private set; }
|
||||
@@ -23,6 +24,7 @@ namespace Discord
|
||||
public IntegrationAccount Account { get; private set; }
|
||||
|
||||
public override DiscordClient Discord => Guild.Discord;
|
||||
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks);
|
||||
|
||||
public GuildIntegration(Guild guild, Model model)
|
||||
: base(model.Id)
|
||||
@@ -41,7 +43,7 @@ namespace Discord
|
||||
IsSyncing = model.Syncing;
|
||||
ExpireBehavior = model.ExpireBehavior;
|
||||
ExpireGracePeriod = model.ExpireGracePeriod;
|
||||
SyncedAt = model.SyncedAt;
|
||||
_syncedAtTicks = model.SyncedAt.UtcTicks;
|
||||
|
||||
Role = Guild.GetRole(model.RoleId);
|
||||
User = new User(Discord, model.User);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
//TODO: Add docstrings
|
||||
public interface IGuildIntegration
|
||||
{
|
||||
ulong Id { get; }
|
||||
@@ -11,7 +12,7 @@ namespace Discord
|
||||
bool IsSyncing { get; }
|
||||
ulong ExpireBehavior { get; }
|
||||
ulong ExpireGracePeriod { get; }
|
||||
DateTime SyncedAt { get; }
|
||||
DateTimeOffset SyncedAt { get; }
|
||||
IntegrationAccount Account { get; }
|
||||
|
||||
IGuild Guild { get; }
|
||||
|
||||
@@ -5,6 +5,6 @@ namespace Discord
|
||||
public interface ISnowflakeEntity : IEntity<ulong>
|
||||
{
|
||||
/// <summary> Gets when this object was created. </summary>
|
||||
DateTime CreatedAt { get; }
|
||||
DateTimeOffset CreatedAt { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ namespace Discord
|
||||
/// <summary> Gets the amount of times this invite has been used. </summary>
|
||||
int Uses { get; }
|
||||
/// <summary> Gets when this invite was created. </summary>
|
||||
DateTime CreatedAt { get; }
|
||||
DateTimeOffset CreatedAt { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,17 @@ namespace Discord
|
||||
{
|
||||
internal class InviteMetadata : Invite, IInviteMetadata
|
||||
{
|
||||
private long _createdAtTicks;
|
||||
|
||||
public bool IsRevoked { get; private set; }
|
||||
public bool IsTemporary { get; private set; }
|
||||
public int? MaxAge { get; private set; }
|
||||
public int? MaxUses { get; private set; }
|
||||
public int Uses { get; private set; }
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public IUser Inviter { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks);
|
||||
|
||||
public InviteMetadata(DiscordClient client, Model model)
|
||||
: base(client, model)
|
||||
{
|
||||
@@ -28,7 +31,7 @@ namespace Discord
|
||||
MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null;
|
||||
MaxUses = model.MaxUses;
|
||||
Uses = model.Uses;
|
||||
CreatedAt = model.CreatedAt;
|
||||
_createdAtTicks = model.CreatedAt.UtcTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Discord
|
||||
public interface IMessage : IDeletable, ISnowflakeEntity, IUpdateable
|
||||
{
|
||||
/// <summary> Gets the time of this message's last edit, if any. </summary>
|
||||
DateTime? EditedTimestamp { get; }
|
||||
DateTimeOffset? EditedTimestamp { get; }
|
||||
/// <summary> Returns true if this message was sent as a text-to-speech message. </summary>
|
||||
bool IsTTS { get; }
|
||||
/// <summary> Returns the original, unprocessed text for this message. </summary>
|
||||
@@ -16,7 +16,7 @@ namespace Discord
|
||||
/// <summary> Returns the text for this message after mention processing. </summary>
|
||||
string Text { get; }
|
||||
/// <summary> Gets the time this message was sent. </summary>
|
||||
DateTime Timestamp { get; }
|
||||
DateTimeOffset Timestamp { get; }
|
||||
|
||||
/// <summary> Gets the channel this message was sent to. </summary>
|
||||
IMessageChannel Channel { get; }
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Discord
|
||||
internal class Message : SnowflakeEntity, IMessage
|
||||
{
|
||||
private bool _isMentioningEveryone;
|
||||
private long _timestampTicks;
|
||||
private long? _editedTimestampTicks;
|
||||
|
||||
public DateTime? EditedTimestamp { get; private set; }
|
||||
public bool IsTTS { get; private set; }
|
||||
public string RawText { get; private set; }
|
||||
public string Text { get; private set; }
|
||||
public DateTime Timestamp { get; private set; }
|
||||
|
||||
public IMessageChannel Channel { get; }
|
||||
public IUser Author { get; }
|
||||
@@ -29,6 +29,8 @@ namespace Discord
|
||||
public ImmutableArray<User> MentionedUsers { get; private set; }
|
||||
|
||||
public override DiscordClient Discord => (Channel as Entity<ulong>).Discord;
|
||||
public DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
|
||||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);
|
||||
|
||||
public Message(IMessageChannel channel, IUser author, Model model)
|
||||
: base(model.Id)
|
||||
@@ -56,9 +58,9 @@ namespace Discord
|
||||
if (model.IsTextToSpeech.IsSpecified)
|
||||
IsTTS = model.IsTextToSpeech.Value;
|
||||
if (model.Timestamp.IsSpecified)
|
||||
Timestamp = model.Timestamp.Value;
|
||||
_timestampTicks = model.Timestamp.Value.UtcTicks;
|
||||
if (model.EditedTimestamp.IsSpecified)
|
||||
EditedTimestamp = model.EditedTimestamp.Value;
|
||||
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks;
|
||||
if (model.IsMentioningEveryone.IsSpecified)
|
||||
_isMentioningEveryone = model.IsMentioningEveryone.Value;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Discord
|
||||
internal abstract class SnowflakeEntity : Entity<ulong>, ISnowflakeEntity
|
||||
{
|
||||
//TODO: C#7 Candidate for Extension Property. Lets us remove this class.
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
|
||||
public SnowflakeEntity(ulong id)
|
||||
: base(id)
|
||||
|
||||
@@ -14,9 +14,10 @@ namespace Discord
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
internal class GuildUser : IGuildUser, ISnowflakeEntity
|
||||
{
|
||||
private long? _joinedAtTicks;
|
||||
|
||||
public bool IsDeaf { get; private set; }
|
||||
public bool IsMute { get; private set; }
|
||||
public DateTime? JoinedAt { get; private set; }
|
||||
public string Nickname { get; private set; }
|
||||
public GuildPermissions GuildPermissions { get; private set; }
|
||||
|
||||
@@ -26,7 +27,7 @@ namespace Discord
|
||||
|
||||
public ulong Id => User.Id;
|
||||
public string AvatarUrl => User.AvatarUrl;
|
||||
public DateTime CreatedAt => User.CreatedAt;
|
||||
public DateTimeOffset CreatedAt => User.CreatedAt;
|
||||
public string Discriminator => User.Discriminator;
|
||||
public bool IsAttached => User.IsAttached;
|
||||
public bool IsBot => User.IsBot;
|
||||
@@ -36,6 +37,7 @@ namespace Discord
|
||||
public virtual Game? Game => User.Game;
|
||||
|
||||
public DiscordClient Discord => Guild.Discord;
|
||||
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
|
||||
|
||||
public GuildUser(Guild guild, User user)
|
||||
{
|
||||
@@ -62,7 +64,7 @@ namespace Discord
|
||||
//if (model.Mute.IsSpecified)
|
||||
IsMute = model.Mute;
|
||||
//if (model.JoinedAt.IsSpecified)
|
||||
JoinedAt = model.JoinedAt;
|
||||
_joinedAtTicks = model.JoinedAt.UtcTicks;
|
||||
if (model.Nick.IsSpecified)
|
||||
Nickname = model.Nick.Value;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Discord
|
||||
/// <summary> Returns true if the guild has muted this user. </summary>
|
||||
bool IsMute { get; }
|
||||
/// <summary> Gets when this user joined this guild. </summary>
|
||||
DateTime? JoinedAt { get; }
|
||||
DateTimeOffset? JoinedAt { get; }
|
||||
/// <summary> Gets the nickname for this user. </summary>
|
||||
string Nickname { get; }
|
||||
/// <summary> Gets the guild-level permissions granted to this user by their roles. </summary>
|
||||
|
||||
@@ -9,14 +9,15 @@ namespace Discord
|
||||
internal class User : SnowflakeEntity, IUser
|
||||
{
|
||||
private string _avatarId;
|
||||
|
||||
public string Discriminator { get; private set; }
|
||||
private ushort _discriminator;
|
||||
|
||||
public bool IsBot { get; private set; }
|
||||
public string Username { get; private set; }
|
||||
|
||||
public override DiscordClient Discord { get; }
|
||||
|
||||
public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId);
|
||||
public string Discriminator => _discriminator.ToString("D4");
|
||||
public string Mention => MentionUtils.Mention(this, false);
|
||||
public string NicknameMention => MentionUtils.Mention(this, true);
|
||||
public virtual Game? Game => null;
|
||||
@@ -33,7 +34,7 @@ namespace Discord
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
_avatarId = model.Avatar;
|
||||
Discriminator = model.Discriminator;
|
||||
_discriminator = ushort.Parse(model.Discriminator);
|
||||
IsBot = model.Bot;
|
||||
Username = model.Username;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,15 @@ namespace Discord
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public CachedGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id);
|
||||
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetChannel(x)).ToReadOnlyCollection(_channels);
|
||||
public IReadOnlyCollection<ICachedGuildChannel> Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
var channels = _channels;
|
||||
var store = Discord.DataStore;
|
||||
return channels.Select(x => store.GetChannel(x) as ICachedGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels);
|
||||
}
|
||||
}
|
||||
public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection();
|
||||
|
||||
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
|
||||
|
||||
@@ -11,12 +11,13 @@ namespace Discord.Extensions
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source)
|
||||
=> new ConcurrentDictionaryWrapper<TValue, TSource>(source, query);
|
||||
}
|
||||
|
||||
|
||||
internal struct ConcurrentDictionaryWrapper<TValue, TSource> : IReadOnlyCollection<TValue>
|
||||
{
|
||||
private readonly IReadOnlyCollection<TSource> _source;
|
||||
private readonly IEnumerable<TValue> _query;
|
||||
|
||||
//It's okay that this count is affected by race conditions - we're wrapping a concurrent collection and that's to be expected
|
||||
public int Count => _source.Count;
|
||||
|
||||
public ConcurrentDictionaryWrapper(IReadOnlyCollection<TSource> source, IEnumerable<TValue> query)
|
||||
|
||||
@@ -4,15 +4,12 @@ namespace Discord
|
||||
{
|
||||
internal static class DateTimeUtils
|
||||
{
|
||||
private const ulong EpochTicks = 621355968000000000UL;
|
||||
private const ulong DiscordEpochMillis = 1420070400000UL;
|
||||
public static DateTimeOffset FromSnowflake(ulong value)
|
||||
=> DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL));
|
||||
|
||||
public static DateTime FromEpochMilliseconds(ulong value)
|
||||
=> new DateTime((long)(value * TimeSpan.TicksPerMillisecond + EpochTicks), DateTimeKind.Utc);
|
||||
public static DateTime FromEpochSeconds(ulong value)
|
||||
=> new DateTime((long)(value * TimeSpan.TicksPerSecond + EpochTicks), DateTimeKind.Utc);
|
||||
|
||||
public static DateTime FromSnowflake(ulong value)
|
||||
=> FromEpochMilliseconds((value >> 22) + DiscordEpochMillis);
|
||||
public static DateTimeOffset FromTicks(long ticks)
|
||||
=> new DateTimeOffset(ticks, TimeSpan.Zero);
|
||||
public static DateTimeOffset? FromTicks(long? ticks)
|
||||
=> ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user