Add new reference system

This commit is contained in:
RogueException
2015-10-25 04:12:13 -03:00
parent 0086537872
commit 674c585bee
10 changed files with 205 additions and 164 deletions

View File

@@ -223,6 +223,9 @@
<Compile Include="..\Discord.Net\Helpers\Mention.cs"> <Compile Include="..\Discord.Net\Helpers\Mention.cs">
<Link>Helpers\Mention.cs</Link> <Link>Helpers\Mention.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Helpers\Reference.cs">
<Link>Helpers\Reference.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Helpers\TaskHelper.cs"> <Compile Include="..\Discord.Net\Helpers\TaskHelper.cs">
<Link>Helpers\TaskHelper.cs</Link> <Link>Helpers\TaskHelper.cs</Link>
</Compile> </Compile>

View File

@@ -0,0 +1,68 @@
using System;
namespace Discord
{
internal class Reference<T>
where T : CachedObject
{
private Action<T> _onCache, _onUncache;
private Func<string, T> _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<string, T> onUpdate, Action<T> onCache = null, Action<T> onUncache = null)
: this(null, onUpdate, onCache, onUncache) { }
public Reference(string id, Func<string, T> getItem, Action<T> onCache = null, Action<T> onUncache = null)
{
_id = id;
_getItem = getItem;
_onCache = onCache;
_onUncache = onUncache;
}
}
}

View File

@@ -5,6 +5,8 @@
protected readonly DiscordClient _client; protected readonly DiscordClient _client;
private bool _isCached; private bool _isCached;
internal bool IsCached => _isCached;
internal CachedObject(DiscordClient client, string id) internal CachedObject(DiscordClient client, string id)
{ {
_client = client; _client = client;
@@ -18,18 +20,18 @@
internal void Cache() internal void Cache()
{ {
OnCached(); LoadReferences();
_isCached = true; _isCached = true;
} }
internal void Uncache() internal void Uncache()
{ {
if (_isCached) if (_isCached)
{ {
OnUncached(); UnloadReferences();
_isCached = false; _isCached = false;
} }
} }
internal abstract void OnCached(); internal abstract void LoadReferences();
internal abstract void OnUncached(); internal abstract void UnloadReferences();
} }
} }

View File

@@ -33,19 +33,19 @@ namespace Discord
/// <summary> Returns the position of this channel in the channel list for this server. </summary> /// <summary> Returns the position of this channel in the channel list for this server. </summary>
public int Position { get; private set; } public int Position { get; private set; }
/// <summary> Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). </summary> /// <summary> Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). </summary>
public bool IsPrivate => _recipientId != null; public bool IsPrivate => _recipient.Id != null;
/// <summary> Returns the type of this channel (see ChannelTypes). </summary> /// <summary> Returns the type of this channel (see ChannelTypes). </summary>
public string Type { get; private set; } public string Type { get; private set; }
/// <summary> Returns the server containing this channel. </summary> /// <summary> Returns the server containing this channel. </summary>
[JsonIgnore] [JsonIgnore]
public Server Server { get; private set; } public Server Server => _server.Value;
private readonly string _serverId; private readonly Reference<Server> _server;
/// For private chats, returns the target user, otherwise null. /// For private chats, returns the target user, otherwise null.
[JsonIgnore] [JsonIgnore]
public User Recipient { get; private set; } public User Recipient => _recipient.Value;
private readonly string _recipientId; private readonly Reference<User> _recipient;
/// <summary> Returns a collection of all users with read access to this channel. </summary> /// <summary> Returns a collection of all users with read access to this channel. </summary>
[JsonIgnore] [JsonIgnore]
@@ -74,39 +74,35 @@ namespace Discord
internal Channel(DiscordClient client, string id, string serverId, string recipientId) internal Channel(DiscordClient client, string id, string serverId, string recipientId)
: base(client, id) : base(client, id)
{ {
_serverId = serverId; _server = new Reference<Server>(serverId,
_recipientId = recipientId; x => _client.Servers[x],
x => x.AddChannel(this),
x => x.RemoveChannel(this));
_recipient = new Reference<User>(recipientId,
x => _client.Users[x, _server.Id],
x =>
{
Name = "@" + x.Name;
x.GlobalUser.PrivateChannel = this;
},
x => x.GlobalUser.PrivateChannel = null);
_permissionOverwrites = _initialPermissionsOverwrites; _permissionOverwrites = _initialPermissionsOverwrites;
_areMembersStale = true; _areMembersStale = true;
//Local Cache //Local Cache
_messages = new ConcurrentDictionary<string, Message>(); _messages = new ConcurrentDictionary<string, Message>();
} }
internal override void OnCached() internal override void LoadReferences()
{ {
if (IsPrivate) if (IsPrivate)
{ _recipient.Load();
var recipient = _client.Users[_recipientId, null];
Name = "@" + recipient.Name;
recipient.GlobalUser.PrivateChannel = this;
Recipient = recipient;
}
else else
{ _server.Load();
var server = _client.Servers[_serverId];
server.AddChannel(this);
Server = server;
}
} }
internal override void OnUncached() internal override void UnloadReferences()
{ {
var server = Server; _server.Unload();
if (server != null) _recipient.Unload();
server.RemoveChannel(this);
var recipient = Recipient;
if (recipient != null)
recipient.GlobalUser.PrivateChannel = null;
var globalMessages = _client.Messages; var globalMessages = _client.Messages;
var messages = _messages; var messages = _messages;
@@ -167,7 +163,10 @@ namespace Discord
} }
private void UpdateMembersCache() 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<string, User>();
_areMembersStale = false; _areMembersStale = false;
} }

View File

@@ -43,8 +43,8 @@ namespace Discord
{ {
_users = new ConcurrentDictionary<string, User>(); _users = new ConcurrentDictionary<string, User>();
} }
internal override void OnCached() { } internal override void LoadReferences() { }
internal override void OnUncached() internal override void UnloadReferences()
{ {
//Don't need to clean _users - they're considered owned by server //Don't need to clean _users - they're considered owned by server
} }

View File

@@ -21,58 +21,37 @@ namespace Discord
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> /// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary>
public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Id); public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Id);
/// <summary> Returns the user that created this invite. </summary> /// <summary> Returns the user that created this invite. </summary>
[JsonIgnore] [JsonIgnore]
public User Inviter { get; private set; } public User Inviter => _inviter.Value;
[JsonProperty("InviterId")] private readonly Reference<User> _inviter;
private readonly string _inviterId;
/// <summary> Returns the server this invite is to. </summary> /// <summary> Returns the server this invite is to. </summary>
[JsonIgnore] [JsonIgnore]
public Server Server { get; private set; } public Server Server => _server.Value;
[JsonProperty("ServerId")] private readonly Reference<Server> _server;
private readonly string _serverId;
/// <summary> Returns the channel this invite is to. </summary> /// <summary> Returns the channel this invite is to. </summary>
[JsonIgnore] [JsonIgnore]
public Channel Channel { get; private set; } public Channel Channel => _channel.Value;
[JsonProperty("ChannelId")] private readonly Reference<Channel> _channel;
private readonly string _channelId;
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId)
: base(client, code) : base(client, code)
{ {
XkcdCode = xkcdPass; XkcdCode = xkcdPass;
_serverId = serverId; _server = new Reference<Server>(serverId, x => _client.Servers[x] ?? new Server(client, x));
_inviterId = inviterId; _inviter = new Reference<User>(serverId, x => _client.Users[x, _server.Id] ?? new User(client, x, _server.Id));
_channelId = channelId; _channel = new Reference<Channel>(serverId, x => _client.Channels[x] ?? new Channel(client, x, _server.Id, null));
} }
internal override void LoadReferences()
internal override void OnCached()
{ {
var server = _client.Servers[_serverId]; _server.Load();
if (server == null) _inviter.Load();
server = new Server(_client, _serverId); _channel.Load();
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;
}
} }
internal override void OnUncached() { } internal override void UnloadReferences() { }
public override string ToString() => XkcdCode ?? Id; public override string ToString() => XkcdCode ?? Id;

