From c63bb228d15bec8660445117de1cf329478d1723 Mon Sep 17 00:00:00 2001 From: SNAKE Date: Thu, 24 Oct 2024 14:38:06 +0800 Subject: [PATCH] fix: 1. AsyncIO uses PathBuilders that already return to the pool, 2. ObserverCollections only do actions synchronously --- Salvavida/Package/Runtime/AsyncIO.cs | 2 +- Salvavida/Package/Runtime/AsyncJob.cs | 16 ++-- Salvavida/Package/Runtime/ObservableArray.cs | 7 +- .../Package/Runtime/ObservableCollection.cs | 80 +++++++++++++---- .../Package/Runtime/ObservableDictionary.cs | 35 ++++++-- Salvavida/Package/Runtime/ObservableList.cs | 11 ++- Salvavida/Package/Runtime/Serializer.cs | 88 ++++++++++++++++--- 7 files changed, 186 insertions(+), 53 deletions(-) diff --git a/Salvavida/Package/Runtime/AsyncIO.cs b/Salvavida/Package/Runtime/AsyncIO.cs index fa68bc9..fd9466d 100644 --- a/Salvavida/Package/Runtime/AsyncIO.cs +++ b/Salvavida/Package/Runtime/AsyncIO.cs @@ -17,7 +17,7 @@ public abstract partial class AsyncIO : IDisposable public void QueueJob(AsyncJob job) { - var hashCode = job.PathBuilder.GetHashCode(); + var hashCode = job.PathBuilder!.GetHashCode(); if (_jobs.TryGetValue(hashCode, out var j)) { job.JoinJob(j); diff --git a/Salvavida/Package/Runtime/AsyncJob.cs b/Salvavida/Package/Runtime/AsyncJob.cs index 650073c..322faa7 100644 --- a/Salvavida/Package/Runtime/AsyncJob.cs +++ b/Salvavida/Package/Runtime/AsyncJob.cs @@ -6,9 +6,9 @@ namespace Salvavida { public abstract class AsyncJob : INotifyCompletion { - protected AsyncJob(PathBuilder pathBuilder, CancellationToken token) + protected AsyncJob(IObjectPool.UsingScope pathScope, CancellationToken token) { - PathBuilder = pathBuilder; + _pathScope = pathScope; _token = token; } @@ -18,11 +18,12 @@ protected AsyncJob(PathBuilder pathBuilder, CancellationToken token) private int _jobFinished; private int _completed; private AsyncJob? _joinedJob; + private IObjectPool.UsingScope _pathScope; public bool IsCompleted => _completed > 0; public bool JobFinished => _jobFinished > 0; - public PathBuilder PathBuilder { get; } + public PathBuilder? PathBuilder => _pathScope.Value; public void RunJob() { @@ -49,6 +50,7 @@ public void SetComplete() JoinedJobOnComplete(_joinedJob); _joinedJob.SetComplete(); } + _pathScope.Dispose(); } public void OnCompleted(Action continuation) @@ -72,8 +74,8 @@ public void JoinJob(AsyncJob job) public class AsyncVoidJob : AsyncJob { - public AsyncVoidJob(PathBuilder pathBuilder, Action action, CancellationToken token) - : base(pathBuilder, token) + public AsyncVoidJob(IObjectPool.UsingScope pathScope, Action action, CancellationToken token) + : base(pathScope, token) { _action = action ?? throw new ArgumentNullException(nameof(action)); } @@ -108,8 +110,8 @@ protected override void JoinedJobOnComplete(AsyncJob job) public class AsyncValueJob : AsyncJob { - public AsyncValueJob(PathBuilder pathBuilder, Func valueGetter, CancellationToken token) - : base(pathBuilder, token) + public AsyncValueJob(IObjectPool.UsingScope pathScope, Func valueGetter, CancellationToken token) + : base(pathScope, token) { _valueGetter = valueGetter ?? throw new ArgumentNullException(nameof(valueGetter)); } diff --git a/Salvavida/Package/Runtime/ObservableArray.cs b/Salvavida/Package/Runtime/ObservableArray.cs index b771a91..c2e1e40 100644 --- a/Salvavida/Package/Runtime/ObservableArray.cs +++ b/Salvavida/Package/Runtime/ObservableArray.cs @@ -96,11 +96,14 @@ protected override void OnChildChanged(T child, string _) return CollectionChangeInfo.Add(_arr, 0); } - protected override void TrySaveSource(Serializer serializer, PathBuilder pathBuilder) + protected override void TrySaveSource(Serializer serializer, PathBuilder? pathBuilder) { if (string.IsNullOrEmpty(SvId)) throw new NullReferenceException(nameof(SvId)); - serializer.SaveArray(_arr, pathBuilder); + if (pathBuilder == null) + serializer.FreshActionByPolicy(this, path => serializer.SaveArray(_arr, path)); + else + serializer.SaveArray(_arr, pathBuilder); } public void Add(T? item) => throw new NotSupportedException(); diff --git a/Salvavida/Package/Runtime/ObservableCollection.cs b/Salvavida/Package/Runtime/ObservableCollection.cs index 22d25b8..86dcf20 100644 --- a/Salvavida/Package/Runtime/ObservableCollection.cs +++ b/Salvavida/Package/Runtime/ObservableCollection.cs @@ -48,16 +48,19 @@ public void TrySave(Serializer? serializer, PathBuilder pathBuilder) _isDirty = false; } - protected abstract void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder pathBuilder); + protected abstract void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder? pathBuilder); - protected abstract void TrySaveSource(Serializer serializer, PathBuilder pathBuilder); + protected abstract void TrySaveSource(Serializer serializer, PathBuilder? pathBuilder); - protected void ClearCollection(Serializer serializer, PathBuilder pathBuilder) + protected void ClearCollection(Serializer serializer, PathBuilder? pathBuilder) { - serializer.DeleteAll(pathBuilder); + if (pathBuilder == null) + serializer.FreshDeleteAllByPolicy(this); + else + serializer.DeleteAll(pathBuilder); } void ISavable.SetParent(ISavable? parent) @@ -130,9 +133,7 @@ protected void OnCollectionChange(CollectionChangeInfo e) { if (SaveSeparately) { - using var action = serializer.BeginFreshAction(out var pathBuilder); - this.GetSavePathAsSpan(pathBuilder); - TrySaveSeparatelyByEvent(serializer, pathBuilder, e); + TrySaveSeparatelyByEvent(serializer, null, e); } else { @@ -142,12 +143,12 @@ protected void OnCollectionChange(CollectionChangeInfo e) CollectionChanged?.Invoke(e); } - protected override void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder pathBuilder) + protected override void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder? pathBuilder) { TrySaveSeparatelyByEvent(serializer, pathBuilder, CreateSaveAllEvent()); } - protected void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder pathBuilder, CollectionChangeInfo e) + protected void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder? pathBuilder, CollectionChangeInfo e) { if (!SaveSeparately) throw new NotSupportedException(); @@ -157,7 +158,7 @@ protected void TrySaveSeparatelyByEvent(Serializer serializer, PathBuilder pathB TrySaveItems(serializer, pathBuilder, e); } - protected virtual void TrySaveItems(Serializer serializer, PathBuilder pathBuilder, CollectionChangeInfo e) + protected virtual void TrySaveItems(Serializer serializer, PathBuilder? pathBuilder, CollectionChangeInfo e) { if (string.IsNullOrEmpty(SvId) || SvParent == null) return; @@ -181,21 +182,42 @@ protected virtual void TrySaveItems(Serializer serializer, PathBuilder pathBuild } } - protected void CollectionSave(Serializer serializer, PathBuilder path, TElem? oldItem, TElem? newItem, int startingIndex) + protected void CollectionSave(Serializer serializer, PathBuilder? path, TElem? oldItem, TElem? newItem, int startingIndex) { if (oldItem is ISavable old) - serializer.Delete(old, path, PathBuilder.Type.Collection); + { + if (path == null) + serializer.FreshDeleteByPolicy(old); + else + serializer.Delete(old, path, PathBuilder.Type.Collection); + } if (newItem is ISavable nuevo) { #if DEBUG if (newItem is ISaveWithOrder swo && swo.SvOrder != startingIndex) throw new Exception($"index mismatch, svOrder: {swo.SvOrder}, index: {startingIndex}"); #endif - serializer.Save(nuevo, path, PathBuilder.Type.Collection); + if (path == null) + serializer.FreshSaveByPolicy(nuevo); + else + serializer.Save(nuevo, path, PathBuilder.Type.Collection); + } + } + + protected void CollectionSave(Serializer serializer, PathBuilder? path, IList oldItems, IList newItems, int startingIndex) + { + if (path == null) + { + serializer.FreshActionByPolicy(this, pathBuilder => + { + CollectionSaveAction(serializer, pathBuilder, oldItems, newItems, startingIndex); + }); } + else + CollectionSaveAction(serializer, path, oldItems, newItems, startingIndex); } - protected void CollectionSave(Serializer serializer, PathBuilder path, IList oldItems, IList newItems, int startingIndex) + private void CollectionSaveAction(Serializer serializer, PathBuilder path, IList oldItems, IList newItems, int startingIndex) { if (oldItems != null) { @@ -224,20 +246,42 @@ protected void CollectionSave(Serializer serializer, PathBuilder path, IList items) + protected void CollectionUpdateOrder(Serializer serializer, PathBuilder? pathBuilder, IList items) + { + if (pathBuilder == null) + { + serializer.FreshActionByPolicy(this, path => + { + CollectionUpdateOrderAction(serializer, path, items); + }); + } + else + { + CollectionUpdateOrder(serializer, pathBuilder, items); + } + } + protected void CollectionUpdateOrderAction(Serializer serializer, PathBuilder pathBuilder, IList items) { for (var i = 0; i < items.Count; i++) { diff --git a/Salvavida/Package/Runtime/ObservableDictionary.cs b/Salvavida/Package/Runtime/ObservableDictionary.cs index 5094d44..af14101 100644 --- a/Salvavida/Package/Runtime/ObservableDictionary.cs +++ b/Salvavida/Package/Runtime/ObservableDictionary.cs @@ -115,7 +115,7 @@ protected override void OnChildChanged(TValue obj, string _) } - protected override void TrySaveItems(Serializer serializer, PathBuilder pathBuilder, CollectionChangeInfo e) + protected override void TrySaveItems(Serializer serializer, PathBuilder? pathBuilder, CollectionChangeInfo e) { switch (e.Action) { @@ -139,19 +139,37 @@ protected override void TrySaveItems(Serializer serializer, PathBuilder pathBuil throw new NotSupportedException(); } } - private void KeyCollectionSave(Serializer serializer, PathBuilder pathBuilder, TValue value, bool isRemove) + private void KeyCollectionSave(Serializer serializer, PathBuilder? pathBuilder, TValue value, bool isRemove) { if (value is not ISavable sv) throw new ArgumentException("values have to be implementations of ISavable"); if (isRemove) - serializer.Delete(sv, pathBuilder, PathBuilder.Type.Collection); + { + if (pathBuilder == null) + serializer.FreshDeleteByPolicy(sv); + else + serializer.Delete(sv, pathBuilder, PathBuilder.Type.Collection); + } else - serializer.Save(sv, pathBuilder, PathBuilder.Type.Collection); + { + if (pathBuilder == null) + serializer.FreshSaveByPolicy(sv); + else + serializer.Save(sv, pathBuilder, PathBuilder.Type.Collection); + } } - private void KeyCollectionSave(Serializer serializer, PathBuilder pathBuilder, IList values, bool isRemove) + private void KeyCollectionSave(Serializer serializer, PathBuilder? pathBuilder, IList values, bool isRemove) { if (values == null) throw new ArgumentNullException(nameof(values)); + if (pathBuilder == null) + serializer.FreshActionByPolicy(this, path => KeyCollectionSaveAction(serializer, path, values, isRemove)); + else + KeyCollectionSaveAction(serializer, pathBuilder, values, isRemove); + } + + private void KeyCollectionSaveAction(Serializer serializer, PathBuilder pathBuilder, IList values, bool isRemove) + { for (var i = 0; i < values.Count; i++) { if (values[i] is not ISavable value) @@ -163,11 +181,14 @@ private void KeyCollectionSave(Serializer serializer, PathBuilder pathBuilder, I } } - protected override void TrySaveSource(Serializer serializer, PathBuilder path) + protected override void TrySaveSource(Serializer serializer, PathBuilder? pathBuilder) { if (string.IsNullOrEmpty(SvId)) throw new NullReferenceException(nameof(SvId)); - serializer.SaveDict(_dict, path); + if (pathBuilder == null) + serializer.FreshActionByPolicy(this, path => serializer.SaveDict(_dict, path)); + else + serializer.SaveDict(_dict, pathBuilder); } public void Add(TKey key, TValue? value) diff --git a/Salvavida/Package/Runtime/ObservableList.cs b/Salvavida/Package/Runtime/ObservableList.cs index ced0c40..ac2595d 100644 --- a/Salvavida/Package/Runtime/ObservableList.cs +++ b/Salvavida/Package/Runtime/ObservableList.cs @@ -93,7 +93,7 @@ protected override void OnChildChanged(T obj, string _) return CollectionChangeInfo.Add(_list, 0); } - protected override void TrySaveItems(Serializer serializer, PathBuilder path, CollectionChangeInfo e) + protected override void TrySaveItems(Serializer serializer, PathBuilder? path, CollectionChangeInfo e) { base.TrySaveItems(serializer, path, e); if (!_orderMatters) @@ -111,14 +111,17 @@ protected override void TrySaveItems(Serializer serializer, PathBuilder path, Co } } - protected override void TrySaveSource(Serializer serializer, PathBuilder pathBuilder) + protected override void TrySaveSource(Serializer serializer, PathBuilder? pathBuilder) { if (string.IsNullOrEmpty(SvId)) throw new NullReferenceException(nameof(SvId)); - serializer.SaveList(_list, pathBuilder); + if (pathBuilder == null) + serializer.FreshActionByPolicy(this, path => serializer.SaveList(_list, path)); + else + serializer.SaveList(_list, pathBuilder); } - private void TryUpdateOrder(Serializer serializer, PathBuilder pathBuilder, int index) + private void TryUpdateOrder(Serializer serializer, PathBuilder? pathBuilder, int index) { var count = _list.Count - index; if (count <= 0) diff --git a/Salvavida/Package/Runtime/Serializer.cs b/Salvavida/Package/Runtime/Serializer.cs index f1b6df7..670e1aa 100644 --- a/Salvavida/Package/Runtime/Serializer.cs +++ b/Salvavida/Package/Runtime/Serializer.cs @@ -78,12 +78,34 @@ protected virtual bool CheckNotDirty(T value) return false; } - public FreshActionLocker BeginFreshAction(out PathBuilder pathBuilder) + protected FreshActionLocker BeginFreshAction(out PathBuilder pathBuilder) { pathBuilder = _pathBuilder; return new FreshActionLocker(this); } + public void FreshActionByPolicy(T parent, Action action) where T : ISavable + { + + } + + public void FreshActionAsync(T parent, Action action, CancellationToken token) where T : ISavable + { + var pathScope = PathBuilderPool.Get(out var path); + parent.GetParentPathAsSpan(path); + ThrowIfPathIsEmpty(path); + AsyncIO.QueueJob(new AsyncVoidJob(pathScope, () => action(pathScope.Value!), token)); + } + + public void FreshActionSync(T parent, Action action) where T : ISavable + { + AsyncIO.ForceComplete(); + using var locker = BeginFreshAction(out var path); + parent.GetParentPathAsSpan(path); + ThrowIfPathIsEmpty(path); + action(path); + } + public bool FreshHas(T data) where T : ISavable { if (data == null || string.IsNullOrEmpty(data.SvId)) @@ -97,9 +119,9 @@ public async Task FreshHasAsync(T data, CancellationToken token) where { if (data == null || string.IsNullOrEmpty(data.SvId)) throw new ArgumentNullException(nameof(data)); - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); data.GetParentPathAsSpan(path); - var job = new AsyncValueJob(path, () => Has(path), token); + var job = new AsyncValueJob(pathScope, () => Has(path), token); AsyncIO.QueueJob(job); return await job; } @@ -142,7 +164,7 @@ public void FreshUpdateIdAsync(T data, ReadOnlyMemory oldId) where T : throw new ArgumentNullException(nameof(data)); if (CheckNotDirty(data)) return; - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); data.GetParentPathAsSpan(path); DoUpdateId(data, path, oldId.Span); } @@ -188,10 +210,10 @@ public void FreshUpdateOrderAsync(ISavable data, int order, CancellationToken to throw new ArgumentNullException(nameof(data)); if (CheckNotDirty(data)) return; - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); data.GetSavePathAsSpan(path); ThrowIfPathIsEmpty(path); - AsyncIO.QueueJob(new AsyncVoidJob(path, () => DoUpdateOrder(data, path, order), token)); + AsyncIO.QueueJob(new AsyncVoidJob(pathScope, () => DoUpdateOrder(data, path, order), token)); } public void UpdateOrder(ISavable data, PathBuilder path, int order) @@ -237,10 +259,10 @@ public async Task FreshSaveAsync(T data, CancellationToken token) where T : I throw new ArgumentNullException(nameof(data)); if (CheckNotDirty(data)) return; - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); data.GetSavePathAsSpan(path); ThrowIfPathIsEmpty(path); - var job = new AsyncVoidJob(path, () => DoSaveObject(data, path), token); + var job = new AsyncVoidJob(pathScope, () => DoSaveObject(data, path), token); AsyncIO.QueueJob(job); await job; } @@ -261,11 +283,11 @@ public async Task FreshSaveAsync(ISavable parent, ReadOnlyMemory propNa throw new ArgumentNullException(nameof(data)); if (CheckNotDirty(data)) return; - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); parent.GetSavePathAsSpan(path); ThrowIfPathIsEmpty(path); path.Push(propName.Span, type); - var job = new AsyncVoidJob(path, () => DoSaveObject(data, path), default); + var job = new AsyncVoidJob(pathScope, () => DoSaveObject(data, path), default); AsyncIO.QueueJob(job); await job; } @@ -340,9 +362,9 @@ protected virtual void DoSaveObject(T obj, PathBuilder path) { if (svid.IsEmpty) throw new ArgumentNullException(nameof(svid)); - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); path.Push(svid.Span, PathBuilder.Type.Property); - var job = new AsyncValueJob(path, () => DoRead(path, out _), token); + var job = new AsyncValueJob(pathScope, () => DoRead(path, out _), token); AsyncIO.QueueJob(job); return await job; } @@ -398,6 +420,14 @@ protected virtual void DoSaveObject(T obj, PathBuilder path) protected abstract Dictionary? DoReadDict(PathBuilder path, bool saveSeparately); + public void FreshDeleteByPolicy(T data) where T : ISavable + { + if (SavePolicy == SavePolicy.Sync) + FreshDeleteSync(data); + else + FreshDeleteAsync(data, default); + } + public void FreshDeleteSync(T data) where T : ISavable { if (data == null) @@ -413,10 +443,10 @@ public void FreshDeleteAsync(T data, CancellationToken token) where T : ISava { if (data == null) throw new ArgumentNullException(nameof(data)); - using var pathScope = PathBuilderPool.Get(out var path); + var pathScope = PathBuilderPool.Get(out var path); data.GetSavePathAsSpan(path); ThrowIfPathIsEmpty(path); - AsyncIO.QueueJob(new AsyncVoidJob(path, () => DoDelete(path), token)); + AsyncIO.QueueJob(new AsyncVoidJob(pathScope, () => DoDelete(path), token)); } public void Delete(T data, PathBuilder path, PathBuilder.Type type) where T : ISavable @@ -439,6 +469,36 @@ public void DeleteObject(PathBuilder path, ReadOnlySpan propName, PathBuil protected abstract void DoDelete(PathBuilder path); + public void FreshDeleteAllByPolicy(T savable) where T : ISavable + { + if (SavePolicy == SavePolicy.Sync) + FreshDeleteAllSync(savable); + else + FreshDeleteAllAsync(savable, default); + } + + public void FreshDeleteAllSync(T savable) where T : ISavable + { + if (savable == null) + throw new ArgumentNullException(nameof(savable)); + AsyncIO.ForceComplete(); + using var locker = BeginFreshAction(out var path); + savable.GetSavePathAsSpan(path); + ThrowIfPathIsEmpty(path); + DeleteAll(path); + } + + public void FreshDeleteAllAsync(T savable, CancellationToken token) where T : ISavable + { + if (savable == null) + throw new ArgumentNullException(nameof(savable)); + var pathScope = PathBuilderPool.Get(out var path); + savable.GetSavePathAsSpan(path); + ThrowIfPathIsEmpty(path); + DeleteAll(path); + AsyncIO.QueueJob(new AsyncVoidJob(pathScope, () => DeleteAll(path), token)); + } + public void DeleteAll(T savable, PathBuilder path, PathBuilder.Type type) where T : ISavable { if (savable == null || string.IsNullOrEmpty(savable.SvId))