| @@ -11,8 +11,6 @@ namespace Discord | |||
| void Update(TType model); | |||
| TType ToModel(); | |||
| TResult ToModel<TResult>() where TResult : TType, new(); | |||
| } | |||
| public interface ICached | |||
| @@ -12,7 +12,7 @@ namespace Discord | |||
| string Nickname { get; set; } | |||
| string GuildAvatar { get; set; } | |||
| ulong[] Roles { get; set; } | |||
| DateTimeOffset JoinedAt { get; set; } | |||
| DateTimeOffset? JoinedAt { get; set; } | |||
| DateTimeOffset? PremiumSince { get; set; } | |||
| bool IsDeaf { get; set; } | |||
| bool IsMute { get; set; } | |||
| @@ -9,7 +9,6 @@ namespace Discord | |||
| public interface IThreadMemberModel : IEntityModel<ulong> | |||
| { | |||
| ulong? ThreadId { get; set; } | |||
| ulong? UserId { get; set; } | |||
| DateTimeOffset JoinedAt { get; set; } | |||
| } | |||
| } | |||
| @@ -39,8 +39,8 @@ namespace Discord.API | |||
| get => Roles.GetValueOrDefault(Array.Empty<ulong>()); set => throw new NotSupportedException(); | |||
| } | |||
| DateTimeOffset IMemberModel.JoinedAt { | |||
| get => JoinedAt.GetValueOrDefault(); set => throw new NotSupportedException(); | |||
| DateTimeOffset? IMemberModel.JoinedAt { | |||
| get => JoinedAt.ToNullable(); set => throw new NotSupportedException(); | |||
| } | |||
| DateTimeOffset? IMemberModel.PremiumSince { | |||
| @@ -15,7 +15,6 @@ namespace Discord.API | |||
| public DateTimeOffset JoinTimestamp { get; set; } | |||
| ulong? IThreadMemberModel.ThreadId { get => ThreadId.ToNullable(); set => throw new NotSupportedException(); } | |||
| ulong? IThreadMemberModel.UserId { get => UserId.ToNullable(); set => throw new NotSupportedException(); } | |||
| DateTimeOffset IThreadMemberModel.JoinedAt { get => JoinTimestamp; set => throw new NotSupportedException(); } | |||
| ulong IEntityModel<ulong>.Id { get => UserId.GetValueOrDefault(0); set => throw new NotSupportedException(); } | |||
| } | |||
| @@ -1,21 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public enum CacheRunMode | |||
| { | |||
| /// <summary> | |||
| /// The cache should preform a synchronous cache lookup. | |||
| /// </summary> | |||
| Sync, | |||
| /// <summary> | |||
| /// The cache should preform either a <see cref="Sync"/> or asynchronous cache lookup. | |||
| /// </summary> | |||
| Async | |||
| } | |||
| } | |||
| @@ -12,6 +12,16 @@ namespace Discord.WebSocket | |||
| private readonly ConcurrentDictionary<Type, object> _storeCache = new(); | |||
| private readonly ConcurrentDictionary<object, object> _subStoreCache = new(); | |||
| private readonly Dictionary<Type, Type> _models = new() | |||
| { | |||
| { typeof(IUserModel), typeof(API.User) }, | |||
| { typeof(ICurrentUserModel), typeof(API.CurrentUser) }, | |||
| { typeof(IMemberModel), typeof(API.GuildMember) }, | |||
| { typeof(IThreadMemberModel), typeof(API.ThreadMember)}, | |||
| { typeof(IPresenceModel), typeof(API.Presence)}, | |||
| { typeof(IActivityModel), typeof(API.Game)} | |||
| }; | |||
| private class DefaultEntityStore<TModel, TId> : IEntityStore<TModel, TId> | |||
| where TModel : IEntityModel<TId> | |||
| where TId : IEquatable<TId> | |||
| @@ -23,44 +33,72 @@ namespace Discord.WebSocket | |||
| _cache = cache; | |||
| } | |||
| public ValueTask AddOrUpdateAsync(TModel model, CacheRunMode runmode) | |||
| public TModel Get(TId id) | |||
| { | |||
| _cache.AddOrUpdate(model.Id, model, (_, __) => model); | |||
| if (_cache.TryGetValue(id, out var model)) | |||
| return model; | |||
| return default; | |||
| } | |||
| public ValueTask AddOrUpdateBatchAsync(IEnumerable<TModel> models, CacheRunMode runmode) | |||
| public IEnumerable<TModel> GetAll() | |||
| { | |||
| return _cache.Select(x => x.Value); | |||
| } | |||
| public void AddOrUpdate(TModel model) | |||
| { | |||
| _cache.AddOrUpdate(model.Id, model, (_, __) => model); | |||
| } | |||
| public void AddOrUpdateBatch(IEnumerable<TModel> models) | |||
| { | |||
| foreach (var model in models) | |||
| _cache.AddOrUpdate(model.Id, model, (_, __) => model); | |||
| return default; | |||
| } | |||
| public void Remove(TId id) | |||
| { | |||
| _cache.TryRemove(id, out _); | |||
| } | |||
| public void PurgeAll() | |||
| { | |||
| _cache.Clear(); | |||
| } | |||
| public IAsyncEnumerable<TModel> GetAllAsync(CacheRunMode runmode) | |||
| ValueTask<TModel> IEntityStore<TModel, TId>.GetAsync(TId id) => new ValueTask<TModel>(Get(id)); | |||
| IAsyncEnumerable<TModel> IEntityStore<TModel, TId>.GetAllAsync() | |||
| { | |||
| var coll = _cache.Select(x => x.Value).GetEnumerator(); | |||
| return AsyncEnumerable.Create((_) => AsyncEnumerator.Create( | |||
| () => new ValueTask<bool>(coll.MoveNext()), | |||
| () => coll.Current, | |||
| () => new ValueTask())); | |||
| var enumerator = GetAll().GetEnumerator(); | |||
| return AsyncEnumerable.Create((cancellationToken) | |||
| => AsyncEnumerator.Create( | |||
| () => new ValueTask<bool>(enumerator.MoveNext()), | |||
| () => enumerator.Current, | |||
| () => new ValueTask()) | |||
| ); | |||
| } | |||
| public ValueTask<TModel> GetAsync(TId id, CacheRunMode runmode) | |||
| ValueTask IEntityStore<TModel, TId>.AddOrUpdateAsync(TModel model) | |||
| { | |||
| if (_cache.TryGetValue(id, out var model)) | |||
| return new ValueTask<TModel>(model); | |||
| AddOrUpdate(model); | |||
| return default; | |||
| } | |||
| public ValueTask RemoveAsync(TId id, CacheRunMode runmode) | |||
| ValueTask IEntityStore<TModel, TId>.AddOrUpdateBatchAsync(IEnumerable<TModel> models) | |||
| { | |||
| _cache.TryRemove(id, out _); | |||
| AddOrUpdateBatch(models); | |||
| return default; | |||
| } | |||
| public ValueTask PurgeAllAsync(CacheRunMode runmode) | |||
| ValueTask IEntityStore<TModel, TId>.RemoveAsync(TId id) | |||
| { | |||
| _cache.Clear(); | |||
| Remove(id); | |||
| return default; | |||
| } | |||
| ValueTask IEntityStore<TModel, TId>.PurgeAllAsync() | |||
| { | |||
| PurgeAll(); | |||
| return default; | |||
| } | |||
| } | |||
| public Type GetModel<TInterface>() | |||
| { | |||
| if (_models.TryGetValue(typeof(TInterface), out var t)) | |||
| return t; | |||
| return null; | |||
| } | |||
| public virtual ValueTask<IEntityStore<TModel, TId>> GetStoreAsync<TModel, TId>() | |||
| @@ -8,6 +8,8 @@ namespace Discord.WebSocket | |||
| { | |||
| public interface ICacheProvider | |||
| { | |||
| Type GetModel<TModelInterface>(); | |||
| ValueTask<IEntityStore<TModel, TId>> GetStoreAsync<TModel, TId>() | |||
| where TModel : IEntityModel<TId> | |||
| where TId : IEquatable<TId>; | |||
| @@ -21,11 +23,17 @@ namespace Discord.WebSocket | |||
| where TModel : IEntityModel<TId> | |||
| where TId : IEquatable<TId> | |||
| { | |||
| ValueTask<TModel> GetAsync(TId id, CacheRunMode runmode); | |||
| IAsyncEnumerable<TModel> GetAllAsync(CacheRunMode runmode); | |||
| ValueTask AddOrUpdateAsync(TModel model, CacheRunMode runmode); | |||
| ValueTask AddOrUpdateBatchAsync(IEnumerable<TModel> models, CacheRunMode runmode); | |||
| ValueTask RemoveAsync(TId id, CacheRunMode runmode); | |||
| ValueTask PurgeAllAsync(CacheRunMode runmode); | |||
| ValueTask<TModel> GetAsync(TId id); | |||
| TModel Get(TId id); | |||
| IAsyncEnumerable<TModel> GetAllAsync(); | |||
| IEnumerable<TModel> GetAll(); | |||
| ValueTask AddOrUpdateAsync(TModel model); | |||
| void AddOrUpdate(TModel model); | |||
| ValueTask AddOrUpdateBatchAsync(IEnumerable<TModel> models); | |||
| void AddOrUpdateBatch(IEnumerable<TModel> models); | |||
| ValueTask RemoveAsync(TId id); | |||
| void Remove(TId id); | |||
| ValueTask PurgeAllAsync(); | |||
| void PurgeAll(); | |||
| } | |||
| } | |||
| @@ -92,28 +92,6 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| private TResult RunOrThrowValueTask<TResult>(ValueTask<TResult> t) | |||
| { | |||
| if (_allowSyncWaits) | |||
| { | |||
| return t.GetAwaiter().GetResult(); | |||
| } | |||
| else if (t.IsCompleted) | |||
| return t.Result; | |||
| else | |||
| throw new InvalidOperationException("Cannot run asynchronous value task in synchronous context"); | |||
| } | |||
| private void RunOrThrowValueTask(ValueTask t) | |||
| { | |||
| if (_allowSyncWaits) | |||
| { | |||
| t.GetAwaiter().GetResult(); | |||
| } | |||
| else if (!t.IsCompleted) | |||
| throw new InvalidOperationException("Cannot run asynchronous value task in synchronous context"); | |||
| } | |||
| public async ValueTask InitializeAsync() | |||
| { | |||
| _store ??= await _cacheProvider.GetStoreAsync<TModel, TId>().ConfigureAwait(false); | |||
| @@ -137,7 +115,7 @@ namespace Discord.WebSocket | |||
| return entity; | |||
| } | |||
| var model = RunOrThrowValueTask(_store.GetAsync(id, CacheRunMode.Sync)); | |||
| var model = _store.Get(id); | |||
| if (model != null) | |||
| { | |||
| @@ -156,7 +134,7 @@ namespace Discord.WebSocket | |||
| return entity; | |||
| } | |||
| var model = await _store.GetAsync(id, CacheRunMode.Async).ConfigureAwait(false); | |||
| var model = await _store.GetAsync(id).ConfigureAwait(false); | |||
| if (model != null) | |||
| { | |||
| @@ -175,7 +153,7 @@ namespace Discord.WebSocket | |||
| public IEnumerable<TEntity> GetAll() | |||
| { | |||
| var models = RunOrThrowValueTask(_store.GetAllAsync(CacheRunMode.Sync).ToArrayAsync()); | |||
| var models = _store.GetAll(); | |||
| return models.Select(x => | |||
| { | |||
| var entity = _entityBuilder(x); | |||
| @@ -186,7 +164,7 @@ namespace Discord.WebSocket | |||
| public async IAsyncEnumerable<TEntity> GetAllAsync() | |||
| { | |||
| await foreach(var model in _store.GetAllAsync(CacheRunMode.Async)) | |||
| await foreach(var model in _store.GetAllAsync()) | |||
| { | |||
| var entity = _entityBuilder(model); | |||
| _references.TryAdd(model.Id, new CacheReference<TEntity>(entity)); | |||
| @@ -212,13 +190,13 @@ namespace Discord.WebSocket | |||
| return (TEntity)entity; | |||
| var model = valueFactory(id); | |||
| await AddOrUpdateAsync(model); | |||
| await AddOrUpdateAsync(model).ConfigureAwait(false); | |||
| return _entityBuilder(model); | |||
| } | |||
| public void AddOrUpdate(TModel model) | |||
| { | |||
| RunOrThrowValueTask(_store.AddOrUpdateAsync(model, CacheRunMode.Sync)); | |||
| _store.AddOrUpdate(model); | |||
| if (TryGetReference(model.Id, out var reference)) | |||
| reference.Update(model); | |||
| } | |||
| @@ -227,14 +205,13 @@ namespace Discord.WebSocket | |||
| { | |||
| if (TryGetReference(model.Id, out var reference)) | |||
| reference.Update(model); | |||
| return _store.AddOrUpdateAsync(model, CacheRunMode.Async); | |||
| return _store.AddOrUpdateAsync(model); | |||
| } | |||
| public void BulkAddOrUpdate(IEnumerable<TModel> models) | |||
| { | |||
| RunOrThrowValueTask(_store.AddOrUpdateBatchAsync(models, CacheRunMode.Sync)); | |||
| foreach(var model in models) | |||
| _store.AddOrUpdateBatch(models); | |||
| foreach (var model in models) | |||
| { | |||
| if (_references.TryGetValue(model.Id, out var rf) && rf.Reference.TryGetTarget(out var entity)) | |||
| entity.Update(model); | |||
| @@ -243,7 +220,7 @@ namespace Discord.WebSocket | |||
| public async ValueTask BulkAddOrUpdateAsync(IEnumerable<TModel> models) | |||
| { | |||
| await _store.AddOrUpdateBatchAsync(models, CacheRunMode.Async).ConfigureAwait(false); | |||
| await _store.AddOrUpdateBatchAsync(models).ConfigureAwait(false); | |||
| foreach (var model in models) | |||
| { | |||
| @@ -254,26 +231,26 @@ namespace Discord.WebSocket | |||
| public void Remove(TId id) | |||
| { | |||
| RunOrThrowValueTask(_store.RemoveAsync(id, CacheRunMode.Sync)); | |||
| _store.Remove(id); | |||
| _references.TryRemove(id, out _); | |||
| } | |||
| public ValueTask RemoveAsync(TId id) | |||
| { | |||
| _references.TryRemove(id, out _); | |||
| return _store.RemoveAsync(id, CacheRunMode.Async); | |||
| return _store.RemoveAsync(id); | |||
| } | |||
| public void Purge() | |||
| { | |||
| RunOrThrowValueTask(_store.PurgeAllAsync(CacheRunMode.Sync)); | |||
| _store.PurgeAll(); | |||
| _references.Clear(); | |||
| } | |||
| public ValueTask PurgeAsync() | |||
| { | |||
| _references.Clear(); | |||
| return _store.PurgeAllAsync(CacheRunMode.Async); | |||
| return _store.PurgeAllAsync(); | |||
| } | |||
| TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id); | |||
| @@ -380,5 +357,24 @@ namespace Discord.WebSocket | |||
| _threadMemberLock.Release(); | |||
| } | |||
| } | |||
| public ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser> GetThreadMemberStore(ulong threadId) | |||
| => _threadMemberStores.TryGetValue(threadId, out var store) ? store : null; | |||
| public TModel GetModel<TModel, TFallback>() | |||
| where TFallback : class, TModel, new() | |||
| { | |||
| var type = _cacheProvider.GetModel<TModel>(); | |||
| if (type != null) | |||
| { | |||
| if (!type.GetInterfaces().Contains(typeof(TModel))) | |||
| throw new InvalidOperationException($"Cannot use {type.Name} as a model for {typeof(TModel).Name}"); | |||
| return (TModel)Activator.CreateInstance(type); | |||
| } | |||
| else | |||
| return new TFallback(); | |||
| } | |||
| } | |||
| } | |||
| @@ -74,7 +74,6 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public bool? IsPending { get; private set; } | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | |||
| /// <summary> | |||
| @@ -159,7 +158,7 @@ namespace Discord.WebSocket | |||
| } | |||
| internal void Update(MemberModel model) | |||
| { | |||
| _joinedAtTicks = model.JoinedAt.UtcTicks; | |||
| _joinedAtTicks = model.JoinedAt.HasValue ? model.JoinedAt.Value.UtcTicks : null; | |||
| Nickname = model.Nickname; | |||
| GuildAvatarId = model.GuildAvatar; | |||
| UpdateRoles(model.Roles); | |||
| @@ -232,6 +231,17 @@ namespace Discord.WebSocket | |||
| internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; | |||
| public override void Dispose() | |||
| { | |||
| if (IsFreed) | |||
| return; | |||
| GC.SuppressFinalize(this); | |||
| Discord.StateManager.GetMemberStore(_guildId)?.RemoveReference(Id); | |||
| IsFreed = true; | |||
| } | |||
| ~SocketGuildUser() => Dispose(); | |||
| #endregion | |||
| #region IGuildUser | |||
| @@ -249,7 +259,7 @@ namespace Discord.WebSocket | |||
| #region Cache | |||
| private struct CacheModel : MemberModel | |||
| private class CacheModel : MemberModel | |||
| { | |||
| public ulong Id { get; set; } | |||
| public string Nickname { get; set; } | |||
| @@ -258,7 +268,7 @@ namespace Discord.WebSocket | |||
| public ulong[] Roles { get; set; } | |||
| public DateTimeOffset JoinedAt { get; set; } | |||
| public DateTimeOffset? JoinedAt { get; set; } | |||
| public DateTimeOffset? PremiumSince { get; set; } | |||
| @@ -271,40 +281,25 @@ namespace Discord.WebSocket | |||
| public DateTimeOffset? CommunicationsDisabledUntil { get; set; } | |||
| } | |||
| internal new MemberModel ToModel() | |||
| => ToModel<CacheModel>(); | |||
| internal new TModel ToModel<TModel>() where TModel : MemberModel, new() | |||
| { | |||
| return new TModel | |||
| { | |||
| Id = Id, | |||
| CommunicationsDisabledUntil = TimedOutUntil, | |||
| GuildAvatar = GuildAvatarId, | |||
| IsDeaf = IsDeafened, | |||
| IsMute = IsMuted, | |||
| IsPending = IsPending, | |||
| JoinedAt = JoinedAt ?? DateTimeOffset.UtcNow, // review: nullable joined at here? should our model reflect this? | |||
| Nickname = Nickname, | |||
| PremiumSince = PremiumSince, | |||
| Roles = _roleIds.ToArray() | |||
| }; | |||
| var model = Discord.StateManager.GetModel<MemberModel, CacheModel>(); | |||
| model.Id = Id; | |||
| model.Nickname = Nickname; | |||
| model.GuildAvatar = GuildAvatarId; | |||
| model.Roles = _roleIds.ToArray(); | |||
| model.JoinedAt = JoinedAt; | |||
| model.PremiumSince = PremiumSince; | |||
| model.IsDeaf = IsDeafened; | |||
| model.IsMute = IsMuted; | |||
| model.IsPending = IsPending; | |||
| model.CommunicationsDisabledUntil = TimedOutUntil; | |||
| return model; | |||
| } | |||
| MemberModel ICached<MemberModel>.ToModel() | |||
| => ToModel(); | |||
| TResult ICached<MemberModel>.ToModel<TResult>() | |||
| => ToModel<TResult>(); | |||
| void ICached<MemberModel>.Update(MemberModel model) => Update(model); | |||
| public override void Dispose() | |||
| { | |||
| GC.SuppressFinalize(this); | |||
| Discord.StateManager.GetMemberStore(_guildId)?.RemoveReference(Id); | |||
| } | |||
| ~SocketGuildUser() => Dispose(); | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -112,7 +112,6 @@ namespace Discord.WebSocket | |||
| internal SocketPresence Clone() => MemberwiseClone() as SocketPresence; | |||
| ~SocketPresence() => Dispose(); | |||
| public void Dispose() | |||
| { | |||
| if (IsFreed) | |||
| @@ -128,7 +127,7 @@ namespace Discord.WebSocket | |||
| } | |||
| #region Cache | |||
| private struct CacheModel : Model | |||
| private class CacheModel : Model | |||
| { | |||
| public UserStatus Status { get; set; } | |||
| @@ -187,48 +186,43 @@ namespace Discord.WebSocket | |||
| } | |||
| internal Model ToModel() | |||
| => ToModel<CacheModel>(); | |||
| internal TModel ToModel<TModel>() where TModel : Model, new() | |||
| { | |||
| return new TModel | |||
| var model = Discord.StateManager.GetModel<Model, CacheModel>(); | |||
| model.Status = Status; | |||
| model.ActiveClients = ActiveClients.ToArray(); | |||
| model.UserId = UserId; | |||
| model.GuildId = GuildId; | |||
| model.Activities = Activities.Select(x => | |||
| { | |||
| Status = Status, | |||
| ActiveClients = ActiveClients.ToArray(), | |||
| UserId = UserId, | |||
| GuildId = GuildId, | |||
| Activities = Activities.Select(x => | |||
| switch (x) | |||
| { | |||
| case Game game: | |||
| switch (game) | |||
| { | |||
| case RichGame richGame: | |||
| return richGame.ToModel<ActivityCacheModel>(); | |||
| case SpotifyGame spotify: | |||
| return spotify.ToModel<ActivityCacheModel>(); | |||
| case CustomStatusGame custom: | |||
| return custom.ToModel<ActivityCacheModel, EmojiCacheModel>(); | |||
| case StreamingGame stream: | |||
| return stream.ToModel<ActivityCacheModel>(); | |||
| } | |||
| break; | |||
| } | |||
| return new ActivityCacheModel | |||
| { | |||
| switch (x) | |||
| { | |||
| case Game game: | |||
| switch (game) | |||
| { | |||
| case RichGame richGame: | |||
| return richGame.ToModel<ActivityCacheModel>(); | |||
| case SpotifyGame spotify: | |||
| return spotify.ToModel<ActivityCacheModel>(); | |||
| case CustomStatusGame custom: | |||
| return custom.ToModel<ActivityCacheModel, EmojiCacheModel>(); | |||
| case StreamingGame stream: | |||
| return stream.ToModel<ActivityCacheModel>(); | |||
| } | |||
| break; | |||
| } | |||
| return new ActivityCacheModel | |||
| { | |||
| Name = x.Name, | |||
| Details = x.Details, | |||
| Flags = x.Flags, | |||
| Type = x.Type | |||
| }; | |||
| }).ToArray(), | |||
| }; | |||
| Name = x.Name, | |||
| Details = x.Details, | |||
| Flags = x.Flags, | |||
| Type = x.Type | |||
| }; | |||
| }).ToArray(); | |||
| return model; | |||
| } | |||
| Model ICached<Model>.ToModel() => ToModel(); | |||
| TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | |||
| void ICached<Model>.Update(Model model) => Update(model); | |||
| bool ICached.IsFreed => IsFreed; | |||
| @@ -97,7 +97,7 @@ namespace Discord.WebSocket | |||
| } | |||
| #region Cache | |||
| private struct CacheModel : Model | |||
| private class CacheModel : Model | |||
| { | |||
| public bool? IsVerified { get; set; } | |||
| @@ -125,29 +125,23 @@ namespace Discord.WebSocket | |||
| } | |||
| internal new Model ToModel() | |||
| => ToModel<CacheModel>(); | |||
| internal new TModel ToModel<TModel>() where TModel : Model, new() | |||
| { | |||
| return new TModel | |||
| { | |||
| Avatar = AvatarId, | |||
| Discriminator = Discriminator, | |||
| Email = Email, | |||
| Flags = Flags, | |||
| Id = Id, | |||
| IsBot = IsBot, | |||
| IsMfaEnabled = IsMfaEnabled, | |||
| IsVerified = IsVerified, | |||
| Locale = Locale, | |||
| PremiumType = this.PremiumType, | |||
| PublicFlags = PublicFlags ?? UserProperties.None, | |||
| Username = Username | |||
| }; | |||
| var model = Discord.StateManager.GetModel<Model, CacheModel>(); | |||
| model.Avatar = AvatarId; | |||
| model.Discriminator = Discriminator; | |||
| model.Email = Email; | |||
| model.Flags = Flags; | |||
| model.IsBot = IsBot; | |||
| model.IsMfaEnabled = IsMfaEnabled; | |||
| model.Locale = Locale; | |||
| model.PremiumType = PremiumType; | |||
| model.PublicFlags = PublicFlags ?? UserProperties.None; | |||
| model.Username = Username; | |||
| model.Id = Id; | |||
| return model; | |||
| } | |||
| Model ICached<Model>.ToModel() => ToModel(); | |||
| TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | |||
| void ICached<Model>.Update(Model model) => Update(model); | |||
| #endregion | |||
| } | |||
| @@ -151,14 +151,14 @@ namespace Discord.WebSocket | |||
| internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member) | |||
| { | |||
| var entity = new SocketThreadUser(guild.Discord, guild.Id, thread.Id, model.UserId.Value); | |||
| var entity = new SocketThreadUser(guild.Discord, guild.Id, thread.Id, model.Id); | |||
| entity.Update(model); | |||
| return entity; | |||
| } | |||
| internal static SocketThreadUser Create(DiscordSocketClient client, ulong guildId, ulong threadId, Model model) | |||
| { | |||
| var entity = new SocketThreadUser(client, guildId, threadId, model.UserId.Value); | |||
| var entity = new SocketThreadUser(client, guildId, threadId, model.Id); | |||
| entity.Update(model); | |||
| return entity; | |||
| } | |||
| @@ -242,7 +242,12 @@ namespace Discord.WebSocket | |||
| public override void Dispose() | |||
| { | |||
| if (IsFreed) | |||
| return; | |||
| GC.SuppressFinalize(this); | |||
| Discord.StateManager.GetThreadMemberStore(_threadId)?.RemoveReference(Id); | |||
| IsFreed = true; | |||
| } | |||
| @@ -255,27 +260,21 @@ namespace Discord.WebSocket | |||
| #region Cache | |||
| private class CacheModel : Model | |||
| { | |||
| public ulong Id { get; set; } | |||
| public ulong? ThreadId { get; set; } | |||
| public ulong? UserId { get; set; } | |||
| public DateTimeOffset JoinedAt { get; set; } | |||
| ulong IEntityModel<ulong>.Id { get => UserId.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||
| } | |||
| internal new Model ToModel() => ToModel<CacheModel>(); | |||
| internal new TModel ToModel<TModel>() where TModel : Model, new() | |||
| internal new Model ToModel() | |||
| { | |||
| return new TModel | |||
| { | |||
| JoinedAt = ThreadJoinedAt, | |||
| ThreadId = _threadId, | |||
| UserId = Id | |||
| }; | |||
| var model = Discord.StateManager.GetModel<Model, CacheModel>(); | |||
| model.Id = Id; | |||
| model.ThreadId = _threadId; | |||
| model.JoinedAt = ThreadJoinedAt; | |||
| return model; | |||
| } | |||
| Model ICached<Model>.ToModel() => ToModel(); | |||
| TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | |||
| void ICached<Model>.Update(Model model) => Update(model); | |||
| #endregion | |||
| } | |||
| @@ -137,29 +137,20 @@ namespace Discord.WebSocket | |||
| public ulong Id { get; set; } | |||
| } | |||
| internal TModel ToModel<TModel>() where TModel : Model, new() | |||
| internal Model ToModel() | |||
| { | |||
| return new TModel | |||
| { | |||
| Avatar = AvatarId, | |||
| Discriminator = Discriminator, | |||
| Id = Id, | |||
| IsBot = IsBot, | |||
| Username = Username | |||
| }; | |||
| var model = Discord.StateManager.GetModel<Model, CacheModel>(); | |||
| model.Avatar = AvatarId; | |||
| model.Discriminator = Discriminator; | |||
| model.Id = Id; | |||
| model.IsBot = IsBot; | |||
| model.Username = Username; | |||
| return model; | |||
| } | |||
| internal Model ToModel() | |||
| => ToModel<CacheModel>(); | |||
| Model ICached<Model>.ToModel() | |||
| => ToModel(); | |||
| TResult ICached<Model>.ToModel<TResult>() | |||
| => ToModel<TResult>(); | |||
| void ICached<Model>.Update(Model model) => Update(model); | |||
| bool ICached.IsFreed => IsFreed; | |||
| #endregion | |||