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