mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-01 06:00:46 +00:00
Optimize serializable file writes
This commit is contained in:
@@ -100,9 +100,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
|
||||
public void ExceptWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
foreach (T item in other) {
|
||||
Remove(item);
|
||||
}
|
||||
RemoveRange(other);
|
||||
}
|
||||
|
||||
[MustDisposeResource]
|
||||
@@ -113,8 +111,14 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
|
||||
|
||||
IReadOnlySet<T> otherSet = other as IReadOnlySet<T> ?? other.ToHashSet();
|
||||
|
||||
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
|
||||
Remove(item);
|
||||
bool modified = false;
|
||||
|
||||
foreach (T _ in this.Where(item => !otherSet.Contains(item) && BackingCollection.TryRemove(item, out _))) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,24 +186,24 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
IReadOnlySet<T> otherSet = other as IReadOnlySet<T> ?? other.ToHashSet();
|
||||
HashSet<T> removed = [];
|
||||
|
||||
foreach (T item in otherSet.Where(Contains)) {
|
||||
removed.Add(item);
|
||||
Remove(item);
|
||||
HashSet<T> removed = otherSet.Where(item => Contains(item) && BackingCollection.TryRemove(item, out _)).ToHashSet();
|
||||
|
||||
bool modified = removed.Count > 0;
|
||||
|
||||
foreach (T _ in otherSet.Where(item => !removed.Contains(item) && BackingCollection.TryAdd(item, true))) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
foreach (T item in otherSet.Where(item => !removed.Contains(item))) {
|
||||
Add(item);
|
||||
if (modified) {
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
foreach (T otherElement in other) {
|
||||
Add(otherElement);
|
||||
}
|
||||
AddRange(other);
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) {
|
||||
@@ -215,44 +219,66 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
|
||||
public bool AddRange(IEnumerable<T> items) {
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
bool result = false;
|
||||
bool modified = false;
|
||||
|
||||
foreach (T _ in items.Where(Add)) {
|
||||
result = true;
|
||||
foreach (T _ in items.Where(item => BackingCollection.TryAdd(item, true))) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (modified) {
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool RemoveRange(IEnumerable<T> items) {
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
bool result = false;
|
||||
bool modified = false;
|
||||
|
||||
foreach (T _ in items.Where(Remove)) {
|
||||
result = true;
|
||||
foreach (T _ in items.Where(item => BackingCollection.TryRemove(item, out _))) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (modified) {
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public int RemoveWhere(Predicate<T> match) {
|
||||
ArgumentNullException.ThrowIfNull(match);
|
||||
|
||||
return BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _));
|
||||
int count = BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _));
|
||||
|
||||
if (count > 0) {
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
|
||||
public bool ReplaceIfNeededWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
if (SetEquals(other)) {
|
||||
ICollection<T> otherCollection = other as ICollection<T> ?? other.ToHashSet();
|
||||
|
||||
if (SetEquals(otherCollection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReplaceWith(other);
|
||||
BackingCollection.Clear();
|
||||
|
||||
foreach (T item in otherCollection) {
|
||||
BackingCollection.TryAdd(item, true);
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -261,7 +287,6 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
|
||||
public void ReplaceWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
Clear();
|
||||
UnionWith(other);
|
||||
ReplaceIfNeededWith(other);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Nito.AsyncEx;
|
||||
@@ -60,6 +61,12 @@ public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
T oldValue = BackingCollection[index];
|
||||
|
||||
if (EqualityComparer<T>.Default.Equals(oldValue, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingCollection[index] = value;
|
||||
}
|
||||
|
||||
@@ -86,6 +93,10 @@ public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
|
||||
public void Clear() {
|
||||
using (Lock.WriterLock()) {
|
||||
if (BackingCollection.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingCollection.Clear();
|
||||
}
|
||||
|
||||
@@ -155,9 +166,15 @@ public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
public void ReplaceWith(IEnumerable<T> collection) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
ICollection<T> newCollection = collection as ICollection<T> ?? collection.ToList();
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
if (BackingCollection.SequenceEqual(newCollection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingCollection.Clear();
|
||||
BackingCollection.AddRange(collection);
|
||||
BackingCollection.AddRange(newCollection);
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
@@ -65,6 +65,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
BackingDictionary[key] = value;
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
@@ -111,6 +112,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
BackingDictionary.Clear();
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,12 +105,6 @@ public abstract class SerializableFile : IDisposable {
|
||||
string newFilePath = $"{serializableFile.FilePath}.new";
|
||||
|
||||
if (File.Exists(serializableFile.FilePath)) {
|
||||
string currentJson = await File.ReadAllTextAsync(serializableFile.FilePath).ConfigureAwait(false);
|
||||
|
||||
if (json == currentJson) {
|
||||
return;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
File.Replace(newFilePath, serializableFile.FilePath, null);
|
||||
@@ -156,12 +150,6 @@ public abstract class SerializableFile : IDisposable {
|
||||
// We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
|
||||
#pragma warning disable CA3003 // Ignored due to caller's intent
|
||||
if (File.Exists(filePath)) {
|
||||
string currentJson = await File.ReadAllTextAsync(filePath).ConfigureAwait(false);
|
||||
|
||||
if (json == currentJson) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
File.Replace(newFilePath, filePath, null);
|
||||
|
||||
Reference in New Issue
Block a user