@@ -6,7 +6,7 @@ using System.Threading.Tasks; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
internal interface ICached<TType> | |||||
internal interface ICached<TType> : ICached, IDisposable | |||||
{ | { | ||||
void Update(TType model); | void Update(TType model); | ||||
@@ -14,4 +14,9 @@ namespace Discord | |||||
TResult ToModel<TResult>() where TResult : TType, new(); | TResult ToModel<TResult>() where TResult : TType, new(); | ||||
} | } | ||||
public interface ICached | |||||
{ | |||||
bool IsFreed { get; } | |||||
} | |||||
} | } |
@@ -0,0 +1,76 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.WebSocket | |||||
{ | |||||
/// <summary> | |||||
/// Represents a lazily-loaded cached value that can be loaded synchronously or asynchronously. | |||||
/// </summary> | |||||
/// <typeparam name="TEntity">The type of the entity.</typeparam> | |||||
/// <typeparam name="TId">The primary id type of the entity.</typeparam> | |||||
public class LazyCached<TEntity, TId> | |||||
where TEntity : class, ICached | |||||
where TId : IEquatable<TId> | |||||
{ | |||||
/// <summary> | |||||
/// Gets or loads the cached value synchronously. | |||||
/// </summary> | |||||
public TEntity Value | |||||
=> GetOrLoad(); | |||||
/// <summary> | |||||
/// Gets whether or not the <see cref="Value"/> has been loaded and is still alive. | |||||
/// </summary> | |||||
public bool IsValueCreated | |||||
=> _loadedValue != null && _loadedValue.IsFreed; | |||||
private TEntity _loadedValue; | |||||
private readonly ILookupReferenceStore<TEntity, TId> _store; | |||||
private readonly TId _id; | |||||
private readonly object _lock = new(); | |||||
internal LazyCached(TEntity value) | |||||
{ | |||||
_loadedValue = value; | |||||
} | |||||
internal LazyCached(TId id, ILookupReferenceStore<TEntity, TId> store) | |||||
{ | |||||
_store = store; | |||||
_id = id; | |||||
} | |||||
private TEntity GetOrLoad() | |||||
{ | |||||
lock (_lock) | |||||
{ | |||||
if(!IsValueCreated) | |||||
_loadedValue = _store.Get(_id); | |||||
return _loadedValue; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Gets or loads the value from the cache asynchronously. | |||||
/// </summary> | |||||
/// <returns>The loaded or fetched entity.</returns> | |||||
public async ValueTask<TEntity> GetAsync() | |||||
{ | |||||
if (!IsValueCreated) | |||||
_loadedValue = await _store.GetAsync(_id).ConfigureAwait(false); | |||||
return _loadedValue; | |||||
} | |||||
} | |||||
public class LazyCached<TEntity> : LazyCached<TEntity, ulong> | |||||
where TEntity : class, ICached | |||||
{ | |||||
internal LazyCached(ulong id, ILookupReferenceStore<TEntity, ulong> store) | |||||
: base(id, store) { } | |||||
internal LazyCached(TEntity entity) | |||||
: base(entity) { } | |||||
} | |||||
} |
@@ -19,8 +19,6 @@ namespace Discord.WebSocket | |||||
private int _referenceCount; | private int _referenceCount; | ||||
private readonly object _lock = new object(); | |||||
public CacheReference(TType value) | public CacheReference(TType value) | ||||
{ | { | ||||
Reference = new(value); | Reference = new(value); | ||||
@@ -39,28 +37,31 @@ namespace Discord.WebSocket | |||||
public void ReleaseReference() | public void ReleaseReference() | ||||
{ | { | ||||
lock (_lock) | |||||
{ | |||||
if (_referenceCount > 0) | |||||
_referenceCount--; | |||||
} | |||||
Interlocked.Decrement(ref _referenceCount); | |||||
} | } | ||||
} | } | ||||
internal class ReferenceStore<TEntity, TModel, TId, ISharedEntity> | |||||
where TEntity : class, ICached<TModel>, ISharedEntity | |||||
internal interface ILookupReferenceStore<TEntity, TId> | |||||
{ | |||||
TEntity Get(TId id); | |||||
ValueTask<TEntity> GetAsync(TId id); | |||||
} | |||||
internal class ReferenceStore<TEntity, TModel, TId, TSharedEntity> : ILookupReferenceStore<TEntity, TId> | |||||
where TEntity : class, ICached<TModel>, TSharedEntity | |||||
where TModel : IEntityModel<TId> | where TModel : IEntityModel<TId> | ||||
where TId : IEquatable<TId> | where TId : IEquatable<TId> | ||||
where ISharedEntity : class | |||||
where TSharedEntity : class | |||||
{ | { | ||||
private readonly ICacheProvider _cacheProvider; | private readonly ICacheProvider _cacheProvider; | ||||
private readonly ConcurrentDictionary<TId, CacheReference<TEntity>> _references = new(); | private readonly ConcurrentDictionary<TId, CacheReference<TEntity>> _references = new(); | ||||
private IEntityStore<TModel, TId> _store; | private IEntityStore<TModel, TId> _store; | ||||
private Func<TModel, TEntity> _entityBuilder; | private Func<TModel, TEntity> _entityBuilder; | ||||
private Func<TId, RequestOptions, Task<ISharedEntity>> _restLookup; | |||||
private Func<TId, RequestOptions, Task<TSharedEntity>> _restLookup; | |||||
private readonly bool _allowSyncWaits; | private readonly bool _allowSyncWaits; | ||||
private readonly object _lock = new(); | private readonly object _lock = new(); | ||||
public ReferenceStore(ICacheProvider cacheProvider, Func<TModel, TEntity> entityBuilder, Func<TId, RequestOptions, Task<ISharedEntity>> restLookup, bool allowSyncWaits) | |||||
public ReferenceStore(ICacheProvider cacheProvider, Func<TModel, TEntity> entityBuilder, Func<TId, RequestOptions, Task<TSharedEntity>> restLookup, bool allowSyncWaits) | |||||
{ | { | ||||
_allowSyncWaits = allowSyncWaits; | _allowSyncWaits = allowSyncWaits; | ||||
_cacheProvider = cacheProvider; | _cacheProvider = cacheProvider; | ||||
@@ -68,6 +69,19 @@ namespace Discord.WebSocket | |||||
_restLookup = restLookup; | _restLookup = restLookup; | ||||
} | } | ||||
internal bool RemoveReference(TId id) | |||||
{ | |||||
if(_references.TryGetValue(id, out var rf)) | |||||
{ | |||||
rf.ReleaseReference(); | |||||
if (rf.CanRelease) | |||||
return _references.TryRemove(id, out _); | |||||
} | |||||
return false; | |||||
} | |||||
internal void ClearDeadReferences() | internal void ClearDeadReferences() | ||||
{ | { | ||||
lock (_lock) | lock (_lock) | ||||
@@ -135,7 +149,7 @@ namespace Discord.WebSocket | |||||
return null; | return null; | ||||
} | } | ||||
public async ValueTask<ISharedEntity> GetAsync(TId id, CacheMode mode, RequestOptions options = null) | |||||
public async ValueTask<TSharedEntity> GetAsync(TId id, CacheMode mode, RequestOptions options = null) | |||||
{ | { | ||||
if (TryGetReference(id, out var entity)) | if (TryGetReference(id, out var entity)) | ||||
{ | { | ||||
@@ -216,6 +230,28 @@ namespace Discord.WebSocket | |||||
return _store.AddOrUpdateAsync(model, CacheRunMode.Async); | return _store.AddOrUpdateAsync(model, CacheRunMode.Async); | ||||
} | } | ||||
public void BulkAddOrUpdate(IEnumerable<TModel> models) | |||||
{ | |||||
RunOrThrowValueTask(_store.AddOrUpdateBatchAsync(models, CacheRunMode.Sync)); | |||||
foreach(var model in models) | |||||
{ | |||||
if (_references.TryGetValue(model.Id, out var rf) && rf.Reference.TryGetTarget(out var entity)) | |||||
entity.Update(model); | |||||
} | |||||
} | |||||
public async ValueTask BulkAddOrUpdateAsync(IEnumerable<TModel> models) | |||||
{ | |||||
await _store.AddOrUpdateBatchAsync(models, CacheRunMode.Async).ConfigureAwait(false); | |||||
foreach (var model in models) | |||||
{ | |||||
if (_references.TryGetValue(model.Id, out var rf) && rf.Reference.TryGetTarget(out var entity)) | |||||
entity.Update(model); | |||||
} | |||||
} | |||||
public void Remove(TId id) | public void Remove(TId id) | ||||
{ | { | ||||
RunOrThrowValueTask(_store.RemoveAsync(id, CacheRunMode.Sync)); | RunOrThrowValueTask(_store.RemoveAsync(id, CacheRunMode.Sync)); | ||||
@@ -239,6 +275,9 @@ namespace Discord.WebSocket | |||||
_references.Clear(); | _references.Clear(); | ||||
return _store.PurgeAllAsync(CacheRunMode.Async); | return _store.PurgeAllAsync(CacheRunMode.Async); | ||||
} | } | ||||
TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id); | |||||
async ValueTask<TEntity> ILookupReferenceStore<TEntity, TId>.GetAsync(TId id) => (TEntity)await GetAsync(id, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
} | } | ||||
internal partial class ClientStateManager | internal partial class ClientStateManager | ||||
@@ -261,7 +300,7 @@ namespace Discord.WebSocket | |||||
PresenceStore = new ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence>( | PresenceStore = new ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence>( | ||||
_cacheProvider, | _cacheProvider, | ||||
m => SocketPresence.Create(m), | |||||
m => SocketPresence.Create(_client, m), | |||||
(id, options) => Task.FromResult<IPresence>(null), | (id, options) => Task.FromResult<IPresence>(null), | ||||
AllowSyncWaits); | AllowSyncWaits); | ||||
@@ -284,6 +323,9 @@ namespace Discord.WebSocket | |||||
await PresenceStore.InitializeAsync(); | await PresenceStore.InitializeAsync(); | ||||
} | } | ||||
public ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser> GetMemberStore(ulong guildId) | |||||
=> TryGetMemberStore(guildId, out var store) ? store : null; | |||||
public bool TryGetMemberStore(ulong guildId, out ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser> store) | public bool TryGetMemberStore(ulong guildId, out ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser> store) | ||||
=> _memberStores.TryGetValue(guildId, out store); | => _memberStores.TryGetValue(guildId, out store); | ||||
@@ -214,7 +214,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
if (StateManager.TryGetMemberStore(guildId, out var store)) | if (StateManager.TryGetMemberStore(guildId, out var store)) | ||||
return store.GetAsync(userId, cacheMode, options); | return store.GetAsync(userId, cacheMode, options); | ||||
return ValueTask.FromResult<IGuildUser>(null); | |||||
return new ValueTask<IGuildUser>((IGuildUser)null); | |||||
} | } | ||||
#endregion | #endregion | ||||
@@ -690,7 +690,7 @@ namespace Discord.WebSocket | |||||
if (CurrentUser == null) | if (CurrentUser == null) | ||||
return; | return; | ||||
var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | ||||
await StateManager.PresenceStore.AddOrUpdateAsync(new SocketPresence(Status, null, activities).ToModel()).ConfigureAwait(false); | |||||
await StateManager.PresenceStore.AddOrUpdateAsync(new SocketPresence(this, Status, null, activities).ToModel()).ConfigureAwait(false); | |||||
var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); | var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); | ||||
@@ -870,7 +870,7 @@ namespace Discord.WebSocket | |||||
Rest.CreateRestSelfUser(data.User); | Rest.CreateRestSelfUser(data.User); | ||||
var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | ||||
await StateManager.PresenceStore.AddOrUpdateAsync(new SocketPresence(Status, null, activities).ToModel()).ConfigureAwait(false); | |||||
await StateManager.PresenceStore.AddOrUpdateAsync(new SocketPresence(this, Status, null, activities).ToModel()).ConfigureAwait(false); | |||||
ApiClient.CurrentUserId = currentUser.Id; | ApiClient.CurrentUserId = currentUser.Id; | ||||
ApiClient.CurrentApplicationId = data.Application.Id; | ApiClient.CurrentApplicationId = data.Application.Id; | ||||
@@ -1345,7 +1345,7 @@ namespace Discord.WebSocket | |||||
if (user != null) | if (user != null) | ||||
user.Update(data.User); | user.Update(data.User); | ||||
else | else | ||||
user = StateManager.GetOrAddUser(data.User.Id, (x) => data.User); | |||||
user = await StateManager.UserStore.GetOrAddAsync(data.User.Id, _ => data.User).ConfigureAwait(false); | |||||
await TimedInvokeAsync(_userLeftEvent, nameof(UserLeft), guild, user).ConfigureAwait(false); | await TimedInvokeAsync(_userLeftEvent, nameof(UserLeft), guild, user).ConfigureAwait(false); | ||||
} | } | ||||
@@ -1560,7 +1560,7 @@ namespace Discord.WebSocket | |||||
SocketUser user = guild.GetUser(data.User.Id); | SocketUser user = guild.GetUser(data.User.Id); | ||||
if (user == null) | if (user == null) | ||||
user = SocketUnknownUser.Create(this, StateManager, data.User); | |||||
user = SocketUnknownUser.Create(this, data.User); | |||||
await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); | await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); | ||||
} | } | ||||
else | else | ||||
@@ -1584,9 +1584,9 @@ namespace Discord.WebSocket | |||||
return; | return; | ||||
} | } | ||||
SocketUser user = StateManager.GetUser(data.User.Id); | |||||
SocketUser user = (SocketUser)await StateManager.UserStore.GetAsync(data.User.Id, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
if (user == null) | if (user == null) | ||||
user = SocketUnknownUser.Create(this, StateManager, data.User); | |||||
user = SocketUnknownUser.Create(this, data.User); | |||||
await TimedInvokeAsync(_userUnbannedEvent, nameof(UserUnbanned), user, guild).ConfigureAwait(false); | await TimedInvokeAsync(_userUnbannedEvent, nameof(UserUnbanned), user, guild).ConfigureAwait(false); | ||||
} | } | ||||
else | else | ||||
@@ -1630,7 +1630,7 @@ namespace Discord.WebSocket | |||||
if (guild != null) | if (guild != null) | ||||
{ | { | ||||
if (data.WebhookId.IsSpecified) | if (data.WebhookId.IsSpecified) | ||||
author = SocketWebhookUser.Create(guild, StateManager, data.Author.Value, data.WebhookId.Value); | |||||
author = SocketWebhookUser.Create(guild, data.Author.Value, data.WebhookId.Value); | |||||
else | else | ||||
author = guild.GetUser(data.Author.Value.Id); | author = guild.GetUser(data.Author.Value.Id); | ||||
} | } | ||||
@@ -1695,7 +1695,7 @@ namespace Discord.WebSocket | |||||
if (guild != null) | if (guild != null) | ||||
{ | { | ||||
if (data.WebhookId.IsSpecified) | if (data.WebhookId.IsSpecified) | ||||
author = SocketWebhookUser.Create(guild, StateManager, data.Author.Value, data.WebhookId.Value); | |||||
author = SocketWebhookUser.Create(guild, data.Author.Value, data.WebhookId.Value); | |||||
else | else | ||||
author = guild.GetUser(data.Author.Value.Id); | author = guild.GetUser(data.Author.Value.Id); | ||||
} | } | ||||
@@ -1966,7 +1966,7 @@ namespace Discord.WebSocket | |||||
else | else | ||||
{ | { | ||||
var globalBefore = user.GlobalUser.Value.Clone(); | var globalBefore = user.GlobalUser.Value.Clone(); | ||||
if (user.GlobalUser.Value.Update(StateManager, data.User)) | |||||
if (user.GlobalUser.Value.Update(data.User)) | |||||
{ | { | ||||
//Global data was updated, trigger UserUpdated | //Global data was updated, trigger UserUpdated | ||||
await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); | await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); | ||||
@@ -1975,7 +1975,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
user = StateManager.GetUser(data.User.Id); | |||||
user = (SocketUser)await StateManager.UserStore.GetAsync(data.User.Id, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
if (user == null) | if (user == null) | ||||
{ | { | ||||
await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false); | await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false); | ||||
@@ -1984,9 +1984,9 @@ namespace Discord.WebSocket | |||||
} | } | ||||
var before = user.Presence?.Value?.Clone(); | var before = user.Presence?.Value?.Clone(); | ||||
user.Update(StateManager, data.User); | |||||
var after = SocketPresence.Create(data); | |||||
StateManager.AddOrUpdatePresence(data); | |||||
user.Update(data.User); | |||||
var after = SocketPresence.Create(this, data); | |||||
await StateManager.PresenceStore.AddOrUpdateAsync(data).ConfigureAwait(false); | |||||
await TimedInvokeAsync(_presenceUpdated, nameof(PresenceUpdated), user, before, after).ConfigureAwait(false); | await TimedInvokeAsync(_presenceUpdated, nameof(PresenceUpdated), user, before, after).ConfigureAwait(false); | ||||
} | } | ||||
break; | break; | ||||
@@ -2114,7 +2114,7 @@ namespace Discord.WebSocket | |||||
if (data.Id == CurrentUser.Id) | if (data.Id == CurrentUser.Id) | ||||
{ | { | ||||
var before = CurrentUser.Clone(); | var before = CurrentUser.Clone(); | ||||
CurrentUser.Update(StateManager, data); | |||||
CurrentUser.Update(data); | |||||
await TimedInvokeAsync(_selfUpdatedEvent, nameof(CurrentUserUpdated), before, CurrentUser).ConfigureAwait(false); | await TimedInvokeAsync(_selfUpdatedEvent, nameof(CurrentUserUpdated), before, CurrentUser).ConfigureAwait(false); | ||||
} | } | ||||
else | else | ||||
@@ -2277,7 +2277,7 @@ namespace Discord.WebSocket | |||||
: null; | : null; | ||||
SocketUser target = data.TargetUser.IsSpecified | SocketUser target = data.TargetUser.IsSpecified | ||||
? (guild.GetUser(data.TargetUser.Value.Id) ?? (SocketUser)SocketUnknownUser.Create(this, StateManager, data.TargetUser.Value)) | |||||
? (guild.GetUser(data.TargetUser.Value.Id) ?? (SocketUser)SocketUnknownUser.Create(this, data.TargetUser.Value)) | |||||
: null; | : null; | ||||
var invite = SocketInvite.Create(this, guild, channel, inviter, target, data); | var invite = SocketInvite.Create(this, guild, channel, inviter, target, data); | ||||
@@ -2332,7 +2332,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
SocketUser user = data.User.IsSpecified | SocketUser user = data.User.IsSpecified | ||||
? StateManager.GetOrAddUser(data.User.Value.Id, (_) => data.User.Value) | |||||
? await StateManager.UserStore.GetOrAddAsync(data.User.Value.Id, (_) => data.User.Value).ConfigureAwait(false) | |||||
: guild?.AddOrUpdateUser(data.Member.Value); // null if the bot scope isn't set, so the guild cannot be retrieved. | : guild?.AddOrUpdateUser(data.Member.Value); // null if the bot scope isn't set, so the guild cannot be retrieved. | ||||
SocketChannel channel = null; | SocketChannel channel = null; | ||||
@@ -2821,7 +2821,7 @@ namespace Discord.WebSocket | |||||
return; | return; | ||||
} | } | ||||
var user = (SocketUser)guild.GetUser(data.UserId) ?? StateManager.GetUser(data.UserId); | |||||
var user = (SocketUser)guild.GetUser(data.UserId) ?? StateManager.UserStore.Get(data.UserId); | |||||
var cacheableUser = new Cacheable<SocketUser, RestUser, IUser, ulong>(user, data.UserId, user != null, () => Rest.GetUserAsync(data.UserId)); | var cacheableUser = new Cacheable<SocketUser, RestUser, IUser, ulong>(user, data.UserId, user != null, () => Rest.GetUserAsync(data.UserId)); | ||||
@@ -2958,7 +2958,7 @@ namespace Discord.WebSocket | |||||
internal async Task<SocketGuild> AddGuildAsync(ExtendedGuild model) | internal async Task<SocketGuild> AddGuildAsync(ExtendedGuild model) | ||||
{ | { | ||||
await StateManager.InitializeGuildStoreAsync(model.Id).ConfigureAwait(false); | |||||
await StateManager.GetMemberStoreAsync(model.Id).ConfigureAwait(false); | |||||
var guild = SocketGuild.Create(this, StateManager, model); | var guild = SocketGuild.Create(this, StateManager, model); | ||||
StateManager.AddGuild(guild); | StateManager.AddGuild(guild); | ||||
if (model.Large) | if (model.Large) | ||||
@@ -43,7 +43,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
internal override void Update(ClientStateManager state, Model model) | internal override void Update(ClientStateManager state, Model model) | ||||
{ | { | ||||
Recipient.Update(state, model.Recipients.Value[0]); | |||||
Recipient.Update(model.Recipients.Value[0]); | |||||
} | } | ||||
internal static SocketDMChannel Create(DiscordSocketClient discord, ClientStateManager state, ulong channelId, API.User recipient) | internal static SocketDMChannel Create(DiscordSocketClient discord, ClientStateManager state, ulong channelId, API.User recipient) | ||||
{ | { | ||||
@@ -53,7 +53,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
internal void Update(ClientStateManager state, API.User recipient) | internal void Update(ClientStateManager state, API.User recipient) | ||||
{ | { | ||||
Recipient.Update(state, recipient); | |||||
Recipient.Update(recipient); | |||||
} | } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -77,7 +77,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
var users = new ConcurrentDictionary<ulong, SocketGroupUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(models.Length * 1.05)); | var users = new ConcurrentDictionary<ulong, SocketGroupUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(models.Length * 1.05)); | ||||
for (int i = 0; i < models.Length; i++) | for (int i = 0; i < models.Length; i++) | ||||
users[models[i].Id] = SocketGroupUser.Create(this, state, models[i]); | |||||
users[models[i].Id] = SocketGroupUser.Create(this, models[i]); | |||||
_users = users; | _users = users; | ||||
} | } | ||||
@@ -265,8 +265,7 @@ namespace Discord.WebSocket | |||||
return user; | return user; | ||||
else | else | ||||
{ | { | ||||
var privateUser = SocketGroupUser.Create(this, Discord.StateManager, model); | |||||
privateUser.GlobalUser.AddRef(); | |||||
var privateUser = SocketGroupUser.Create(this, model); | |||||
_users[privateUser.Id] = privateUser; | _users[privateUser.Id] = privateUser; | ||||
return privateUser; | return privateUser; | ||||
} | } | ||||
@@ -275,7 +274,6 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
if (_users.TryRemove(id, out SocketGroupUser user)) | if (_users.TryRemove(id, out SocketGroupUser user)) | ||||
{ | { | ||||
user.GlobalUser.RemoveRef(Discord); | |||||
return user; | return user; | ||||
} | } | ||||
return null; | return null; | ||||
@@ -171,7 +171,6 @@ namespace Discord.WebSocket | |||||
else | else | ||||
{ | { | ||||
member = SocketThreadUser.Create(Guild, this, model, guildMember); | member = SocketThreadUser.Create(Guild, this, model, guildMember); | ||||
member.GlobalUser.AddRef(); | |||||
_members[member.Id] = member; | _members[member.Id] = member; | ||||
} | } | ||||
return member; | return member; | ||||
@@ -305,7 +305,7 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// Gets the current logged-in user. | /// Gets the current logged-in user. | ||||
/// </summary> | /// </summary> | ||||
public SocketGuildUser CurrentUser => Discord.StateManager.GetMember(Discord.CurrentUser.Id, Id); | |||||
public SocketGuildUser CurrentUser => Discord.StateManager.TryGetMemberStore(Id, out var store) ? store.Get(Discord.CurrentUser.Id) : null; | |||||
/// <summary> | /// <summary> | ||||
/// Gets the built-in role containing all users in this guild. | /// Gets the built-in role containing all users in this guild. | ||||
/// </summary> | /// </summary> | ||||
@@ -356,7 +356,7 @@ namespace Discord.WebSocket | |||||
/// <returns> | /// <returns> | ||||
/// A collection of guild users found within this guild. | /// A collection of guild users found within this guild. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketGuildUser> Users => Discord.StateManager.GetMembers(Id).Cast<SocketGuildUser>().ToImmutableArray(); | |||||
public IReadOnlyCollection<SocketGuildUser> Users => Discord.StateManager.TryGetMemberStore(Id, out var store) ? store.GetAll().ToImmutableArray() : ImmutableArray<SocketGuildUser>.Empty; | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of all roles in this guild. | /// Gets a collection of all roles in this guild. | ||||
/// </summary> | /// </summary> | ||||
@@ -547,8 +547,12 @@ namespace Discord.WebSocket | |||||
internal async ValueTask UpdateCacheAsync(ExtendedModel model) | internal async ValueTask UpdateCacheAsync(ExtendedModel model) | ||||
{ | { | ||||
await Discord.StateManager.BulkAddOrUpdatePresenceAsync(model.Presences).ConfigureAwait(false); | |||||
await Discord.StateManager.BulkAddOrUpdateMembersAsync(Id, model.Members).ConfigureAwait(false); | |||||
await Discord.StateManager.PresenceStore.BulkAddOrUpdateAsync(model.Presences); | |||||
await Discord.StateManager.UserStore.BulkAddOrUpdateAsync(model.Members.Select(x => x.User)); | |||||
if(Discord.StateManager.TryGetMemberStore(Id, out var store)) | |||||
store.BulkAddOrUpdate(model.Members); | |||||
} | } | ||||
internal void Update(ClientStateManager state, EmojiUpdateModel model) | internal void Update(ClientStateManager state, EmojiUpdateModel model) | ||||
@@ -1055,7 +1059,7 @@ namespace Discord.WebSocket | |||||
/// A guild user associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found. | /// A guild user associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found. | ||||
/// </returns> | /// </returns> | ||||
public SocketGuildUser GetUser(ulong id) | public SocketGuildUser GetUser(ulong id) | ||||
=> Discord.StateManager.GetMember(id, Id); | |||||
=> Discord.StateManager.TryGetMemberStore(Id, out var store) ? store.Get(id) : null; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null, IEnumerable<ulong> includeRoleIds = null) | public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null, IEnumerable<ulong> includeRoleIds = null) | ||||
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options, includeRoleIds); | => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options, includeRoleIds); | ||||
@@ -1064,11 +1068,10 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
SocketGuildUser member; | SocketGuildUser member; | ||||
if ((member = GetUser(model.Id)) != null) | if ((member = GetUser(model.Id)) != null) | ||||
member.GlobalUser?.Update(Discord.StateManager, model); | |||||
member.Update(model); | |||||
else | else | ||||
{ | { | ||||
member = SocketGuildUser.Create(Id, Discord, model); | member = SocketGuildUser.Create(Id, Discord, model); | ||||
member.GlobalUser.AddRef(); | |||||
DownloadedMemberCount++; | DownloadedMemberCount++; | ||||
} | } | ||||
return member; | return member; | ||||
@@ -1076,12 +1079,11 @@ namespace Discord.WebSocket | |||||
internal SocketGuildUser AddOrUpdateUser(MemberModel model) | internal SocketGuildUser AddOrUpdateUser(MemberModel model) | ||||
{ | { | ||||
SocketGuildUser member; | SocketGuildUser member; | ||||
if ((member = GetUser(model.User.Id)) != null) | |||||
member.Update(Discord.StateManager, model); | |||||
if ((member = GetUser(model.Id)) != null) | |||||
member.Update(model); | |||||
else | else | ||||
{ | { | ||||
member = SocketGuildUser.Create(Id, Discord, model); | member = SocketGuildUser.Create(Id, Discord, model); | ||||
member.GlobalUser.AddRef(); | |||||
DownloadedMemberCount++; | DownloadedMemberCount++; | ||||
} | } | ||||
return member; | return member; | ||||
@@ -1092,8 +1094,8 @@ namespace Discord.WebSocket | |||||
if ((member = GetUser(id)) != null) | if ((member = GetUser(id)) != null) | ||||
{ | { | ||||
DownloadedMemberCount--; | DownloadedMemberCount--; | ||||
member.GlobalUser.RemoveRef(Discord); | |||||
Discord.StateManager.RemoveMember(id, Id); | |||||
if (Discord.StateManager.TryGetMemberStore(Id, out var store)) | |||||
store.Remove(id); | |||||
return member; | return member; | ||||
} | } | ||||
return null; | return null; | ||||
@@ -1114,8 +1116,9 @@ namespace Discord.WebSocket | |||||
var membersToPurge = users.Where(x => predicate.Invoke(x) && x?.Id != Discord.CurrentUser.Id); | var membersToPurge = users.Where(x => predicate.Invoke(x) && x?.Id != Discord.CurrentUser.Id); | ||||
var membersToKeep = users.Where(x => !predicate.Invoke(x) || x?.Id == Discord.CurrentUser.Id); | var membersToKeep = users.Where(x => !predicate.Invoke(x) || x?.Id == Discord.CurrentUser.Id); | ||||
foreach (var member in membersToPurge) | |||||
Discord.StateManager.RemoveMember(member.Id, Id); | |||||
if(Discord.StateManager.TryGetMemberStore(Id, out var store)) | |||||
foreach (var member in membersToPurge) | |||||
store.Remove(member.Id); | |||||
_downloaderPromise = new TaskCompletionSource<bool>(); | _downloaderPromise = new TaskCompletionSource<bool>(); | ||||
DownloadedMemberCount = membersToKeep.Count(); | DownloadedMemberCount = membersToKeep.Count(); | ||||
@@ -1240,7 +1243,6 @@ namespace Discord.WebSocket | |||||
/// in order to use this property. | /// in order to use this property. | ||||
/// </remarks> | /// </remarks> | ||||
/// </param> | /// </param> | ||||
/// <param name="speakers">A collection of speakers for the event.</param> | |||||
/// <param name="location">The location of the event; links are supported</param> | /// <param name="location">The location of the event; links are supported</param> | ||||
/// <param name="coverImage">The optional banner image for the event.</param> | /// <param name="coverImage">The optional banner image for the event.</param> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
@@ -89,7 +89,7 @@ namespace Discord.WebSocket | |||||
if(guildUser != null) | if(guildUser != null) | ||||
{ | { | ||||
if(model.Creator.IsSpecified) | if(model.Creator.IsSpecified) | ||||
guildUser.Update(Discord.StateManager, model.Creator.Value); | |||||
guildUser.Update(model.Creator.Value); | |||||
Creator = guildUser; | Creator = guildUser; | ||||
} | } | ||||
@@ -56,7 +56,7 @@ namespace Discord.WebSocket | |||||
if (Channel is SocketGuildChannel channel) | if (Channel is SocketGuildChannel channel) | ||||
{ | { | ||||
if (model.Message.Value.WebhookId.IsSpecified) | if (model.Message.Value.WebhookId.IsSpecified) | ||||
author = SocketWebhookUser.Create(channel.Guild, Discord.StateManager, model.Message.Value.Author.Value, model.Message.Value.WebhookId.Value); | |||||
author = SocketWebhookUser.Create(channel.Guild, model.Message.Value.Author.Value, model.Message.Value.WebhookId.Value); | |||||
else if (model.Message.Value.Author.IsSpecified) | else if (model.Message.Value.Author.IsSpecified) | ||||
author = channel.Guild.GetUser(model.Message.Value.Author.Value.Id); | author = channel.Guild.GetUser(model.Message.Value.Author.Value.Id); | ||||
} | } | ||||
@@ -88,7 +88,7 @@ namespace Discord.WebSocket | |||||
if (guild != null) | if (guild != null) | ||||
{ | { | ||||
if (msg.Value.WebhookId.IsSpecified) | if (msg.Value.WebhookId.IsSpecified) | ||||
author = SocketWebhookUser.Create(guild, discord.StateManager, msg.Value.Author.Value, msg.Value.WebhookId.Value); | |||||
author = SocketWebhookUser.Create(guild, msg.Value.Author.Value, msg.Value.WebhookId.Value); | |||||
else | else | ||||
author = guild.GetUser(msg.Value.Author.Value.Id); | author = guild.GetUser(msg.Value.Author.Value.Id); | ||||
} | } | ||||
@@ -251,7 +251,7 @@ namespace Discord.WebSocket | |||||
if (user != null) | if (user != null) | ||||
newMentions.Add(user); | newMentions.Add(user); | ||||
else | else | ||||
newMentions.Add(SocketUnknownUser.Create(Discord, state, val)); | |||||
newMentions.Add(SocketUnknownUser.Create(Discord, val)); | |||||
} | } | ||||
} | } | ||||
_userMentions = newMentions.ToImmutable(); | _userMentions = newMentions.ToImmutable(); | ||||
@@ -263,7 +263,7 @@ namespace Discord.WebSocket | |||||
Interaction = new MessageInteraction<SocketUser>(model.Interaction.Value.Id, | Interaction = new MessageInteraction<SocketUser>(model.Interaction.Value.Id, | ||||
model.Interaction.Value.Type, | model.Interaction.Value.Type, | ||||
model.Interaction.Value.Name, | model.Interaction.Value.Name, | ||||
SocketGlobalUser.Create(Discord, state, model.Interaction.Value.User)); | |||||
SocketGlobalUser.Create(Discord, model.Interaction.Value.User)); | |||||
} | } | ||||
if (model.Flags.IsSpecified) | if (model.Flags.IsSpecified) | ||||
@@ -122,14 +122,14 @@ namespace Discord.WebSocket | |||||
if (guild != null) | if (guild != null) | ||||
{ | { | ||||
if (webhookId != null) | if (webhookId != null) | ||||
refMsgAuthor = SocketWebhookUser.Create(guild, state, refMsg.Author.Value, webhookId.Value); | |||||
refMsgAuthor = SocketWebhookUser.Create(guild, refMsg.Author.Value, webhookId.Value); | |||||
else | else | ||||
refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id); | refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id); | ||||
} | } | ||||
else | else | ||||
refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id); | refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id); | ||||
if (refMsgAuthor == null) | if (refMsgAuthor == null) | ||||
refMsgAuthor = SocketUnknownUser.Create(Discord, state, refMsg.Author.Value); | |||||
refMsgAuthor = SocketUnknownUser.Create(Discord, refMsg.Author.Value); | |||||
} | } | ||||
else | else | ||||
// Message author wasn't specified in the payload, so create a completely anonymous unknown user | // Message author wasn't specified in the payload, so create a completely anonymous unknown user | ||||
@@ -26,11 +26,11 @@ namespace Discord.WebSocket | |||||
return entity; | return entity; | ||||
} | } | ||||
~SocketGlobalUser() => Discord.StateManager.RemoveReferencedGlobalUser(Id); | |||||
~SocketGlobalUser() => Dispose(); | |||||
public override void Dispose() | public override void Dispose() | ||||
{ | { | ||||
GC.SuppressFinalize(this); | GC.SuppressFinalize(this); | ||||
Discord.StateManager.RemoveReferencedGlobalUser(Id); | |||||
Discord.StateManager.UserStore.RemoveReference(Id); | |||||
} | } | ||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; | private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; | ||||
@@ -28,7 +28,7 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// Gets the guild the user is in. | /// Gets the guild the user is in. | ||||
/// </summary> | /// </summary> | ||||
public Lazy<SocketGuild> Guild { get; } | |||||
public Lazy<SocketGuild> Guild { get; } // TODO: convert to LazyCached once guilds are cached. | |||||
/// <summary> | /// <summary> | ||||
/// Gets the guilds id that the user is in. | /// Gets the guilds id that the user is in. | ||||
/// </summary> | /// </summary> | ||||
@@ -146,7 +146,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
var entity = new SocketGuildUser(guildId, model.Id, client); | var entity = new SocketGuildUser(guildId, model.Id, client); | ||||
if (entity.Update(model)) | if (entity.Update(model)) | ||||
client.StateManager.AddOrUpdateMember(guildId, entity.ToModel()); | |||||
client.StateManager.GetMemberStore(guildId)?.AddOrUpdate(entity.ToModel()); | |||||
entity.UpdateRoles(Array.Empty<ulong>()); | entity.UpdateRoles(Array.Empty<ulong>()); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -154,7 +154,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
var entity = new SocketGuildUser(guildId, model.Id, client); | var entity = new SocketGuildUser(guildId, model.Id, client); | ||||
entity.Update(model); | entity.Update(model); | ||||
client.StateManager.AddOrUpdateMember(guildId, model); | |||||
client.StateManager.GetMemberStore(guildId)?.AddOrUpdate(model); | |||||
return entity; | return entity; | ||||
} | } | ||||
internal void Update(MemberModel model) | internal void Update(MemberModel model) | ||||
@@ -301,9 +301,9 @@ namespace Discord.WebSocket | |||||
public override void Dispose() | public override void Dispose() | ||||
{ | { | ||||
GC.SuppressFinalize(this); | GC.SuppressFinalize(this); | ||||
Discord.StateManager.RemovedReferencedMember(Id, _guildId); | |||||
Discord.StateManager.GetMemberStore(_guildId)?.RemoveReference(Id); | |||||
} | } | ||||
~SocketGuildUser() => Discord.StateManager.RemovedReferencedMember(Id, _guildId); | |||||
~SocketGuildUser() => Dispose(); | |||||
#endregion | #endregion | ||||
} | } | ||||
@@ -15,6 +15,8 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
internal ulong UserId; | internal ulong UserId; | ||||
internal ulong? GuildId; | internal ulong? GuildId; | ||||
internal bool IsFreed; | |||||
internal DiscordSocketClient Discord; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public UserStatus Status { get; private set; } | public UserStatus Status { get; private set; } | ||||
@@ -23,17 +25,24 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<IActivity> Activities { get; private set; } | public IReadOnlyCollection<IActivity> Activities { get; private set; } | ||||
internal SocketPresence() { } | |||||
internal SocketPresence(UserStatus status, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities) | |||||
public static SocketPresence Default | |||||
=> new SocketPresence(null, UserStatus.Offline, null, null); | |||||
internal SocketPresence(DiscordSocketClient discord) | |||||
{ | |||||
Discord = discord; | |||||
} | |||||
internal SocketPresence(DiscordSocketClient discord, UserStatus status, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities) | |||||
: this(discord) | |||||
{ | { | ||||
Status = status; | Status = status; | ||||
ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty; | ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty; | ||||
Activities = activities ?? ImmutableList<IActivity>.Empty; | Activities = activities ?? ImmutableList<IActivity>.Empty; | ||||
} | } | ||||
internal static SocketPresence Create(Model model) | |||||
internal static SocketPresence Create(DiscordSocketClient client, Model model) | |||||
{ | { | ||||
var entity = new SocketPresence(); | |||||
var entity = new SocketPresence(client); | |||||
entity.Update(model); | entity.Update(model); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -102,6 +111,22 @@ namespace Discord.WebSocket | |||||
internal SocketPresence Clone() => MemberwiseClone() as SocketPresence; | internal SocketPresence Clone() => MemberwiseClone() as SocketPresence; | ||||
~SocketPresence() => Dispose(); | |||||
public void Dispose() | |||||
{ | |||||
if (IsFreed) | |||||
return; | |||||
GC.SuppressFinalize(this); | |||||
if(Discord != null) | |||||
{ | |||||
Discord.StateManager.PresenceStore.RemoveReference(UserId); | |||||
IsFreed = true; | |||||
} | |||||
} | |||||
#region Cache | #region Cache | ||||
private struct CacheModel : Model | private struct CacheModel : Model | ||||
{ | { | ||||
@@ -205,6 +230,7 @@ namespace Discord.WebSocket | |||||
Model ICached<Model>.ToModel() => ToModel(); | Model ICached<Model>.ToModel() => ToModel(); | ||||
TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | ||||
void ICached<Model>.Update(Model model) => Update(model); | void ICached<Model>.Update(Model model) => Update(model); | ||||
bool ICached.IsFreed => IsFreed; | |||||
#endregion | #endregion | ||||
} | } | ||||
@@ -19,17 +19,6 @@ namespace Discord.WebSocket | |||||
public bool IsVerified { get; private set; } | public bool IsVerified { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public bool IsMfaEnabled { get; private set; } | public bool IsMfaEnabled { get; private set; } | ||||
/// <inheritdoc /> | |||||
public override bool IsBot { get { return GlobalUser.Value.IsBot; } internal set { GlobalUser.Value.IsBot = value; } } | |||||
/// <inheritdoc /> | |||||
public override string Username { get { return GlobalUser.Value.Username; } internal set { GlobalUser.Value.Username = value; } } | |||||
/// <inheritdoc /> | |||||
public override ushort DiscriminatorValue { get { return GlobalUser.Value.DiscriminatorValue; } internal set { GlobalUser.Value.DiscriminatorValue = value; } } | |||||
/// <inheritdoc /> | |||||
public override string AvatarId { get { return GlobalUser.Value.AvatarId; } internal set { GlobalUser.Value.AvatarId = value; } } | |||||
/// <inheritdoc /> | |||||
internal override Lazy<SocketPresence> Presence { get { return GlobalUser.Value.Presence; } set { GlobalUser.Value.Presence = value; } } | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public UserProperties Flags { get; internal set; } | public UserProperties Flags { get; internal set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -99,8 +88,12 @@ namespace Discord.WebSocket | |||||
internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; | internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; | ||||
public override void Dispose() | public override void Dispose() | ||||
{ | { | ||||
if (IsFreed) | |||||
return; | |||||
GC.SuppressFinalize(this); | GC.SuppressFinalize(this); | ||||
Discord.StateManager.RemoveReferencedGlobalUser(Id); | |||||
Discord.StateManager.UserStore.RemoveReference(Id); | |||||
IsFreed = true; | |||||
} | } | ||||
#region Cache | #region Cache | ||||
@@ -238,7 +238,7 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => GuildUser.Value.GetGuildAvatarUrl(format, size); | string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => GuildUser.Value.GetGuildAvatarUrl(format, size); | ||||
internal override Lazy<SocketPresence> Presence { get => GuildUser.Value.Presence; set => GuildUser.Value.Presence = value; } | |||||
internal override LazyCached<SocketPresence> Presence { get => GuildUser.Value.Presence; set => GuildUser.Value.Presence = value; } | |||||
public override void Dispose() | public override void Dispose() | ||||
{ | { | ||||
@@ -26,8 +26,8 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override bool IsWebhook => false; | public override bool IsWebhook => false; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
internal override Lazy<SocketPresence> Presence { get { return new Lazy<SocketPresence>(() => new SocketPresence(UserStatus.Offline, null, null)); } set { } } | |||||
internal override Lazy<SocketGlobalUser> GlobalUser { get => new Lazy<SocketGlobalUser>(() => null); set { } } | |||||
internal override LazyCached<SocketPresence> Presence { get { return new(SocketPresence.Default); } set { } } | |||||
internal override LazyCached<SocketGlobalUser> GlobalUser { get => new(null); set { } } | |||||
internal SocketUnknownUser(DiscordSocketClient discord, ulong id) | internal SocketUnknownUser(DiscordSocketClient discord, ulong id) | ||||
: base(discord, id) | : base(discord, id) | ||||
@@ -15,7 +15,7 @@ namespace Discord.WebSocket | |||||
/// Represents a WebSocket-based user. | /// Represents a WebSocket-based user. | ||||
/// </summary> | /// </summary> | ||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
public abstract class SocketUser : SocketEntity<ulong>, IUser, ICached<Model>, IDisposable | |||||
public abstract class SocketUser : SocketEntity<ulong>, IUser, ICached<Model> | |||||
{ | { | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public virtual bool IsBot { get; internal set; } | public virtual bool IsBot { get; internal set; } | ||||
@@ -29,9 +29,9 @@ namespace Discord.WebSocket | |||||
public virtual bool IsWebhook { get; } | public virtual bool IsWebhook { get; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public UserProperties? PublicFlags { get; private set; } | public UserProperties? PublicFlags { get; private set; } | ||||
internal virtual Lazy<SocketGlobalUser> GlobalUser { get; set; } | |||||
internal virtual Lazy<SocketPresence> Presence { get; set; } | |||||
internal virtual LazyCached<SocketGlobalUser> GlobalUser { get; set; } | |||||
internal virtual LazyCached<SocketPresence> Presence { get; set; } | |||||
internal bool IsFreed { get; set; } | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -56,11 +56,11 @@ namespace Discord.WebSocket | |||||
internal SocketUser(DiscordSocketClient discord, ulong id) | internal SocketUser(DiscordSocketClient discord, ulong id) | ||||
: base(discord, id) | : base(discord, id) | ||||
{ | { | ||||
Presence = new LazyCached<SocketPresence>(id, discord.StateManager.PresenceStore); | |||||
GlobalUser = new LazyCached<SocketGlobalUser>(id, discord.StateManager.UserStore); | |||||
} | } | ||||
internal virtual bool Update(Model model) | internal virtual bool Update(Model model) | ||||
{ | { | ||||
Presence ??= new Lazy<SocketPresence>(() => Discord.StateManager.GetPresence(Id), System.Threading.LazyThreadSafetyMode.PublicationOnly); | |||||
GlobalUser ??= new Lazy<SocketGlobalUser>(() => Discord.StateManager.GetUser(Id), System.Threading.LazyThreadSafetyMode.PublicationOnly); | |||||
bool hasChanges = false; | bool hasChanges = false; | ||||
if (model.Avatar != AvatarId) | if (model.Avatar != AvatarId) | ||||
{ | { | ||||
@@ -124,7 +124,7 @@ namespace Discord.WebSocket | |||||
internal SocketUser Clone() => MemberwiseClone() as SocketUser; | internal SocketUser Clone() => MemberwiseClone() as SocketUser; | ||||
#region Cache | #region Cache | ||||
private struct CacheModel : Model | |||||
private class CacheModel : Model | |||||
{ | { | ||||
public string Username { get; set; } | public string Username { get; set; } | ||||
@@ -160,6 +160,8 @@ namespace Discord.WebSocket | |||||
void ICached<Model>.Update(Model model) => Update(model); | void ICached<Model>.Update(Model model) => Update(model); | ||||
bool ICached.IsFreed => IsFreed; | |||||
#endregion | #endregion | ||||
} | } | ||||
} | } |
@@ -33,8 +33,8 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override bool IsWebhook => true; | public override bool IsWebhook => true; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
internal override Lazy<SocketPresence> Presence { get { return new Lazy<SocketPresence>(() => new SocketPresence(UserStatus.Offline, null, null)); } set { } } | |||||
internal override Lazy<SocketGlobalUser> GlobalUser { get => new Lazy<SocketGlobalUser>(() => null); set { } } | |||||
internal override LazyCached<SocketPresence> Presence { get { return new(SocketPresence.Default); } set { } } | |||||
internal override LazyCached<SocketGlobalUser> GlobalUser { get => new(null); set { } } | |||||
internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) | internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) | ||||
: base(guild.Discord, id) | : base(guild.Discord, id) | ||||