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