diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 6ad222b82..c07673b18 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -223,6 +223,9 @@ Helpers\Mention.cs + + Helpers\Reference.cs + Helpers\TaskHelper.cs diff --git a/src/Discord.Net/Helpers/Reference.cs b/src/Discord.Net/Helpers/Reference.cs new file mode 100644 index 000000000..f86c9d5a4 --- /dev/null +++ b/src/Discord.Net/Helpers/Reference.cs @@ -0,0 +1,68 @@ +using System; + +namespace Discord +{ + internal class Reference + where T : CachedObject + { + private Action _onCache, _onUncache; + private Func _getItem; + private string _id; + public string Id + { + get { return _id; } + set + { + _id = value; + _value = null; + } + } + + private T _value; + public T Value + { + get + { + var v = _value; //A little trickery to make this threadsafe + if (v != null && !_value.IsCached) + { + v = null; + _value = null; + } + if (v == null && _id != null) + { + v = _getItem(_id); + if (v != null) + _onCache(v); + _value = v; + } + return v; + } + } + + public T Load() + { + return Value; //Used for precaching + } + + public void Unload() + { + if (_onUncache != null) + { + var v = _value; + if (v != null && _onUncache != null) + _onUncache(v); + } + } + + public Reference(Func onUpdate, Action onCache = null, Action onUncache = null) + : this(null, onUpdate, onCache, onUncache) { } + public Reference(string id, Func getItem, Action onCache = null, Action onUncache = null) + { + _id = id; + _getItem = getItem; + _onCache = onCache; + _onUncache = onUncache; + } + } +} diff --git a/src/Discord.Net/Models/CachedObject.cs b/src/Discord.Net/Models/CachedObject.cs index ff5413002..0d0b3bf36 100644 --- a/src/Discord.Net/Models/CachedObject.cs +++ b/src/Discord.Net/Models/CachedObject.cs @@ -5,6 +5,8 @@ protected readonly DiscordClient _client; private bool _isCached; + internal bool IsCached => _isCached; + internal CachedObject(DiscordClient client, string id) { _client = client; @@ -18,18 +20,18 @@ internal void Cache() { - OnCached(); + LoadReferences(); _isCached = true; } internal void Uncache() { if (_isCached) { - OnUncached(); + UnloadReferences(); _isCached = false; } } - internal abstract void OnCached(); - internal abstract void OnUncached(); + internal abstract void LoadReferences(); + internal abstract void UnloadReferences(); } } diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index 2c4a19b9c..ae1cae7c3 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -33,19 +33,19 @@ namespace Discord /// Returns the position of this channel in the channel list for this server. public int Position { get; private set; } /// Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). - public bool IsPrivate => _recipientId != null; + public bool IsPrivate => _recipient.Id != null; /// Returns the type of this channel (see ChannelTypes). public string Type { get; private set; } /// Returns the server containing this channel. [JsonIgnore] - public Server Server { get; private set; } - private readonly string _serverId; + public Server Server => _server.Value; + private readonly Reference _server; /// For private chats, returns the target user, otherwise null. [JsonIgnore] - public User Recipient { get; private set; } - private readonly string _recipientId; + public User Recipient => _recipient.Value; + private readonly Reference _recipient; /// Returns a collection of all users with read access to this channel. [JsonIgnore] @@ -74,39 +74,35 @@ namespace Discord internal Channel(DiscordClient client, string id, string serverId, string recipientId) : base(client, id) { - _serverId = serverId; - _recipientId = recipientId; + _server = new Reference(serverId, + x => _client.Servers[x], + x => x.AddChannel(this), + x => x.RemoveChannel(this)); + _recipient = new Reference(recipientId, + x => _client.Users[x, _server.Id], + x => + { + Name = "@" + x.Name; + x.GlobalUser.PrivateChannel = this; + }, + x => x.GlobalUser.PrivateChannel = null); _permissionOverwrites = _initialPermissionsOverwrites; _areMembersStale = true; //Local Cache _messages = new ConcurrentDictionary(); } - internal override void OnCached() + internal override void LoadReferences() { if (IsPrivate) - { - var recipient = _client.Users[_recipientId, null]; - Name = "@" + recipient.Name; - recipient.GlobalUser.PrivateChannel = this; - Recipient = recipient; - } + _recipient.Load(); else - { - var server = _client.Servers[_serverId]; - server.AddChannel(this); - Server = server; - } + _server.Load(); } - internal override void OnUncached() + internal override void UnloadReferences() { - var server = Server; - if (server != null) - server.RemoveChannel(this); - - var recipient = Recipient; - if (recipient != null) - recipient.GlobalUser.PrivateChannel = null; + _server.Unload(); + _recipient.Unload(); var globalMessages = _client.Messages; var messages = _messages; @@ -167,7 +163,10 @@ namespace Discord } private void UpdateMembersCache() { - _members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); + if (_server.Id != null) + _members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); + else + _members = new Dictionary(); _areMembersStale = false; } diff --git a/src/Discord.Net/Models/GlobalUser.cs b/src/Discord.Net/Models/GlobalUser.cs index 72cc2c9bf..34848c641 100644 --- a/src/Discord.Net/Models/GlobalUser.cs +++ b/src/Discord.Net/Models/GlobalUser.cs @@ -43,8 +43,8 @@ namespace Discord { _users = new ConcurrentDictionary(); } - internal override void OnCached() { } - internal override void OnUncached() + internal override void LoadReferences() { } + internal override void UnloadReferences() { //Don't need to clean _users - they're considered owned by server } diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index e735b99af..f08f1c6cc 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -21,58 +21,37 @@ namespace Discord /// Returns a URL for this invite using XkcdCode if available or Id if not. public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Id); - + /// Returns the user that created this invite. [JsonIgnore] - public User Inviter { get; private set; } - [JsonProperty("InviterId")] - private readonly string _inviterId; + public User Inviter => _inviter.Value; + private readonly Reference _inviter; /// Returns the server this invite is to. [JsonIgnore] - public Server Server { get; private set; } - [JsonProperty("ServerId")] - private readonly string _serverId; + public Server Server => _server.Value; + private readonly Reference _server; /// Returns the channel this invite is to. [JsonIgnore] - public Channel Channel { get; private set; } - [JsonProperty("ChannelId")] - private readonly string _channelId; + public Channel Channel => _channel.Value; + private readonly Reference _channel; internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) : base(client, code) { XkcdCode = xkcdPass; - _serverId = serverId; - _inviterId = inviterId; - _channelId = channelId; + _server = new Reference(serverId, x => _client.Servers[x] ?? new Server(client, x)); + _inviter = new Reference(serverId, x => _client.Users[x, _server.Id] ?? new User(client, x, _server.Id)); + _channel = new Reference(serverId, x => _client.Channels[x] ?? new Channel(client, x, _server.Id, null)); } - - internal override void OnCached() + internal override void LoadReferences() { - var server = _client.Servers[_serverId]; - if (server == null) - server = new Server(_client, _serverId); - Server = server; - - if (_inviterId != null) - { - var inviter = _client.Users[_inviterId, _serverId]; - if (inviter == null) - inviter = new User(_client, _inviterId, _serverId); - Inviter = inviter; - } - - if (_channelId != null) - { - var channel = _client.Channels[_channelId]; - if (channel == null) - channel = new Channel(_client, _channelId, _serverId, null); - Channel = channel; - } + _server.Load(); + _inviter.Load(); + _channel.Load(); } - internal override void OnUncached() { } + internal override void UnloadReferences() { } public override string ToString() => XkcdCode ?? Id; diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index e73bf803b..24afcf9b9 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -129,48 +129,36 @@ namespace Discord /// Returns the server containing the channel this message was sent to. [JsonIgnore] - public Server Server => Channel.Server; + public Server Server => _channel.Value.Server; /// Returns the channel this message was sent to. [JsonIgnore] - public Channel Channel { get; private set; } - private readonly string _channelId; + public Channel Channel => _channel.Value; + private readonly Reference _channel; /// Returns true if the current user created this message. - public bool IsAuthor => _client.CurrentUserId == _userId; + public bool IsAuthor => _client.CurrentUserId == _user.Id; /// Returns the author of this message. [JsonIgnore] - public User User { get; private set; } - private readonly string _userId; + public User User => _user.Value; + private readonly Reference _user; internal Message(DiscordClient client, string id, string channelId, string userId) : base(client, id) { - _channelId = channelId; - _userId = userId; + _channel = new Reference(channelId, x => _client.Channels[x], x => x.AddMessage(this), x => x.RemoveMessage(this)); + _user = new Reference(userId, x => _client.Users[x]); Attachments = _initialAttachments; Embeds = _initialEmbeds; } - internal override void OnCached() + internal override void LoadReferences() { - //References - var channel = _client.Channels[_channelId]; - channel.AddMessage(this); - Channel = channel; - - var user = _client.Users[_userId, channel.Server?.Id]; - //user.AddMessage(this); - User = user; + _channel.Load(); + _user.Load(); } - internal override void OnUncached() + internal override void UnloadReferences() { - //References - var channel = Channel; - if (channel != null) - channel.RemoveMessage(this); - - /*var user = User; - if (user != null) - user.RemoveMessage(this);*/ + _channel.Unload(); + _user.Unload(); } internal void Update(MessageInfo model) diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index 83c8ba620..f65a17d13 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -6,9 +6,7 @@ using System.Linq; namespace Discord { public sealed class Role : CachedObject - { - private readonly string _serverId; - + { /// Returns the name of this role. public string Name { get; private set; } /// If true, this role is displayed isolated from other users. @@ -22,40 +20,35 @@ namespace Discord /// Returns the the permissions contained by this role. public ServerPermissions Permissions { get; } - + /// Returns the server this role is a member of. [JsonIgnore] - public Server Server { get; private set; } + public Server Server => _server.Value; + private readonly Reference _server; /// Returns true if this is the role representing all users in a server. - public bool IsEveryone => _serverId == null || Id == _serverId; + public bool IsEveryone => _server.Id == null || Id == _server.Id; /// Returns a list of all members in this role. [JsonIgnore] - public IEnumerable Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); + public IEnumerable Members => _server.Id != null ? (IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this))) : new User[0]; //TODO: Add local members cache internal Role(DiscordClient client, string id, string serverId) : base(client, id) { - _serverId = serverId; + _server = new Reference(serverId, x => _client.Servers[x], x => x.AddRole(this), x => x.RemoveRole(this)); Permissions = new ServerPermissions(0); Permissions.Lock(); Color = new Color(0); Color.Lock(); } - internal override void OnCached() + internal override void LoadReferences() { - //References - var server = _client.Servers[_serverId]; - server.AddRole(this); - Server = server; + _server.Load(); } - internal override void OnUncached() + internal override void UnloadReferences() { - //References - var server = Server; - if (server != null) - server.RemoveRole(this); + _server.Unload(); } internal void Update(RoleInfo model) diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index 20ea50fe8..2ceaac81f 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -84,8 +84,8 @@ namespace Discord _bans = new ConcurrentDictionary(); _invites = new ConcurrentDictionary(); } - internal override void OnCached() { } - internal override void OnUncached() + internal override void LoadReferences() { } + internal override void UnloadReferences() { //Global Cache var globalChannels = _client.Channels; @@ -210,21 +210,31 @@ namespace Discord internal void AddMember(User member) { - _members.TryAdd(member.Id, member); - foreach (var channel in Channels) + if (_members.TryAdd(member.Id, member)) { - member.AddChannel(channel); - channel.InvalidatePermissionsCache(member); + if (member.Id == _ownerId) + Owner = member; + + foreach (var channel in Channels) + { + member.AddChannel(channel); + channel.InvalidatePermissionsCache(member); + } } } internal void RemoveMember(User member) { - foreach (var channel in Channels) + if (_members.TryRemove(member.Id, out member)) { - member.RemoveChannel(channel); - channel.InvalidatePermissionsCache(member); + if (member.Id == _ownerId) + Owner = null; + + foreach (var channel in Channels) + { + member.RemoveChannel(channel); + channel.InvalidatePermissionsCache(member); + } } - _members.TryRemove(member.Id, out member); } internal void HasMember(User user) => _members.ContainsKey(user.Id); diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 6da926ea6..bfb21f1b0 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -16,7 +16,7 @@ namespace Discord private ServerPermissions _serverPermissions; /// Returns a unique identifier combining this user's id with its server's. - internal string UniqueId => GetId(Id, _serverId); + internal string UniqueId => GetId(Id, _server.Id); /// Returns the name of this user on this server. public string Name { get; private set; } /// Returns a by-name unique identifier separating this user from others with the same name. @@ -49,11 +49,12 @@ namespace Discord private DateTime _lastOnline; [JsonIgnore] - internal GlobalUser GlobalUser { get; private set; } + internal GlobalUser GlobalUser => _globalUser.Value; + private readonly Reference _globalUser; [JsonIgnore] - public Server Server { get; private set; } - private string _serverId; + public Server Server => _server.Value; + private readonly Reference _server; [JsonIgnore] public Channel VoiceChannel { get; private set; } @@ -64,7 +65,7 @@ namespace Discord /// Returns a collection of all messages this user has sent on this server that are still in cache. [JsonIgnore] - public IEnumerable Messages => _client.Messages.Where(x => x.User.Id == Id && x.Server.Id == _serverId); + public IEnumerable Messages => _client.Messages.Where(x => x.User.Id == Id && x.Server.Id == _server.Id); /// Returns a collection of all channels this user is a member of. [JsonIgnore] @@ -73,44 +74,41 @@ namespace Discord internal User(DiscordClient client, string id, string serverId) : base(client, id) { - _serverId = serverId; + _globalUser = new Reference(id, + x => _client.GlobalUsers.GetOrAdd(x), + x => x.AddUser(this), + x => x.RemoveUser(this)); + _server = new Reference(serverId, + x => _client.Servers[x], + x => + { + x.AddMember(this); + if (x.Id == _client.CurrentUserId) + x.CurrentMember = this; + }, + x => + { + x.RemoveMember(this); + if (x.Id == _client.CurrentUserId) + x.CurrentMember = null; + }); Status = UserStatus.Offline; - //_roles = new Dictionary(); _channels = new ConcurrentDictionary(); _permissions = new ConcurrentDictionary(); _serverPermissions = new ServerPermissions(); - } - internal override void OnCached() - { - if (_serverId != null) - { - var server = _client.Servers[_serverId]; - server.AddMember(this); - if (Id == _client.CurrentUserId) - server.CurrentMember = this; - Server = server; - } - else - UpdateRoles(null); - var user = _client.GlobalUsers.GetOrAdd(Id); - user.AddUser(this); - GlobalUser = user; + if (serverId == null) + UpdateRoles(null); } - internal override void OnUncached() + internal override void LoadReferences() { - //References - var server = Server; - if (server != null) - { - server.RemoveMember(this); - if (Id == _client.CurrentUserId) - server.CurrentMember = null; - } - - var globalUser = GlobalUser; - if (globalUser != null) - globalUser.RemoveUser(this); + _globalUser.Load(); + _server.Load(); + } + internal override void UnloadReferences() + { + _globalUser.Unload(); + _server.Unload(); } public override string ToString() => Id; @@ -128,6 +126,7 @@ namespace Discord { if (model.User != null) Update(model.User); + if (model.JoinedAt.HasValue) JoinedAt = model.JoinedAt.Value; if (model.Roles != null) @@ -138,6 +137,7 @@ namespace Discord internal void Update(ExtendedMemberInfo model) { Update(model as API.MemberInfo); + if (model.IsServerDeafened != null) IsServerDeafened = model.IsServerDeafened.Value; if (model.IsServerMuted != null) @@ -156,9 +156,8 @@ namespace Discord if (Status == UserStatus.Offline) _lastOnline = DateTime.UtcNow; } - - //Allows null - GameId = model.GameId; + + GameId = model.GameId; //Allows null } internal void Update(VoiceMemberInfo model) { @@ -188,7 +187,7 @@ namespace Discord else newRoles = new Dictionary(); Role everyone; - if (_serverId != null) + if (_server.Id != null) everyone = Server.EveryoneRole; else everyone = _client.Roles.VirtualEveryone;