diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index 5935205..9434c22 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -2,7 +2,8 @@ net8.0;net7.0;net6.0 enable - enable + enable + $(DefineConstants);WITH_VERSIONING true Recyclable.Collections 0.0.6 @@ -30,8 +31,12 @@ - - - - + + + + + + + + diff --git a/Recyclable.Collections/RecyclableList.AddRange.cs b/Recyclable.Collections/RecyclableList.AddRange.cs index b51d56b..68db403 100644 --- a/Recyclable.Collections/RecyclableList.AddRange.cs +++ b/Recyclable.Collections/RecyclableList.AddRange.cs @@ -126,8 +126,9 @@ namespace Recyclable.Collections list._count = currentItemsCount; } - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static void AddRange(this RecyclableList list, in Array items) + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + [VersionedCollectionsGenerator.RewriteForVersioned] + public static void AddRange(this RecyclableList list, in Array items) { if (list._capacity < list._count + items.Length) { @@ -136,7 +137,9 @@ namespace Recyclable.Collections Array.Copy(items, items.GetLowerBound(0), list._memoryBlock, list._count, items.Length); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -149,7 +152,9 @@ namespace Recyclable.Collections new ReadOnlySpan(items).CopyTo(new Span(list._memoryBlock, list._count, items.Length)); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -162,7 +167,9 @@ namespace Recyclable.Collections items.CopyTo(new Span(list._memoryBlock, list._count, items.Length)); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -175,7 +182,9 @@ namespace Recyclable.Collections items.CopyTo(new Span(list._memoryBlock, list._count, items.Length)); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -188,7 +197,9 @@ namespace Recyclable.Collections items.CopyTo(list._memoryBlock, list._count); list._count += items.Count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -201,7 +212,9 @@ namespace Recyclable.Collections items.CopyTo(list._memoryBlock, list._count); list._count += items.Count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -214,7 +227,9 @@ namespace Recyclable.Collections items.CopyTo(list._memoryBlock, list._count); list._count += items.Count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -227,7 +242,9 @@ namespace Recyclable.Collections new ReadOnlySpan(items._memoryBlock, 0, items._count).CopyTo(new Span(list._memoryBlock, list._count, items._count)); list._count += items._count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -278,7 +295,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -309,7 +328,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -349,7 +370,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -372,7 +395,9 @@ namespace Recyclable.Collections new ReadOnlySpan(items._items, 0, sourceCount).CopyTo(new Span(list._memoryBlock, oldCount, sourceCount)); list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -411,7 +436,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -434,7 +461,9 @@ namespace Recyclable.Collections new ReadOnlySpan(items._heap, 0, sourceCount).CopyTo(new Span(list._memoryBlock, oldCount, sourceCount)); list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -473,7 +502,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -505,7 +536,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -532,7 +565,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } public static IEnumerator AddRange(this RecyclableList list, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) @@ -656,12 +691,16 @@ namespace Recyclable.Collections else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity) && requiredAdditionalCapacity != 0) { AddRangeWithKnownCount(list, items, list._count, requiredAdditionalCapacity); + #if WITH_VERSIONING list._version++; + #endif } else { AddRangeEnumerated(list, items, growByCount); + #if WITH_VERSIONING list._version++; + #endif } } } diff --git a/Recyclable.Collections/RecyclableList.Compatibility.List.InsertRange.cs b/Recyclable.Collections/RecyclableList.Compatibility.List.InsertRange.cs index 27a1107..38d55ae 100644 --- a/Recyclable.Collections/RecyclableList.Compatibility.List.InsertRange.cs +++ b/Recyclable.Collections/RecyclableList.Compatibility.List.InsertRange.cs @@ -79,7 +79,9 @@ namespace Recyclable.Collections } list._count += requiredAdditionalCapacity; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -94,7 +96,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); Array.Copy(items, items.GetLowerBound(0), memoryBlock, index, items.Length); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -109,7 +113,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); new ReadOnlySpan(items).CopyTo(new Span(memoryBlock, index, items.Length)); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -124,7 +130,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); items.CopyTo(new Span(memoryBlock, index, items.Length)); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -139,7 +147,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); items.CopyTo(new Span(memoryBlock, index, items.Length)); list._count += items.Length; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -154,7 +164,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); items.CopyTo(memoryBlock, index); list._count += items.Count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -169,7 +181,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); items.CopyTo(list._memoryBlock, index); list._count += items.Count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -184,7 +198,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); items.CopyTo(memoryBlock, index); list._count += items.Count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -199,7 +215,9 @@ namespace Recyclable.Collections Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); new ReadOnlySpan(items._memoryBlock, 0, items._count).CopyTo(new(list._memoryBlock, index, items._count)); list._count += items._count; + #if WITH_VERSIONING list._version++; + #endif } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -251,7 +269,9 @@ namespace Recyclable.Collections } list._count = targetCapacity; + #if WITH_VERSIONING list._version++; + #endif } public static IEnumerator InsertRange(this RecyclableList list, int index, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) diff --git a/Recyclable.Collections/RecyclableList.Compatibility.List.cs b/Recyclable.Collections/RecyclableList.Compatibility.List.cs index ba3e662..3e4a1fa 100644 --- a/Recyclable.Collections/RecyclableList.Compatibility.List.cs +++ b/Recyclable.Collections/RecyclableList.Compatibility.List.cs @@ -101,7 +101,9 @@ namespace Recyclable.Collections } list._count -= foundItemsCount; + #if WITH_VERSIONING list._version++; + #endif return foundItemsCount; } @@ -125,43 +127,57 @@ namespace Recyclable.Collections } list._count -= count; + #if WITH_VERSIONING list._version++; + #endif } public static void Reverse(this RecyclableList list) { Array.Reverse(list._memoryBlock, 0, list._count); + #if WITH_VERSIONING list._version++; + #endif } public static void Reverse(this RecyclableList list, int index, int count) { Array.Reverse(list._memoryBlock, index, count); + #if WITH_VERSIONING list._version++; + #endif } public static void Sort(this RecyclableList list) { Array.Sort(list._memoryBlock, 0, list._count); + #if WITH_VERSIONING list._version++; + #endif } public static void Sort(this RecyclableList list, Comparison comparison) { Array.Sort(list._memoryBlock, 0, list._count, new ComparisonToComparerAdapter(comparison)); + #if WITH_VERSIONING list._version++; + #endif } public static void Sort(this RecyclableList list, IComparer? comparer) { Array.Sort(list._memoryBlock, 0, list._count, comparer); + #if WITH_VERSIONING list._version++; + #endif } public static void Sort(this RecyclableList list, int index, int count, IComparer? comparer) { Array.Sort(list._memoryBlock, index, count, comparer); + #if WITH_VERSIONING list._version++; + #endif } public static T[] ToArray(this RecyclableList list) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index db0cfcd..5a46ab2 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -5,8 +5,9 @@ using Recyclable.Collections.Pools; namespace Recyclable.Collections { - [Serializable] - public sealed partial class RecyclableList : IList, IReadOnlyList, IDisposable + [Serializable] + [VersionedCollectionsGenerator.GenerateVersioned] + public sealed partial class RecyclableList : IList, IReadOnlyList, IDisposable { internal static readonly bool _needsClearing = !typeof(T).IsValueType; @@ -17,7 +18,8 @@ namespace Recyclable.Collections #nullable restore internal ulong _version; - public ulong Version => _version; + [VersionedCollectionsGenerator.CloneForVersioned] + public ulong Version => _version; public RecyclableList() { @@ -172,7 +174,9 @@ namespace Recyclable.Collections set { _memoryBlock[index] = value; + #if WITH_VERSIONING _version++; + #endif } } @@ -185,7 +189,9 @@ namespace Recyclable.Collections if (_capacity != value) { _capacity = RecyclableListHelpers.EnsureCapacity(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)value))); + #if WITH_VERSIONING _version++; + #endif } } } @@ -204,7 +210,9 @@ namespace Recyclable.Collections } _count = value; + #if WITH_VERSIONING _version++; + #endif } } } @@ -243,7 +251,9 @@ namespace Recyclable.Collections } _memoryBlock[_count++] = item; + #if WITH_VERSIONING _version++; + #endif } public T[] AsArray => _memoryBlock; @@ -262,7 +272,9 @@ namespace Recyclable.Collections } _count = 0; + #if WITH_VERSIONING _version++; + #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -295,7 +307,9 @@ namespace Recyclable.Collections _memoryBlock[index] = item; _count++; + #if WITH_VERSIONING _version++; + #endif } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -315,7 +329,9 @@ namespace Recyclable.Collections _memoryBlock[_count] = default; } + #if WITH_VERSIONING _version++; + #endif return true; } @@ -342,7 +358,9 @@ namespace Recyclable.Collections _memoryBlock[_count] = default; } + #if WITH_VERSIONING _version++; + #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -356,7 +374,9 @@ namespace Recyclable.Collections public void Dispose() { + #if WITH_VERSIONING _version++; + #endif if (_count != 0) { if (_needsClearing) diff --git a/VersionedCollectionsGenerator/VersionedCollectionsGenerator.cs b/VersionedCollectionsGenerator/VersionedCollectionsGenerator.cs new file mode 100644 index 0000000..0ded016 --- /dev/null +++ b/VersionedCollectionsGenerator/VersionedCollectionsGenerator.cs @@ -0,0 +1,133 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace VersionedCollectionsGenerator +{ + [Generator] + public sealed class VersionedCollectionsGenerator : ISourceGenerator + { + private const string AttributeText = """ +using System; + +namespace VersionedCollectionsGenerator +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + internal sealed class GenerateVersionedAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] + internal sealed class CloneForVersionedAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] + internal sealed class RewriteForVersionedAttribute : Attribute + { + } +} +"""; + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + context.AddSource("GenerateVersionedAttribute.g.cs", SourceText.From(AttributeText, Encoding.UTF8)); + + if (context.SyntaxReceiver is not SyntaxReceiver receiver) + { + return; + } + + foreach (var candidate in receiver.Candidates) + { + var model = context.Compilation.GetSemanticModel(candidate.SyntaxTree); + var symbol = model.GetDeclaredSymbol(candidate) as INamedTypeSymbol; + if (symbol is null) + { + continue; + } + + if (!symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "VersionedCollectionsGenerator.GenerateVersionedAttribute")) + { + continue; + } + + string originalName = symbol.Name; + string newName = $"{originalName}Versioned"; + + var partials = GetAllPartials(context.Compilation, originalName); + int index = 0; + foreach (var partial in partials) + { + var tree = partial.SyntaxTree; + var root = tree.GetRoot(); + var rewriter = new RenameRewriter(originalName, newName); + var newRoot = rewriter.Visit(root); + var text = newRoot.ToFullString() + .Replace("#if WITH_VERSIONING", string.Empty) + .Replace("#endif", string.Empty); + context.AddSource($"{newName}_{index++}.g.cs", SourceText.From(text, Encoding.UTF8)); + } + } + } + + private static IEnumerable GetAllPartials(Compilation compilation, string className) + { + foreach (var tree in compilation.SyntaxTrees) + { + var root = tree.GetRoot(); + foreach (var cls in root.DescendantNodes().OfType()) + { + if (cls.Identifier.ValueText == className) + { + yield return cls; + } + } + } + } + + private sealed class RenameRewriter : CSharpSyntaxRewriter + { + private readonly string _oldName; + private readonly string _newName; + + public RenameRewriter(string oldName, string newName) + { + _oldName = oldName; + _newName = newName; + } + + public override SyntaxToken VisitToken(SyntaxToken token) + { + if (token.ValueText == _oldName) + { + return SyntaxFactory.Identifier(token.LeadingTrivia, _newName, token.TrailingTrivia); + } + + return base.VisitToken(token); + } + } + + private sealed class SyntaxReceiver : ISyntaxReceiver + { + public List Candidates { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is ClassDeclarationSyntax classDecl && classDecl.AttributeLists.Count > 0) + { + Candidates.Add(classDecl); + } + } + } + } +} diff --git a/VersionedCollectionsGenerator/VersionedCollectionsGenerator.csproj b/VersionedCollectionsGenerator/VersionedCollectionsGenerator.csproj new file mode 100644 index 0000000..cc41630 --- /dev/null +++ b/VersionedCollectionsGenerator/VersionedCollectionsGenerator.csproj @@ -0,0 +1,19 @@ + + + + net8.0;net7.0;net6.0 + enable + enable + preview + True + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + +