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;