diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a97f800a..228f1177 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - 'main' + - '34-investigate-possible-memory-leaks' jobs: test: diff --git a/Tests/YDotNet.Tests.Driver/Abstractions/ITask.cs b/Tests/YDotNet.Tests.Driver/Abstractions/ITask.cs new file mode 100644 index 00000000..66c34783 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Abstractions/ITask.cs @@ -0,0 +1,6 @@ +namespace YDotNet.Tests.Driver.Abstractions; + +public interface ITask +{ + Task Run(); +} diff --git a/Tests/YDotNet.Tests.Driver/Program.cs b/Tests/YDotNet.Tests.Driver/Program.cs new file mode 100644 index 00000000..bce7bd67 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Program.cs @@ -0,0 +1,5 @@ +// See https://aka.ms/new-console-template for more information + +using YDotNet.Tests.Driver.Tasks.Docs; + +new ObserveSubDocs().Run(); diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Create.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Create.cs new file mode 100644 index 00000000..a3f6a4ef --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Create.cs @@ -0,0 +1,40 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class Create : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + doc.Array($"sample-{i}"); + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Get.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Get.cs new file mode 100644 index 00000000..9c12f3bd --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Get.cs @@ -0,0 +1,52 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class Get : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + array.InsertRange( + transaction, index: 0, new[] + { + Input.Long(value: 2469L), + Input.Boolean(value: false) + }); + array.Get(transaction, index: 1); + transaction.Commit(); + + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/InsertRange.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/InsertRange.cs new file mode 100644 index 00000000..5cddc7fb --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/InsertRange.cs @@ -0,0 +1,46 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class InsertRange : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + array.InsertRange(transaction, index: 0, new[] { Input.Long(value: 2469L) }); + transaction.Commit(); + + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Iterate.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Iterate.cs new file mode 100644 index 00000000..e9b24a48 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Iterate.cs @@ -0,0 +1,53 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class Iterate : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + array.InsertRange( + transaction, index: 0, new[] + { + Input.Long(value: 2469L), + Input.Boolean(value: false), + Input.Undefined() + }); + array.Iterate(transaction).ToArray(); + transaction.Commit(); + + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Length.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Length.cs new file mode 100644 index 00000000..868f138b --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Length.cs @@ -0,0 +1,47 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class Length : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + array.InsertRange(transaction, index: 0, new[] { Input.Long(value: 2469L) }); + var _ = array.Length; + transaction.Commit(); + + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Move.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Move.cs new file mode 100644 index 00000000..899ad73e --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Move.cs @@ -0,0 +1,53 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class Move : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + array.InsertRange( + transaction, index: 0, new[] + { + Input.Long(value: 2469L), + Input.Boolean(value: false), + Input.Undefined() + }); + array.Move(transaction, sourceIndex: 0, targetIndex: 2); + transaction.Commit(); + + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Observe.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Observe.cs new file mode 100644 index 00000000..8b8a61dd --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/Observe.cs @@ -0,0 +1,49 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class Observe : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var subscription = array.Observe(_ => { }); + + var transaction = doc.WriteTransaction(); + array.InsertRange(transaction, index: 0, new[] { Input.Long(value: 2469L) }); + transaction.Commit(); + + array.Unobserve(subscription); + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Arrays/RemoveRange.cs b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/RemoveRange.cs new file mode 100644 index 00000000..df3cf743 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Arrays/RemoveRange.cs @@ -0,0 +1,54 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Arrays; + +public class RemoveRange : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tArrays:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var array = doc.Array($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + array.InsertRange( + transaction, index: 0, new[] + { + Input.Long(value: 2469L), + Input.Boolean(value: false), + Input.Null(), + Input.Undefined() + }); + array.RemoveRange(transaction, index: 1, length: 2); + transaction.Commit(); + + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Branches/StickyIndex.cs b/Tests/YDotNet.Tests.Driver/Tasks/Branches/StickyIndex.cs new file mode 100644 index 00000000..26777be9 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Branches/StickyIndex.cs @@ -0,0 +1,51 @@ +using YDotNet.Document; +using YDotNet.Document.StickyIndexes; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Branches; + +public class StickyIndex : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + transaction = doc.WriteTransaction(); + text.StickyIndex(transaction, index: 3, StickyAssociationType.After); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/Clone.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/Clone.cs new file mode 100644 index 00000000..3d33301f --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/Clone.cs @@ -0,0 +1,39 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class Clone : ITask +{ + public Task Run() + { + var count = 0; + var doc = new Doc(); + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocuments:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + doc.Clone().Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/Create.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/Create.cs new file mode 100644 index 00000000..93cfe43e --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/Create.cs @@ -0,0 +1,38 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class Create : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocuments:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + new Doc().Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/CreateWithOptions.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/CreateWithOptions.cs new file mode 100644 index 00000000..9b4d7e8d --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/CreateWithOptions.cs @@ -0,0 +1,50 @@ +using YDotNet.Document; +using YDotNet.Document.Options; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class CreateWithOptions : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 2_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocuments:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 20); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var options = new DocOptions + { + Encoding = DocEncoding.Utf8, + Guid = Guid.NewGuid().ToString(), + Id = (ulong) Random.Shared.NextInt64(), + AutoLoad = false, + CollectionId = "random-collection", + ShouldLoad = false, + SkipGarbageCollection = true + }; + + new Doc(options).Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveAfterTransaction.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveAfterTransaction.cs new file mode 100644 index 00000000..e67328f3 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveAfterTransaction.cs @@ -0,0 +1,48 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ObserveAfterTransaction : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocs:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + var subscription = doc.ObserveAfterTransaction(_ => { }); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + doc.UnobserveAfterTransaction(subscription); + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveClear.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveClear.cs new file mode 100644 index 00000000..4b5bc0e1 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveClear.cs @@ -0,0 +1,44 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ObserveClear : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocs:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + + doc.ObserveClear(_ => { }); + doc.Clear(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveSubDocs.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveSubDocs.cs new file mode 100644 index 00000000..1c858b41 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveSubDocs.cs @@ -0,0 +1,48 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ObserveSubDocs : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocs:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + doc.ObserveSubDocs(_ => { }); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveUpdatesV1.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveUpdatesV1.cs new file mode 100644 index 00000000..24f5a28d --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveUpdatesV1.cs @@ -0,0 +1,48 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ObserveUpdatesV1 : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocs:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + doc.ObserveUpdatesV1(_ => { }); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveUpdatesV2.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveUpdatesV2.cs new file mode 100644 index 00000000..20a241c0 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ObserveUpdatesV2.cs @@ -0,0 +1,48 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ObserveUpdatesV2 : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tDocs:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + doc.ObserveUpdatesV2(_ => { }); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadAutoLoad.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadAutoLoad.cs new file mode 100644 index 00000000..41cd4ffe --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadAutoLoad.cs @@ -0,0 +1,46 @@ +using YDotNet.Document; +using YDotNet.Document.Options; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ReadAutoLoad : ITask +{ + public Task Run() + { + var count = 0; + var doc = new Doc( + new DocOptions + { + AutoLoad = true + }); + + // Read many times + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tReads:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var _ = doc.AutoLoad; + count++; + } + } + + doc.Dispose(); + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadCollectionId.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadCollectionId.cs new file mode 100644 index 00000000..d524b71f --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadCollectionId.cs @@ -0,0 +1,46 @@ +using YDotNet.Document; +using YDotNet.Document.Options; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ReadCollectionId : ITask +{ + public Task Run() + { + var count = 0; + var doc = new Doc( + new DocOptions + { + CollectionId = "sample-collection" + }); + + // Read many times + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tReads:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var _ = doc.CollectionId; + count++; + } + } + + doc.Dispose(); + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadGuid.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadGuid.cs new file mode 100644 index 00000000..5edc70a3 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadGuid.cs @@ -0,0 +1,46 @@ +using YDotNet.Document; +using YDotNet.Document.Options; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ReadGuid : ITask +{ + public Task Run() + { + var count = 0; + var doc = new Doc( + new DocOptions + { + Guid = Guid.NewGuid().ToString() + }); + + // Read many times + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tReads:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var _ = doc.Guid; + count++; + } + } + + doc.Dispose(); + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadId.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadId.cs new file mode 100644 index 00000000..a12a058e --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadId.cs @@ -0,0 +1,41 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ReadId : ITask +{ + public Task Run() + { + var count = 0; + var doc = new Doc(); + + // Read many times + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tReads:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var _ = doc.Id; + count++; + } + } + + doc.Dispose(); + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadShouldLoad.cs b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadShouldLoad.cs new file mode 100644 index 00000000..134a76e5 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Docs/ReadShouldLoad.cs @@ -0,0 +1,46 @@ +using YDotNet.Document; +using YDotNet.Document.Options; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Docs; + +public class ReadShouldLoad : ITask +{ + public Task Run() + { + var count = 0; + var doc = new Doc( + new DocOptions + { + Guid = Guid.NewGuid().ToString() + }); + + // Read many times + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tReads:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var _ = doc.ShouldLoad; + count++; + } + } + + doc.Dispose(); + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/Chunks.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Chunks.cs new file mode 100644 index 00000000..cea2e513 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Chunks.cs @@ -0,0 +1,51 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class Chunks : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + transaction = doc.ReadTransaction(); + var chunks = text.Chunks(transaction); + transaction.Commit(); + + chunks.Dispose(); + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/Create.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Create.cs new file mode 100644 index 00000000..1dbbbf70 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Create.cs @@ -0,0 +1,40 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class Create : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + doc.Text($"sample-{i}"); + doc.Dispose(); + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/FormatText.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/FormatText.cs new file mode 100644 index 00000000..edd982a2 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/FormatText.cs @@ -0,0 +1,63 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class FormatText : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + var inputs = new Dictionary + { + { "italic", Input.Boolean(value: true) } + }; + var input = Input.Object(inputs); + + transaction = doc.WriteTransaction(); + text.Format(transaction, index: 3, length: 4, input); + transaction.Commit(); + + foreach (var value in inputs.Values) + { + value.Dispose(); + } + + input.Dispose(); + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/InsertEmbed.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/InsertEmbed.cs new file mode 100644 index 00000000..32a52d15 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/InsertEmbed.cs @@ -0,0 +1,66 @@ +using YDotNet.Document; +using YDotNet.Document.Cells; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class InsertEmbed : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + + var inputs = new Dictionary + { + { "null", Input.Null() }, + { "false", Input.Boolean(value: false) }, + { "true", Input.Boolean(value: true) }, + { "bytes", Input.Bytes(new byte[] { 24, 69 }) }, + { "double", Input.Double(value: 4.20) }, + { "long", Input.Long(value: 1337L) }, + { "string", Input.String("Lucas") } + }; + var input = Input.Object(inputs); + text.InsertEmbed(transaction, index: 0, input); + + transaction.Commit(); + + foreach (var value in inputs.Values) + { + value.Dispose(); + } + + input.Dispose(); + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/InsertText.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/InsertText.cs new file mode 100644 index 00000000..a443588e --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/InsertText.cs @@ -0,0 +1,46 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class InsertText : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/Length.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Length.cs new file mode 100644 index 00000000..e947b5d3 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Length.cs @@ -0,0 +1,50 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class Length : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + transaction = doc.ReadTransaction(); + text.Length(transaction); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/Observe.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Observe.cs new file mode 100644 index 00000000..bab4aa34 --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/Observe.cs @@ -0,0 +1,48 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class Observe : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + var subscription = text.Observe(_ => { }); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + text.Unobserve(subscription); + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/RemoveText.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/RemoveText.cs new file mode 100644 index 00000000..4c42896d --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/RemoveText.cs @@ -0,0 +1,50 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class RemoveText : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + transaction = doc.WriteTransaction(); + text.RemoveRange(transaction, index: 0, length: 7); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/Tasks/Texts/String.cs b/Tests/YDotNet.Tests.Driver/Tasks/Texts/String.cs new file mode 100644 index 00000000..d94365ed --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/Tasks/Texts/String.cs @@ -0,0 +1,50 @@ +using YDotNet.Document; +using YDotNet.Tests.Driver.Abstractions; + +namespace YDotNet.Tests.Driver.Tasks.Texts; + +public class String : ITask +{ + public Task Run() + { + var count = 0; + + // Create many documents + while (count < 1_000_000) + { + // After 1s, stop and show the user the amount of documents + if (count > 0) + { + Console.WriteLine("Status Report"); + Console.WriteLine($"\tTexts:\t{count}"); + Console.WriteLine(); + } + + if (count % 1_000 == 0) + { + Thread.Sleep(millisecondsTimeout: 15); + } + + // Create many documents + for (var i = 0; i < 100; i++) + { + var doc = new Doc(); + var text = doc.Text($"sample-{i}"); + + var transaction = doc.WriteTransaction(); + text.Insert(transaction, index: 0, "YDotNet"); + transaction.Commit(); + + transaction = doc.ReadTransaction(); + text.String(transaction); + transaction.Commit(); + + doc.Dispose(); + + count++; + } + } + + return Task.CompletedTask; + } +} diff --git a/Tests/YDotNet.Tests.Driver/YDotNet.Tests.Driver.csproj b/Tests/YDotNet.Tests.Driver/YDotNet.Tests.Driver.csproj new file mode 100644 index 00000000..8779fb6b --- /dev/null +++ b/Tests/YDotNet.Tests.Driver/YDotNet.Tests.Driver.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/Tests/YDotNet.Tests.Unit/StickyIndexes/AssociationTypeTests.cs b/Tests/YDotNet.Tests.Unit/StickyIndexes/AssociationTypeTests.cs index 75391de7..1da6d378 100644 --- a/Tests/YDotNet.Tests.Unit/StickyIndexes/AssociationTypeTests.cs +++ b/Tests/YDotNet.Tests.Unit/StickyIndexes/AssociationTypeTests.cs @@ -14,8 +14,8 @@ public void ReturnsCorrectlyWithBefore() var text = doc.Text("text"); var transaction = doc.WriteTransaction(); - text.Insert(transaction, 0, "Lucas"); - var stickyIndex = text.StickyIndex(transaction, 3, StickyAssociationType.Before); + text.Insert(transaction, index: 0, "Lucas"); + var stickyIndex = text.StickyIndex(transaction, index: 3, StickyAssociationType.Before); transaction.Commit(); // Act @@ -33,8 +33,8 @@ public void ReturnsCorrectlyWithAfter() var text = doc.Text("text"); var transaction = doc.WriteTransaction(); - text.Insert(transaction, 0, "Lucas"); - var stickyIndex = text.StickyIndex(transaction, 3, StickyAssociationType.After); + text.Insert(transaction, index: 0, "Lucas"); + var stickyIndex = text.StickyIndex(transaction, index: 3, StickyAssociationType.After); transaction.Commit(); // Act diff --git a/YDotNet.sln b/YDotNet.sln index 0de44951..37fb1cbe 100644 --- a/YDotNet.sln +++ b/YDotNet.sln @@ -5,6 +5,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{03858045 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YDotNet.Tests.Unit", "Tests\YDotNet.Tests.Unit\YDotNet.Tests.Unit.csproj", "{F85C9334-87AF-4FFC-A301-3A9F57ECD4AB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YDotNet.Tests.Driver", "Tests\YDotNet.Tests.Driver\YDotNet.Tests.Driver.csproj", "{7ADFABD0-CDD2-4F1C-A39F-BF3A9891265F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,8 +21,13 @@ Global {F85C9334-87AF-4FFC-A301-3A9F57ECD4AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {F85C9334-87AF-4FFC-A301-3A9F57ECD4AB}.Release|Any CPU.ActiveCfg = Debug|Any CPU {F85C9334-87AF-4FFC-A301-3A9F57ECD4AB}.Release|Any CPU.Build.0 = Debug|Any CPU + {7ADFABD0-CDD2-4F1C-A39F-BF3A9891265F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7ADFABD0-CDD2-4F1C-A39F-BF3A9891265F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7ADFABD0-CDD2-4F1C-A39F-BF3A9891265F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7ADFABD0-CDD2-4F1C-A39F-BF3A9891265F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F85C9334-87AF-4FFC-A301-3A9F57ECD4AB} = {03858045-3849-41E2-ACE9-F10AAA4CCBCA} + {7ADFABD0-CDD2-4F1C-A39F-BF3A9891265F} = {03858045-3849-41E2-ACE9-F10AAA4CCBCA} EndGlobalSection EndGlobal diff --git a/YDotNet/Document/Cells/InputPointer.cs b/YDotNet/Document/Cells/InputPointer.cs index b3d1e280..d8b44995 100644 --- a/YDotNet/Document/Cells/InputPointer.cs +++ b/YDotNet/Document/Cells/InputPointer.cs @@ -22,9 +22,18 @@ internal InputPointer(InputNative inputNative, nint pointer) InputNative = inputNative; } + /// + /// Finalizes an instance of the class. + /// + ~InputPointer() + { + Dispose(); + } + /// public override void Dispose() { MemoryWriter.Release(pointer); + GC.SuppressFinalize(this); } } diff --git a/YDotNet/Document/Cells/InputPointerArray.cs b/YDotNet/Document/Cells/InputPointerArray.cs index 9f691c4c..41875151 100644 --- a/YDotNet/Document/Cells/InputPointerArray.cs +++ b/YDotNet/Document/Cells/InputPointerArray.cs @@ -22,9 +22,18 @@ internal InputPointerArray(InputNative inputNative, nint[] pointers) InputNative = inputNative; } + /// + /// Finalizes an instance of the class. + /// + ~InputPointerArray() + { + Dispose(); + } + /// public override void Dispose() { MemoryWriter.ReleaseArray(pointers); + GC.SuppressFinalize(this); } } diff --git a/YDotNet/Document/Cells/Output.cs b/YDotNet/Document/Cells/Output.cs index 58342f4b..8c0028ed 100644 --- a/YDotNet/Document/Cells/Output.cs +++ b/YDotNet/Document/Cells/Output.cs @@ -41,7 +41,7 @@ internal Output(nint handle, bool disposable = false) /// /// Gets the or null if this output cell contains a different type stored. /// - public Doc? Doc => ReferenceAccessor.Access(new Doc(OutputChannel.Doc(Handle))); + public Doc? Doc => ReferenceAccessor.Access(new Doc(OutputChannel.Doc(Handle), disposable: false)); /// /// Gets the or null if this output cell contains a different type stored. @@ -197,5 +197,14 @@ public void Dispose() } OutputChannel.Destroy(Handle); + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the class. + /// + ~Output() + { + Dispose(); } } diff --git a/YDotNet/Document/Doc.cs b/YDotNet/Document/Doc.cs index 0fadc52a..56853832 100644 --- a/YDotNet/Document/Doc.cs +++ b/YDotNet/Document/Doc.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using YDotNet.Document.Events; using YDotNet.Document.Options; using YDotNet.Document.Transactions; @@ -9,6 +10,7 @@ using YDotNet.Infrastructure; using YDotNet.Native.Document; using YDotNet.Native.Document.Events; +using YDotNet.Native.Types; using Array = YDotNet.Document.Types.Arrays.Array; namespace YDotNet.Document; @@ -32,6 +34,8 @@ namespace YDotNet.Document; /// public class Doc : IDisposable { + private readonly bool disposable = true; + /// /// Initializes a new instance of the class. /// @@ -50,15 +54,25 @@ public Doc() /// The options to be used when initializing this document. public Doc(DocOptions options) { - Handle = DocChannel.NewWithOptions(DocOptionsNative.From(options)); + var optionsNative = DocOptionsNative.From(options); + + Handle = DocChannel.NewWithOptions(optionsNative); + + optionsNative.Dispose(); } /// /// Initializes a new instance of the class with the specified . /// /// The pointer to be used by this document to manage the native resource. - internal Doc(nint handle) + /// + /// The flag determines if the resource associated with should be disposed + /// by this instance. + /// + internal Doc(nint handle, bool disposable = true) { + this.disposable = disposable; + Handle = handle; } @@ -70,7 +84,18 @@ internal Doc(nint handle) /// /// Gets the unique document identifier of this instance. /// - public string Guid => MemoryReader.ReadUtf8String(DocChannel.Guid(Handle)); + public string Guid + { + get + { + var handle = DocChannel.Guid(Handle); + var result = MemoryReader.ReadUtf8String(handle); + + StringChannel.Destroy(handle); + + return result; + } + } /// /// Gets the collection identifier of this instance. @@ -82,7 +107,10 @@ public string? CollectionId { get { - MemoryReader.TryReadUtf8String(DocChannel.CollectionId(Handle), out var result); + var handle = DocChannel.CollectionId(Handle); + MemoryReader.TryReadUtf8String(handle, out var result); + + StringChannel.Destroy(handle); return result; } @@ -114,7 +142,21 @@ public string? CollectionId /// public void Dispose() { + if (!disposable) + { + return; + } + DocChannel.Destroy(Handle); + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the class. + /// + ~Doc() + { + Dispose(); } /// @@ -304,12 +346,13 @@ public void Load(Transaction transaction) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveClear(Action action) { - var subscriptionId = DocChannel.ObserveClear( - Handle, - nint.Zero, - (_, doc) => action(ClearEventNative.From(new Doc(doc)).ToClearEvent())); + // Don't finalize this instance because there's another instance responsible for it somewhere else. + DocChannel.ObserveClearCallback callback = (_, docHandle) => + action(ClearEventNative.From(new Doc(docHandle, disposable: false)).ToClearEvent()); + + var subscriptionId = DocChannel.ObserveClear(Handle, nint.Zero, callback); - return new EventSubscription(subscriptionId); + return new EventSubscription(subscriptionId, callback); } /// @@ -332,12 +375,12 @@ public void UnobserveClear(EventSubscription subscription) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveUpdatesV1(Action action) { - var subscriptionId = DocChannel.ObserveUpdatesV1( - Handle, - nint.Zero, - (_, length, data) => action(UpdateEventNative.From(length, data).ToUpdateEvent())); + DocChannel.ObserveUpdatesCallback callback = (_, length, data) => + action(UpdateEventNative.From(length, data).ToUpdateEvent()); + + var subscriptionId = DocChannel.ObserveUpdatesV1(Handle, nint.Zero, callback); - return new EventSubscription(subscriptionId); + return new EventSubscription(subscriptionId, callback); } /// @@ -360,12 +403,12 @@ public void UnobserveUpdatesV1(EventSubscription subscription) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveUpdatesV2(Action action) { - var subscriptionId = DocChannel.ObserveUpdatesV2( - Handle, - nint.Zero, - (_, length, data) => action(UpdateEventNative.From(length, data).ToUpdateEvent())); + DocChannel.ObserveUpdatesCallback callback = (_, length, data) => + action(UpdateEventNative.From(length, data).ToUpdateEvent()); - return new EventSubscription(subscriptionId); + var subscriptionId = DocChannel.ObserveUpdatesV2(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// @@ -388,12 +431,12 @@ public void UnobserveUpdatesV2(EventSubscription subscription) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveAfterTransaction(Action action) { - var subscriptionId = DocChannel.ObserveAfterTransaction( - Handle, - nint.Zero, - (_, afterTransactionEvent) => action(afterTransactionEvent.ToAfterTransactionEvent())); + DocChannel.ObserveAfterTransactionCallback callback = (_, eventHandle) => + action(Marshal.PtrToStructure(eventHandle).ToAfterTransactionEvent()); + + var subscriptionId = DocChannel.ObserveAfterTransaction(Handle, nint.Zero, callback); - return new EventSubscription(subscriptionId); + return new EventSubscription(subscriptionId, callback); } /// @@ -413,12 +456,12 @@ public void UnobserveAfterTransaction(EventSubscription subscription) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveSubDocs(Action action) { - var subscriptionId = DocChannel.ObserveSubDocs( - Handle, - nint.Zero, - (_, subDocsEvent) => action(subDocsEvent.ToSubDocsEvent())); + DocChannel.ObserveSubdocsCallback callback = (_, eventHandle) => + action(Marshal.PtrToStructure(eventHandle).ToSubDocsEvent()); + + var subscriptionId = DocChannel.ObserveSubDocs(Handle, nint.Zero, callback); - return new EventSubscription(subscriptionId); + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/Events/EventSubscription.cs b/YDotNet/Document/Events/EventSubscription.cs index 6a6dd492..3c797c2c 100644 --- a/YDotNet/Document/Events/EventSubscription.cs +++ b/YDotNet/Document/Events/EventSubscription.cs @@ -9,13 +9,23 @@ public class EventSubscription /// Initializes a new instance of the class. /// /// The ID used to identify this instance of . - internal EventSubscription(uint id) + /// The callback to be invoked when the related event is triggered. + internal EventSubscription(uint id, Delegate callback) { Id = id; + Callback = callback; } /// /// Gets the ID used to identify this subscription in the document. /// public uint Id { get; } + + /// + /// Gets the callback to be invoked when the related event is triggered. + /// + /// + /// A reference to the callback is stored to make sure it's not disposed by the GC before the unsubscription. + /// + public Delegate Callback { get; } } diff --git a/YDotNet/Document/StickyIndexes/StickyIndex.cs b/YDotNet/Document/StickyIndexes/StickyIndex.cs index 57ffa59a..38fdfa86 100644 --- a/YDotNet/Document/StickyIndexes/StickyIndex.cs +++ b/YDotNet/Document/StickyIndexes/StickyIndex.cs @@ -40,6 +40,15 @@ internal StickyIndex(nint handle) public void Dispose() { StickyIndexChannel.Destroy(Handle); + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the class. + /// + ~StickyIndex() + { + Dispose(); } /// diff --git a/YDotNet/Document/Transactions/Transaction.cs b/YDotNet/Document/Transactions/Transaction.cs index c073c6af..56a0f406 100644 --- a/YDotNet/Document/Transactions/Transaction.cs +++ b/YDotNet/Document/Transactions/Transaction.cs @@ -73,6 +73,10 @@ public Doc[] SubDocs() for (var i = 0; i < length; i++) { var doc = new Doc(Marshal.ReadIntPtr(handle, i * nint.Size)); + + // Don't finalize this instance because there's another instance responsible for it somewhere else. + GC.SuppressFinalize(doc); + docs[i] = doc; } diff --git a/YDotNet/Document/Types/Arrays/Array.cs b/YDotNet/Document/Types/Arrays/Array.cs index a1c8b16f..4031c7db 100644 --- a/YDotNet/Document/Types/Arrays/Array.cs +++ b/YDotNet/Document/Types/Arrays/Array.cs @@ -42,6 +42,8 @@ public void InsertRange(Transaction transaction, uint index, IEnumerable var inputsPointer = MemoryWriter.WriteStructArray(inputsArray); ArrayChannel.InsertRange(Handle, transaction.Handle, index, inputsPointer, (uint) inputsArray.Length); + + MemoryWriter.Release(inputsPointer); } /// @@ -107,12 +109,11 @@ public void Move(Transaction transaction, uint sourceIndex, uint targetIndex) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription Observe(Action action) { - var subscriptionId = ArrayChannel.Observe( - Handle, - nint.Zero, - (_, eventHandle) => action(new ArrayEvent(eventHandle))); + ArrayChannel.ObserveCallback callback = (_, eventHandle) => action(new ArrayEvent(eventHandle)); + + var subscriptionId = ArrayChannel.Observe(Handle, nint.Zero, callback); - return new EventSubscription(subscriptionId); + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/Types/Arrays/ArrayEnumerator.cs b/YDotNet/Document/Types/Arrays/ArrayEnumerator.cs index aa36e36f..2c2bf449 100644 --- a/YDotNet/Document/Types/Arrays/ArrayEnumerator.cs +++ b/YDotNet/Document/Types/Arrays/ArrayEnumerator.cs @@ -55,5 +55,14 @@ public void Reset() public void Dispose() { Iterator.Dispose(); + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the class. + /// + ~ArrayEnumerator() + { + Dispose(); } } diff --git a/YDotNet/Document/Types/Arrays/ArrayIterator.cs b/YDotNet/Document/Types/Arrays/ArrayIterator.cs index 5c0acfb2..10195b51 100644 --- a/YDotNet/Document/Types/Arrays/ArrayIterator.cs +++ b/YDotNet/Document/Types/Arrays/ArrayIterator.cs @@ -30,6 +30,7 @@ internal ArrayIterator(nint handle) public void Dispose() { ArrayChannel.IteratorDestroy(Handle); + GC.SuppressFinalize(this); } /// @@ -43,4 +44,12 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + /// + /// Finalizes an instance of the class. + /// + ~ArrayIterator() + { + Dispose(); + } } diff --git a/YDotNet/Document/Types/Branches/Branch.cs b/YDotNet/Document/Types/Branches/Branch.cs index d81eba8b..2ca1e64c 100644 --- a/YDotNet/Document/Types/Branches/Branch.cs +++ b/YDotNet/Document/Types/Branches/Branch.cs @@ -1,9 +1,7 @@ using YDotNet.Document.Events; -using YDotNet.Document.StickyIndexes; using YDotNet.Document.Transactions; using YDotNet.Document.Types.Events; using YDotNet.Infrastructure; -using YDotNet.Native.StickyIndex; using YDotNet.Native.Types.Branches; namespace YDotNet.Document.Types.Branches; @@ -38,19 +36,18 @@ protected Branch(nint handle) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveDeep(Action> action) { - var subscriptionId = BranchChannel.ObserveDeep( - Handle, - nint.Zero, - (_, length, eventsHandle) => - { - var events = MemoryReader.TryReadIntPtrArray(eventsHandle, length, size: 24)! - .Select(x => new EventBranch(x)) - .ToArray(); + BranchChannel.ObserveCallback callback = (_, length, eventsHandle) => + { + var events = MemoryReader.TryReadIntPtrArray(eventsHandle, length, size: 24)! + .Select(x => new EventBranch(x)) + .ToArray(); - action(events); - }); + action(events); + }; - return new EventSubscription(subscriptionId); + var subscriptionId = BranchChannel.ObserveDeep(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/Types/Maps/Map.cs b/YDotNet/Document/Types/Maps/Map.cs index 644d0173..7067d407 100644 --- a/YDotNet/Document/Types/Maps/Map.cs +++ b/YDotNet/Document/Types/Maps/Map.cs @@ -118,12 +118,11 @@ public void RemoveAll(Transaction transaction) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription Observe(Action action) { - var subscriptionId = MapChannel.Observe( - Handle, - nint.Zero, - (_, eventHandle) => action(new MapEvent(eventHandle))); + MapChannel.ObserveCallback callback = (_, eventHandle) => action(new MapEvent(eventHandle)); - return new EventSubscription(subscriptionId); + var subscriptionId = MapChannel.Observe(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/Types/Texts/Text.cs b/YDotNet/Document/Types/Texts/Text.cs index 9ce2cb11..4a192de0 100644 --- a/YDotNet/Document/Types/Texts/Text.cs +++ b/YDotNet/Document/Types/Texts/Text.cs @@ -149,12 +149,11 @@ public uint Length(Transaction transaction) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription Observe(Action action) { - var subscriptionId = TextChannel.Observe( - Handle, - nint.Zero, - (_, eventHandle) => action(new TextEvent(eventHandle))); + TextChannel.ObserveCallback callback = (_, eventHandle) => action(new TextEvent(eventHandle)); - return new EventSubscription(subscriptionId); + var subscriptionId = TextChannel.Observe(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/Types/Texts/TextChunks.cs b/YDotNet/Document/Types/Texts/TextChunks.cs index f7fd747a..a606cfab 100644 --- a/YDotNet/Document/Types/Texts/TextChunks.cs +++ b/YDotNet/Document/Types/Texts/TextChunks.cs @@ -40,6 +40,7 @@ internal TextChunks(nint handle, uint length) public void Dispose() { ChunksChannel.Destroy(Handle, Length); + GC.SuppressFinalize(this); } /// @@ -53,4 +54,12 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + /// + /// Finalizes an instance of the class. + /// + ~TextChunks() + { + Dispose(); + } } diff --git a/YDotNet/Document/Types/XmlElements/XmlElement.cs b/YDotNet/Document/Types/XmlElements/XmlElement.cs index 7193de9c..f32e1502 100644 --- a/YDotNet/Document/Types/XmlElements/XmlElement.cs +++ b/YDotNet/Document/Types/XmlElements/XmlElement.cs @@ -282,12 +282,11 @@ public void RemoveRange(Transaction transaction, uint index, uint length) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription Observe(Action action) { - var subscriptionId = XmlElementChannel.Observe( - Handle, - nint.Zero, - (_, eventHandle) => action(new XmlElementEvent(eventHandle))); + XmlElementChannel.ObserveCallback callback = (_, eventHandle) => action(new XmlElementEvent(eventHandle)); - return new EventSubscription(subscriptionId); + var subscriptionId = XmlElementChannel.Observe(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/Types/XmlTexts/XmlText.cs b/YDotNet/Document/Types/XmlTexts/XmlText.cs index 32ead3a0..d9203ee6 100644 --- a/YDotNet/Document/Types/XmlTexts/XmlText.cs +++ b/YDotNet/Document/Types/XmlTexts/XmlText.cs @@ -227,12 +227,11 @@ public void Format(Transaction transaction, uint index, uint length, Input attri /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription Observe(Action action) { - var subscriptionId = XmlTextChannel.Observe( - Handle, - nint.Zero, - (_, eventHandle) => action(new XmlTextEvent(eventHandle))); + XmlTextChannel.ObserveCallback callback = (_, eventHandle) => action(new XmlTextEvent(eventHandle)); - return new EventSubscription(subscriptionId); + var subscriptionId = XmlTextChannel.Observe(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Document/UndoManagers/UndoManager.cs b/YDotNet/Document/UndoManagers/UndoManager.cs index 8c30bd99..ce44878e 100644 --- a/YDotNet/Document/UndoManagers/UndoManager.cs +++ b/YDotNet/Document/UndoManagers/UndoManager.cs @@ -47,12 +47,11 @@ public void Dispose() /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObserveAdded(Action action) { - var subscriptionId = UndoManagerChannel.ObserveAdded( - Handle, - nint.Zero, - (_, undoEvent) => action(undoEvent.ToUndoEvent())); + UndoManagerChannel.ObserveAddedCallback callback = (_, undoEvent) => action(undoEvent.ToUndoEvent()); - return new EventSubscription(subscriptionId); + var subscriptionId = UndoManagerChannel.ObserveAdded(Handle, nint.Zero, callback); + + return new EventSubscription(subscriptionId, callback); } /// @@ -73,12 +72,11 @@ public void UnobserveAdded(EventSubscription subscription) /// The subscription for the event. It may be used to unsubscribe later. public EventSubscription ObservePopped(Action action) { - var subscriptionId = UndoManagerChannel.ObservePopped( - Handle, - nint.Zero, - (_, undoEvent) => action(undoEvent.ToUndoEvent())); + UndoManagerChannel.ObservePoppedCallback callback = (_, undoEvent) => action(undoEvent.ToUndoEvent()); + + var subscriptionId = UndoManagerChannel.ObservePopped(Handle, nint.Zero, callback); - return new EventSubscription(subscriptionId); + return new EventSubscription(subscriptionId, callback); } /// diff --git a/YDotNet/Infrastructure/ReferenceAccessor.cs b/YDotNet/Infrastructure/ReferenceAccessor.cs index ed865725..915df426 100644 --- a/YDotNet/Infrastructure/ReferenceAccessor.cs +++ b/YDotNet/Infrastructure/ReferenceAccessor.cs @@ -82,6 +82,18 @@ internal static class ReferenceAccessor private static T? Access(T instance, nint pointer) where T : class { - return pointer == nint.Zero ? null : instance; + if (pointer == nint.Zero) + { + if (instance is IDisposable) + { + // Instances with null pointers do not need to be finalized + // because they are not holding any native resources. + GC.SuppressFinalize(instance); + } + + return null; + } + + return instance; } } diff --git a/YDotNet/Native/Document/DocChannel.cs b/YDotNet/Native/Document/DocChannel.cs index a7e3060b..2e37ac80 100644 --- a/YDotNet/Native/Document/DocChannel.cs +++ b/YDotNet/Native/Document/DocChannel.cs @@ -1,15 +1,14 @@ using System.Runtime.InteropServices; -using YDotNet.Native.Document.Events; namespace YDotNet.Native.Document; internal static class DocChannel { - public delegate void ObserveAfterTransactionCallback(nint state, AfterTransactionEventNative afterTransactionEvent); + public delegate void ObserveAfterTransactionCallback(nint state, nint eventHandle); - public delegate void ObserveClearCallback(nint state, nint doc); + public delegate void ObserveClearCallback(nint state, nint docHandle); - public delegate void ObserveSubdocsCallback(nint state, SubDocsEventNative subDocsEvent); + public delegate void ObserveSubdocsCallback(nint state, nint eventHandle); public delegate void ObserveUpdatesCallback(nint state, uint length, nint data);