View File

@@ -129,48 +129,36 @@ namespace Discord
/// <summary> Returns the server containing the channel this message was sent to. </summary> /// <summary> Returns the server containing the channel this message was sent to. </summary>
[JsonIgnore] [JsonIgnore]
public Server Server => Channel.Server; public Server Server => _channel.Value.Server;
/// <summary> Returns the channel this message was sent to. </summary> /// <summary> Returns the channel this message was sent to. </summary>
[JsonIgnore] [JsonIgnore]
public Channel Channel { get; private set; } public Channel Channel => _channel.Value;
private readonly string _channelId; private readonly Reference<Channel> _channel;
/// <summary> Returns true if the current user created this message. </summary> /// <summary> Returns true if the current user created this message. </summary>
public bool IsAuthor => _client.CurrentUserId == _userId; public bool IsAuthor => _client.CurrentUserId == _user.Id;
/// <summary> Returns the author of this message. </summary> /// <summary> Returns the author of this message. </summary>
[JsonIgnore] [JsonIgnore]
public User User { get; private set; } public User User => _user.Value;
private readonly string _userId; private readonly Reference<User> _user;
internal Message(DiscordClient client, string id, string channelId, string userId) internal Message(DiscordClient client, string id, string channelId, string userId)
: base(client, id) : base(client, id)
{ {
_channelId = channelId; _channel = new Reference<Channel>(channelId, x => _client.Channels[x], x => x.AddMessage(this), x => x.RemoveMessage(this));
_userId = userId; _user = new Reference<User>(userId, x => _client.Users[x]);
Attachments = _initialAttachments; Attachments = _initialAttachments;
Embeds = _initialEmbeds; Embeds = _initialEmbeds;
} }
internal override void OnCached() internal override void LoadReferences()
{ {
//References _channel.Load();
var channel = _client.Channels[_channelId]; _user.Load();
channel.AddMessage(this);
Channel = channel;
var user = _client.Users[_userId, channel.Server?.Id];
//user.AddMessage(this);
User = user;
} }
internal override void OnUncached() internal override void UnloadReferences()
{ {
//References _channel.Unload();
var channel = Channel; _user.Unload();
if (channel != null)
channel.RemoveMessage(this);
/*var user = User;
if (user != null)
user.RemoveMessage(this);*/
} }
internal void Update(MessageInfo model) internal void Update(MessageInfo model)

View File

@@ -6,9 +6,7 @@ using System.Linq;
namespace Discord namespace Discord
{ {
public sealed class Role : CachedObject public sealed class Role : CachedObject
{ {
private readonly string _serverId;
/// <summary> Returns the name of this role. </summary> /// <summary> Returns the name of this role. </summary>
public string Name { get; private set; } public string Name { get; private set; }
/// <summary> If true, this role is displayed isolated from other users. </summary> /// <summary> If true, this role is displayed isolated from other users. </summary>
@@ -22,40 +20,35 @@ namespace Discord
/// <summary> Returns the the permissions contained by this role. </summary> /// <summary> Returns the the permissions contained by this role. </summary>
public ServerPermissions Permissions { get; } public ServerPermissions Permissions { get; }
/// <summary> Returns the server this role is a member of. </summary> /// <summary> Returns the server this role is a member of. </summary>
[JsonIgnore] [JsonIgnore]
public Server Server { get; private set; } public Server Server => _server.Value;
private readonly Reference<Server> _server;
/// <summary> Returns true if this is the role representing all users in a server. </summary> /// <summary> Returns true if this is the role representing all users in a server. </summary>
public bool IsEveryone => _serverId == null || Id == _serverId; public bool IsEveryone => _server.Id == null || Id == _server.Id;
/// <summary> Returns a list of all members in this role. </summary> /// <summary> Returns a list of all members in this role. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<User> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); public IEnumerable<User> Members => _server.Id != null ? (IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this))) : new User[0];
//TODO: Add local members cache //TODO: Add local members cache
internal Role(DiscordClient client, string id, string serverId) internal Role(DiscordClient client, string id, string serverId)
: base(client, id) : base(client, id)
{ {
_serverId = serverId; _server = new Reference<Server>(serverId, x => _client.Servers[x], x => x.AddRole(this), x => x.RemoveRole(this));
Permissions = new ServerPermissions(0); Permissions = new ServerPermissions(0);
Permissions.Lock(); Permissions.Lock();
Color = new Color(0); Color = new Color(0);
Color.Lock(); Color.Lock();
} }
internal override void OnCached() internal override void LoadReferences()
{ {
//References _server.Load();
var server = _client.Servers[_serverId];
server.AddRole(this);
Server = server;
} }
internal override void OnUncached() internal override void UnloadReferences()
{ {
//References _server.Unload();
var server = Server;
if (server != null)
server.RemoveRole(this);
} }
internal void Update(RoleInfo model) internal void Update(RoleInfo model)

View File

@@ -84,8 +84,8 @@ namespace Discord
_bans = new ConcurrentDictionary<string, bool>(); _bans = new ConcurrentDictionary<string, bool>();
_invites = new ConcurrentDictionary<string, Invite>(); _invites = new ConcurrentDictionary<string, Invite>();
} }
internal override void OnCached() { } internal override void LoadReferences() { }
internal override void OnUncached() internal override void UnloadReferences()
{ {
//Global Cache //Global Cache
var globalChannels = _client.Channels; var globalChannels = _client.Channels;
@@ -210,21 +210,31 @@ namespace Discord
internal void AddMember(User member) internal void AddMember(User member)
{ {
_members.TryAdd(member.Id, member); if (_members.TryAdd(member.Id, member))
foreach (var channel in Channels)
{ {
member.AddChannel(channel); if (member.Id == _ownerId)
channel.InvalidatePermissionsCache(member); Owner = member;
foreach (var channel in Channels)
{
member.AddChannel(channel);
channel.InvalidatePermissionsCache(member);
}
} }
} }
internal void RemoveMember(User member) internal void RemoveMember(User member)
{ {
foreach (var channel in Channels) if (_members.TryRemove(member.Id, out member))
{ {
member.RemoveChannel(channel); if (member.Id == _ownerId)
channel.InvalidatePermissionsCache(member); 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); internal void HasMember(User user) => _members.ContainsKey(user.Id);

View File

@@ -16,7 +16,7 @@ namespace Discord
private ServerPermissions _serverPermissions; private ServerPermissions _serverPermissions;
/// <summary> Returns a unique identifier combining this user's id with its server's. </summary> /// <summary> Returns a unique identifier combining this user's id with its server's. </summary>
internal string UniqueId => GetId(Id, _serverId); internal string UniqueId => GetId(Id, _server.Id);
/// <summary> Returns the name of this user on this server. </summary> /// <summary> Returns the name of this user on this server. </summary>
public string Name { get; private set; } public string Name { get; private set; }
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> /// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary>
@@ -49,11 +49,12 @@ namespace Discord
private DateTime _lastOnline; private DateTime _lastOnline;
[JsonIgnore] [JsonIgnore]
internal GlobalUser GlobalUser { get; private set; } internal GlobalUser GlobalUser => _globalUser.Value;
private readonly Reference<GlobalUser> _globalUser;
[JsonIgnore] [JsonIgnore]
public Server Server { get; private set; } public Server Server => _server.Value;
private string _serverId; private readonly Reference<Server> _server;
[JsonIgnore] [JsonIgnore]
public Channel VoiceChannel { get; private set; } public Channel VoiceChannel { get; private set; }
@@ -64,7 +65,7 @@ namespace Discord
/// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> /// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.User.Id == Id && x.Server.Id == _serverId); public IEnumerable<Message> Messages => _client.Messages.Where(x => x.User.Id == Id && x.Server.Id == _server.Id);
/// <summary> Returns a collection of all channels this user is a member of. </summary> /// <summary> Returns a collection of all channels this user is a member of. </summary>
[JsonIgnore] [JsonIgnore]
@@ -73,44 +74,41 @@ namespace Discord
internal User(DiscordClient client, string id, string serverId) internal User(DiscordClient client, string id, string serverId)
: base(client, id) : base(client, id)
{ {
_serverId = serverId; _globalUser = new Reference<GlobalUser>(id,
x => _client.GlobalUsers.GetOrAdd(x),
x => x.AddUser(this),
x => x.RemoveUser(this));
_server = new Reference<Server>(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; Status = UserStatus.Offline;
//_roles = new Dictionary<string, Role>();
_channels = new ConcurrentDictionary<string, Channel>(); _channels = new ConcurrentDictionary<string, Channel>();
_permissions = new ConcurrentDictionary<string, ChannelPermissions>(); _permissions = new ConcurrentDictionary<string, ChannelPermissions>();
_serverPermissions = new ServerPermissions(); _serverPermissions = new ServerPermissions();
}
internal override void OnCached() if (serverId == null)
{
if (_serverId != null)
{
var server = _client.Servers[_serverId];
server.AddMember(this);
if (Id == _client.CurrentUserId)
server.CurrentMember = this;
Server = server;
}
else
UpdateRoles(null); UpdateRoles(null);
var user = _client.GlobalUsers.GetOrAdd(Id);
user.AddUser(this);
GlobalUser = user;
} }
internal override void OnUncached() internal override void LoadReferences()
{ {
//References _globalUser.Load();
var server = Server; _server.Load();
if (server != null) }
{ internal override void UnloadReferences()
server.RemoveMember(this); {
if (Id == _client.CurrentUserId) _globalUser.Unload();
server.CurrentMember = null; _server.Unload();
}
var globalUser = GlobalUser;
if (globalUser != null)
globalUser.RemoveUser(this);
} }
public override string ToString() => Id; public override string ToString() => Id;
@@ -128,6 +126,7 @@ namespace Discord
{ {
if (model.User != null) if (model.User != null)
Update(model.User); Update(model.User);
if (model.JoinedAt.HasValue) if (model.JoinedAt.HasValue)
JoinedAt = model.JoinedAt.Value; JoinedAt = model.JoinedAt.Value;
if (model.Roles != null) if (model.Roles != null)
@@ -138,6 +137,7 @@ namespace Discord
internal void Update(ExtendedMemberInfo model) internal void Update(ExtendedMemberInfo model)
{ {
Update(model as API.MemberInfo); Update(model as API.MemberInfo);
if (model.IsServerDeafened != null) if (model.IsServerDeafened != null)
IsServerDeafened = model.IsServerDeafened.Value; IsServerDeafened = model.IsServerDeafened.Value;
if (model.IsServerMuted != null) if (model.IsServerMuted != null)
@@ -156,9 +156,8 @@ namespace Discord
if (Status == UserStatus.Offline) if (Status == UserStatus.Offline)
_lastOnline = DateTime.UtcNow; _lastOnline = DateTime.UtcNow;
} }
//Allows null GameId = model.GameId; //Allows null
GameId = model.GameId;
} }
internal void Update(VoiceMemberInfo model) internal void Update(VoiceMemberInfo model)
{ {
@@ -188,7 +187,7 @@ namespace Discord
else else
newRoles = new Dictionary<string, Role>(); newRoles = new Dictionary<string, Role>();
Role everyone; Role everyone;
if (_serverId != null) if (_server.Id != null)
everyone = Server.EveryoneRole; everyone = Server.EveryoneRole;
else else
everyone = _client.Roles.VirtualEveryone; everyone = _client.Roles.VirtualEveryone;