| @@ -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) | ||||