| @@ -11,8 +11,6 @@ namespace Discord | |||||
| void Update(TType model); | void Update(TType model); | ||||
| TType ToModel(); | TType ToModel(); | ||||
| TResult ToModel<TResult>() where TResult : TType, new(); | |||||
| } | } | ||||
| public interface ICached | public interface ICached | ||||
| @@ -12,7 +12,7 @@ namespace Discord | |||||
| string Nickname { get; set; } | string Nickname { get; set; } | ||||
| string GuildAvatar { get; set; } | string GuildAvatar { get; set; } | ||||
| ulong[] Roles { get; set; } | ulong[] Roles { get; set; } | ||||
| DateTimeOffset JoinedAt { get; set; } | |||||
| DateTimeOffset? JoinedAt { get; set; } | |||||
| DateTimeOffset? PremiumSince { get; set; } | DateTimeOffset? PremiumSince { get; set; } | ||||
| bool IsDeaf { get; set; } | bool IsDeaf { get; set; } | ||||
| bool IsMute { get; set; } | bool IsMute { get; set; } | ||||
| @@ -9,7 +9,6 @@ namespace Discord | |||||
| public interface IThreadMemberModel : IEntityModel<ulong> | public interface IThreadMemberModel : IEntityModel<ulong> | ||||
| { | { | ||||
| ulong? ThreadId { get; set; } | ulong? ThreadId { get; set; } | ||||
| ulong? UserId { get; set; } | |||||
| DateTimeOffset JoinedAt { get; set; } | DateTimeOffset JoinedAt { get; set; } | ||||
| } | } | ||||
| } | } | ||||
| @@ -39,8 +39,8 @@ namespace Discord.API | |||||
| get => Roles.GetValueOrDefault(Array.Empty<ulong>()); set => throw new NotSupportedException(); | 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 { | DateTimeOffset? IMemberModel.PremiumSince { | ||||
| @@ -15,7 +15,6 @@ namespace Discord.API | |||||
| public DateTimeOffset JoinTimestamp { get; set; } | public DateTimeOffset JoinTimestamp { get; set; } | ||||
| ulong? IThreadMemberModel.ThreadId { get => ThreadId.ToNullable(); set => throw new NotSupportedException(); } | 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(); } | DateTimeOffset IThreadMemberModel.JoinedAt { get => JoinTimestamp; set => throw new NotSupportedException(); } | ||||
| ulong IEntityModel<ulong>.Id { get => UserId.GetValueOrDefault(0); 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<Type, object> _storeCache = new(); | ||||
| private readonly ConcurrentDictionary<object, object> _subStoreCache = 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> | private class DefaultEntityStore<TModel, TId> : IEntityStore<TModel, TId> | ||||
| where TModel : IEntityModel<TId> | where TModel : IEntityModel<TId> | ||||
| where TId : IEquatable<TId> | where TId : IEquatable<TId> | ||||
| @@ -23,44 +33,72 @@ namespace Discord.WebSocket | |||||
| _cache = cache; | _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; | 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) | foreach (var model in models) | ||||
| _cache.AddOrUpdate(model.Id, model, (_, __) => model); | _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; | 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; | return default; | ||||
| } | } | ||||
| public ValueTask PurgeAllAsync(CacheRunMode runmode) | |||||
| ValueTask IEntityStore<TModel, TId>.RemoveAsync(TId id) | |||||
| { | { | ||||
| _cache.Clear(); | |||||
| Remove(id); | |||||
| return default; | 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>() | public virtual ValueTask<IEntityStore<TModel, TId>> GetStoreAsync<TModel, TId>() | ||||
| @@ -8,6 +8,8 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| public interface ICacheProvider | public interface ICacheProvider | ||||
| { | { | ||||
| Type GetModel<TModelInterface>(); | |||||
| ValueTask<IEntityStore<TModel, TId>> GetStoreAsync<TModel, TId>() | ValueTask<IEntityStore<TModel, TId>> GetStoreAsync<TModel, TId>() | ||||
| where TModel : IEntityModel<TId> | where TModel : IEntityModel<TId> | ||||
| where TId : IEquatable<TId>; | where TId : IEquatable<TId>; | ||||
| @@ -21,11 +23,17 @@ namespace Discord.WebSocket | |||||
| where TModel : IEntityModel<TId> | where TModel : IEntityModel<TId> | ||||
| where TId : IEquatable<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() | public async ValueTask InitializeAsync() | ||||
| { | { | ||||
| _store ??= await _cacheProvider.GetStoreAsync<TModel, TId>().ConfigureAwait(false); | _store ??= await _cacheProvider.GetStoreAsync<TModel, TId>().ConfigureAwait(false); | ||||
| @@ -137,7 +115,7 @@ namespace Discord.WebSocket | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| var model = RunOrThrowValueTask(_store.GetAsync(id, CacheRunMode.Sync)); | |||||
| var model = _store.Get(id); | |||||
| if (model != null) | if (model != null) | ||||
| { | { | ||||
| @@ -156,7 +134,7 @@ namespace Discord.WebSocket | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| var model = await _store.GetAsync(id, CacheRunMode.Async).ConfigureAwait(false); | |||||
| var model = await _store.GetAsync(id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| { | { | ||||
| @@ -175,7 +153,7 @@ namespace Discord.WebSocket | |||||
| public IEnumerable<TEntity> GetAll() | public IEnumerable<TEntity> GetAll() | ||||
| { | { | ||||
| var models = RunOrThrowValueTask(_store.GetAllAsync(CacheRunMode.Sync).ToArrayAsync()); | |||||
| var models = _store.GetAll(); | |||||
| return models.Select(x => | return models.Select(x => | ||||
| { | { | ||||
| var entity = _entityBuilder(x); | var entity = _entityBuilder(x); | ||||
| @@ -186,7 +164,7 @@ namespace Discord.WebSocket | |||||
| public async IAsyncEnumerable<TEntity> GetAllAsync() | 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); | var entity = _entityBuilder(model); | ||||
| _references.TryAdd(model.Id, new CacheReference<TEntity>(entity)); | _references.TryAdd(model.Id, new CacheReference<TEntity>(entity)); | ||||
| @@ -212,13 +190,13 @@ namespace Discord.WebSocket | |||||
| return (TEntity)entity; | return (TEntity)entity; | ||||
| var model = valueFactory(id); | var model = valueFactory(id); | ||||
| await AddOrUpdateAsync(model); | |||||
| await AddOrUpdateAsync(model).ConfigureAwait(false); | |||||
| return _entityBuilder(model); | return _entityBuilder(model); | ||||
| } | } | ||||
| public void AddOrUpdate(TModel model) | public void AddOrUpdate(TModel model) | ||||
| { | { | ||||
| RunOrThrowValueTask(_store.AddOrUpdateAsync(model, CacheRunMode.Sync)); | |||||
| _store.AddOrUpdate(model); | |||||
| if (TryGetReference(model.Id, out var reference)) | if (TryGetReference(model.Id, out var reference)) | ||||
| reference.Update(model); | reference.Update(model); | ||||
| } | } | ||||
| @@ -227,14 +205,13 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (TryGetReference(model.Id, out var reference)) | if (TryGetReference(model.Id, out var reference)) | ||||
| reference.Update(model); | reference.Update(model); | ||||
| return _store.AddOrUpdateAsync(model, CacheRunMode.Async); | |||||
| return _store.AddOrUpdateAsync(model); | |||||
| } | } | ||||
| public void BulkAddOrUpdate(IEnumerable<TModel> models) | 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)) | if (_references.TryGetValue(model.Id, out var rf) && rf.Reference.TryGetTarget(out var entity)) | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -243,7 +220,7 @@ namespace Discord.WebSocket | |||||
| public async ValueTask BulkAddOrUpdateAsync(IEnumerable<TModel> models) | 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) | foreach (var model in models) | ||||
| { | { | ||||
| @@ -254,26 +231,26 @@ namespace Discord.WebSocket | |||||
| public void Remove(TId id) | public void Remove(TId id) | ||||
| { | { | ||||
| RunOrThrowValueTask(_store.RemoveAsync(id, CacheRunMode.Sync)); | |||||
| _store.Remove(id); | |||||
| _references.TryRemove(id, out _); | _references.TryRemove(id, out _); | ||||
| } | } | ||||
| public ValueTask RemoveAsync(TId id) | public ValueTask RemoveAsync(TId id) | ||||
| { | { | ||||
| _references.TryRemove(id, out _); | _references.TryRemove(id, out _); | ||||
| return _store.RemoveAsync(id, CacheRunMode.Async); | |||||
| return _store.RemoveAsync(id); | |||||
| } | } | ||||
| public void Purge() | public void Purge() | ||||
| { | { | ||||
| RunOrThrowValueTask(_store.PurgeAllAsync(CacheRunMode.Sync)); | |||||
| _store.PurgeAll(); | |||||
| _references.Clear(); | _references.Clear(); | ||||
| } | } | ||||
| public ValueTask PurgeAsync() | public ValueTask PurgeAsync() | ||||
| { | { | ||||
| _references.Clear(); | _references.Clear(); | ||||
| return _store.PurgeAllAsync(CacheRunMode.Async); | |||||
| return _store.PurgeAllAsync(); | |||||
| } | } | ||||
| TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id); | TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id); | ||||
| @@ -380,5 +357,24 @@ namespace Discord.WebSocket | |||||
| _threadMemberLock.Release(); | _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 /> | /// <inheritdoc /> | ||||
| public bool? IsPending { get; private set; } | public bool? IsPending { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | ||||
| /// <summary> | /// <summary> | ||||
| @@ -159,7 +158,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| internal void Update(MemberModel model) | internal void Update(MemberModel model) | ||||
| { | { | ||||
| _joinedAtTicks = model.JoinedAt.UtcTicks; | |||||
| _joinedAtTicks = model.JoinedAt.HasValue ? model.JoinedAt.Value.UtcTicks : null; | |||||
| Nickname = model.Nickname; | Nickname = model.Nickname; | ||||
| GuildAvatarId = model.GuildAvatar; | GuildAvatarId = model.GuildAvatar; | ||||
| UpdateRoles(model.Roles); | UpdateRoles(model.Roles); | ||||
| @@ -232,6 +231,17 @@ namespace Discord.WebSocket | |||||
| internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; | 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 | #endregion | ||||
| #region IGuildUser | #region IGuildUser | ||||
| @@ -249,7 +259,7 @@ namespace Discord.WebSocket | |||||
| #region Cache | #region Cache | ||||
| private struct CacheModel : MemberModel | |||||
| private class CacheModel : MemberModel | |||||
| { | { | ||||
| public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
| public string Nickname { get; set; } | public string Nickname { get; set; } | ||||
| @@ -258,7 +268,7 @@ namespace Discord.WebSocket | |||||
| public ulong[] Roles { get; set; } | public ulong[] Roles { get; set; } | ||||
| public DateTimeOffset JoinedAt { get; set; } | |||||
| public DateTimeOffset? JoinedAt { get; set; } | |||||
| public DateTimeOffset? PremiumSince { get; set; } | public DateTimeOffset? PremiumSince { get; set; } | ||||
| @@ -271,40 +281,25 @@ namespace Discord.WebSocket | |||||
| public DateTimeOffset? CommunicationsDisabledUntil { get; set; } | public DateTimeOffset? CommunicationsDisabledUntil { get; set; } | ||||
| } | } | ||||
| internal new MemberModel ToModel() | 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() | MemberModel ICached<MemberModel>.ToModel() | ||||
| => ToModel(); | => ToModel(); | ||||
| TResult ICached<MemberModel>.ToModel<TResult>() | |||||
| => ToModel<TResult>(); | |||||
| void ICached<MemberModel>.Update(MemberModel model) => Update(model); | void ICached<MemberModel>.Update(MemberModel model) => Update(model); | ||||
| public override void Dispose() | |||||
| { | |||||
| GC.SuppressFinalize(this); | |||||
| Discord.StateManager.GetMemberStore(_guildId)?.RemoveReference(Id); | |||||
| } | |||||
| ~SocketGuildUser() => Dispose(); | |||||
| #endregion | #endregion | ||||
| } | } | ||||
| } | } | ||||
| @@ -112,7 +112,6 @@ namespace Discord.WebSocket | |||||
| internal SocketPresence Clone() => MemberwiseClone() as SocketPresence; | internal SocketPresence Clone() => MemberwiseClone() as SocketPresence; | ||||
| ~SocketPresence() => Dispose(); | ~SocketPresence() => Dispose(); | ||||
| public void Dispose() | public void Dispose() | ||||
| { | { | ||||
| if (IsFreed) | if (IsFreed) | ||||
| @@ -128,7 +127,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| #region Cache | #region Cache | ||||
| private struct CacheModel : Model | |||||
| private class CacheModel : Model | |||||
| { | { | ||||
| public UserStatus Status { get; set; } | public UserStatus Status { get; set; } | ||||
| @@ -187,48 +186,43 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| internal Model ToModel() | 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(); | Model ICached<Model>.ToModel() => ToModel(); | ||||
| 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; | bool ICached.IsFreed => IsFreed; | ||||
| @@ -97,7 +97,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| #region Cache | #region Cache | ||||
| private struct CacheModel : Model | |||||
| private class CacheModel : Model | |||||
| { | { | ||||
| public bool? IsVerified { get; set; } | public bool? IsVerified { get; set; } | ||||
| @@ -125,29 +125,23 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| internal new Model ToModel() | 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(); | Model ICached<Model>.ToModel() => ToModel(); | ||||
| TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | |||||
| void ICached<Model>.Update(Model model) => Update(model); | void ICached<Model>.Update(Model model) => Update(model); | ||||
| #endregion | #endregion | ||||
| } | } | ||||
| @@ -151,14 +151,14 @@ namespace Discord.WebSocket | |||||
| internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member) | 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); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal static SocketThreadUser Create(DiscordSocketClient client, ulong guildId, ulong threadId, Model model) | 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); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -242,7 +242,12 @@ namespace Discord.WebSocket | |||||
| public override void Dispose() | public override void Dispose() | ||||
| { | { | ||||
| if (IsFreed) | |||||
| return; | |||||
| GC.SuppressFinalize(this); | GC.SuppressFinalize(this); | ||||
| Discord.StateManager.GetThreadMemberStore(_threadId)?.RemoveReference(Id); | |||||
| IsFreed = true; | |||||
| } | } | ||||
| @@ -255,27 +260,21 @@ namespace Discord.WebSocket | |||||
| #region Cache | #region Cache | ||||
| private class CacheModel : Model | private class CacheModel : Model | ||||
| { | { | ||||
| public ulong Id { get; set; } | |||||
| public ulong? ThreadId { get; set; } | public ulong? ThreadId { get; set; } | ||||
| public ulong? UserId { get; set; } | |||||
| public DateTimeOffset JoinedAt { 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(); | Model ICached<Model>.ToModel() => ToModel(); | ||||
| TResult ICached<Model>.ToModel<TResult>() => ToModel<TResult>(); | |||||
| void ICached<Model>.Update(Model model) => Update(model); | void ICached<Model>.Update(Model model) => Update(model); | ||||
| #endregion | #endregion | ||||
| } | } | ||||
| @@ -137,29 +137,20 @@ namespace Discord.WebSocket | |||||
| public ulong Id { get; set; } | 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() | Model ICached<Model>.ToModel() | ||||
| => ToModel(); | => ToModel(); | ||||
| 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; | bool ICached.IsFreed => IsFreed; | ||||
| #endregion | #endregion | ||||