aboutsummaryrefslogtreecommitdiff
path: root/Utils
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-08 14:44:01 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-08 14:44:01 -0500
commitbe6dc557a3b819248b014992eb96c1cb21f8112b (patch)
tree5361530552856ba8154bfcfbfac8377549117c9e /Utils
parent072a1294646542a73007784d08a35ffcad557b1b (diff)
Initial commit
Diffstat (limited to 'Utils')
-rw-r--r--Utils/LICENSE.txt293
-rw-r--r--Utils/README.md36
-rw-r--r--Utils/src/Async/AccessSerializer.cs297
-rw-r--r--Utils/src/Async/AsyncExclusiveResource.cs169
-rw-r--r--Utils/src/Async/AsyncQueue.cs144
-rw-r--r--Utils/src/Async/AsyncUpdatableResource.cs111
-rw-r--r--Utils/src/Async/Exceptions/AsyncUpdateException.cs52
-rw-r--r--Utils/src/Async/IAsyncExclusiveResource.cs40
-rw-r--r--Utils/src/Async/IAsyncWaitHandle.cs41
-rw-r--r--Utils/src/Async/IWaitHandle.cs59
-rw-r--r--Utils/src/BitField.cs115
-rw-r--r--Utils/src/ERRNO.cs152
-rw-r--r--Utils/src/Extensions/CacheExtensions.cs348
-rw-r--r--Utils/src/Extensions/CollectionExtensions.cs98
-rw-r--r--Utils/src/Extensions/IoExtensions.cs345
-rw-r--r--Utils/src/Extensions/JsonExtensions.cs215
-rw-r--r--Utils/src/Extensions/MemoryExtensions.cs769
-rw-r--r--Utils/src/Extensions/MutexReleaser.cs62
-rw-r--r--Utils/src/Extensions/SafeLibraryExtensions.cs103
-rw-r--r--Utils/src/Extensions/SemSlimReleaser.cs51
-rw-r--r--Utils/src/Extensions/StringExtensions.cs481
-rw-r--r--Utils/src/Extensions/ThreadingExtensions.cs226
-rw-r--r--Utils/src/Extensions/TimerExtensions.cs66
-rw-r--r--Utils/src/Extensions/VnStringExtensions.cs418
-rw-r--r--Utils/src/IIndexable.cs43
-rw-r--r--Utils/src/IO/ArrayPoolStreamBuffer.cs70
-rw-r--r--Utils/src/IO/BackingStream.cs181
-rw-r--r--Utils/src/IO/FileOperations.cs105
-rw-r--r--Utils/src/IO/IDataAccumulator.cs64
-rw-r--r--Utils/src/IO/ISlindingWindowBuffer.cs91
-rw-r--r--Utils/src/IO/IVnTextReader.cs72
-rw-r--r--Utils/src/IO/InMemoryTemplate.cs196
-rw-r--r--Utils/src/IO/IsolatedStorageDirectory.cs154
-rw-r--r--Utils/src/IO/SlidingWindowBufferExtensions.cs213
-rw-r--r--Utils/src/IO/TemporayIsolatedFile.cs57
-rw-r--r--Utils/src/IO/VnMemoryStream.cs469
-rw-r--r--Utils/src/IO/VnStreamReader.cs180
-rw-r--r--Utils/src/IO/VnStreamWriter.cs292
-rw-r--r--Utils/src/IO/VnTextReaderExtensions.cs223
-rw-r--r--Utils/src/IO/WriteOnlyBufferedStream.cs255
-rw-r--r--Utils/src/IObjectStorage.cs48
-rw-r--r--Utils/src/Logging/ILogProvider.cs79
-rw-r--r--Utils/src/Logging/LogLevel.cs33
-rw-r--r--Utils/src/Logging/LoggerExtensions.cs60
-rw-r--r--Utils/src/Memory/Caching/ICacheHolder.cs45
-rw-r--r--Utils/src/Memory/Caching/ICacheable.cs44
-rw-r--r--Utils/src/Memory/Caching/IObjectRental.cs47
-rw-r--r--Utils/src/Memory/Caching/IReusable.cs42
-rw-r--r--Utils/src/Memory/Caching/LRUCache.cs127
-rw-r--r--Utils/src/Memory/Caching/LRUDataStore.cs232
-rw-r--r--Utils/src/Memory/Caching/ObjectRental.cs236
-rw-r--r--Utils/src/Memory/Caching/ObjectRentalBase.cs155
-rw-r--r--Utils/src/Memory/Caching/ReusableStore.cs61
-rw-r--r--Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs76
-rw-r--r--Utils/src/Memory/Caching/ThreadLocalReusableStore.cs64
-rw-r--r--Utils/src/Memory/ForwardOnlyBufferWriter.cs122
-rw-r--r--Utils/src/Memory/ForwardOnlyMemoryReader.cs74
-rw-r--r--Utils/src/Memory/ForwardOnlyMemoryWriter.cs122
-rw-r--r--Utils/src/Memory/ForwardOnlyReader.cs74
-rw-r--r--Utils/src/Memory/IMemoryHandle.cs53
-rw-r--r--Utils/src/Memory/IStringSerializeable.cs55
-rw-r--r--Utils/src/Memory/IUnmangedHeap.cs59
-rw-r--r--Utils/src/Memory/Memory.cs456
-rw-r--r--Utils/src/Memory/MemoryHandle.cs237
-rw-r--r--Utils/src/Memory/PrivateBuffersMemoryPool.cs67
-rw-r--r--Utils/src/Memory/PrivateHeap.cs184
-rw-r--r--Utils/src/Memory/PrivateString.cs185
-rw-r--r--Utils/src/Memory/PrivateStringManager.cs117
-rw-r--r--Utils/src/Memory/ProcessHeap.cs82
-rw-r--r--Utils/src/Memory/RpMallocPrivateHeap.cs279
-rw-r--r--Utils/src/Memory/SubSequence.cs113
-rw-r--r--Utils/src/Memory/SysBufferMemoryManager.cs102
-rw-r--r--Utils/src/Memory/UnmanagedHeapBase.cs185
-rw-r--r--Utils/src/Memory/UnsafeMemoryHandle.cs231
-rw-r--r--Utils/src/Memory/VnString.cs497
-rw-r--r--Utils/src/Memory/VnTable.cs213
-rw-r--r--Utils/src/Memory/VnTempBuffer.cs207
-rw-r--r--Utils/src/Native/SafeLibraryHandle.cs220
-rw-r--r--Utils/src/Native/SafeMethodHandle.cs61
-rw-r--r--Utils/src/NativeLibraryException.cs89
-rw-r--r--Utils/src/Resources/BackedResourceBase.cs79
-rw-r--r--Utils/src/Resources/CallbackOpenHandle.cs44
-rw-r--r--Utils/src/Resources/ExclusiveResourceHandle.cs81
-rw-r--r--Utils/src/Resources/IExclusiveResource.cs39
-rw-r--r--Utils/src/Resources/IResource.cs38
-rw-r--r--Utils/src/Resources/OpenHandle.cs38
-rw-r--r--Utils/src/Resources/OpenResourceHandle.cs44
-rw-r--r--Utils/src/Resources/ResourceDeleteFailedException.cs40
-rw-r--r--Utils/src/Resources/ResourceUpdateFailedException.cs40
-rw-r--r--Utils/src/Resources/UpdatableResource.cs113
-rw-r--r--Utils/src/VNLib.Utils.csproj47
-rw-r--r--Utils/src/VnDisposeable.cs85
-rw-r--r--Utils/src/VnEncoding.cs914
-rw-r--r--Utils/tests/ERRNOTest.cs48
-rw-r--r--Utils/tests/Memory/MemoryHandleTest.cs183
-rw-r--r--Utils/tests/Memory/MemoryTests.cs244
-rw-r--r--Utils/tests/Memory/VnTableTests.cs168
-rw-r--r--Utils/tests/README.md0
-rw-r--r--Utils/tests/VNLib.UtilsTests.csproj36
-rw-r--r--Utils/tests/VnEncodingTests.cs100
100 files changed, 15170 insertions, 0 deletions
diff --git a/Utils/LICENSE.txt b/Utils/LICENSE.txt
new file mode 100644
index 0000000..cbb3969
--- /dev/null
+++ b/Utils/LICENSE.txt
@@ -0,0 +1,293 @@
+Copyright (c) 2022 Vaughn Nugent
+
+Contact information
+ Name: Vaughn Nugent
+ Email: public[at]vaughnnugent[dot]com
+ Website: https://www.vaughnnugent.com
+
+The software in this repository is licensed under the GNU GPL version 2.0 (or any later version).
+
+SPDX-License-Identifier: GPL-2.0-or-later
+
+License-Text:
+
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS \ No newline at end of file
diff --git a/Utils/README.md b/Utils/README.md
new file mode 100644
index 0000000..ddd7e84
--- /dev/null
+++ b/Utils/README.md
@@ -0,0 +1,36 @@
+# VNLib.Utils
+
+A .NET/C# library for common .NET operation optimizations.
+
+namespaces
+- VNLib.Utils.Async - Provides classes for asynchronous access synchronization and some asynchronous collections
+- VNLib.Utils.Extensions - Internal and external extensions for condensed common operations
+- VNLib.Utils.IO - Memory focused data structures for IO operations
+- VNLib.Utils.Logging - Logging interfaces for zero dependency logging
+- VNLib.Utils.Memory - Utilities for safely accessing unmanaged memory and CLR memory
+- VNLib.Utils.Memory.Caching - Data structures for managed object, data caching, and interfaces for cache-able objects
+
+
+### Recommended 3rd Party Libs
+
+This library does not *require* any direct dependencies, however there are some optional ones that are desired for higher performance code. This library does not, modify, contribute, or affect the functionality of any of the 3rd party libraries recommended below.
+
+[**RPMalloc**](https://github.com/mjansson/rpmalloc) By Mattias Jansson - VNlib.Utils.Memory (and sub-classes) may load and bind function calls to this native library determined by environment variables. To use RPMalloc as the default unmanaged allocator simply add the dynamic library to the native lib search path, such as in the executable directory, and set the allocator environment variable as instructed below.
+
+
+### Allocator selection via environment variables
+Valid allocator value for the `VNLIB_SHARED_HEAP_TYPE` environment variable:
+- "win32" - for win32 based private heaps (only valid if using the Microsoft Windows operating system)
+- "rpmalloc" - to load the RPMalloc native library if compiled for your platform
+- none - the default value, will attempt to load the win32 private heap Kernel32 library, otherwise, the native ProcessHeap() cross platform allocator
+
+
+## Usage
+A usage breakdown would be far to lengthy for this library, and instead I intend to keep valid and comprehensive documentation in Visual Studio XML documentation files included in this project's src directory.
+
+This library is a utilities library and therefor may be directly included in your application or libraries,
+
+### License
+
+The software in this repository is licensed under the GNU GPL version 2.0 (or any later version).
+See the LICENSE files for more information. \ No newline at end of file
diff --git a/Utils/src/Async/AccessSerializer.cs b/Utils/src/Async/AccessSerializer.cs
new file mode 100644
index 0000000..ce78f6c
--- /dev/null
+++ b/Utils/src/Async/AccessSerializer.cs
@@ -0,0 +1,297 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: AccessSerializer.cs
+*
+* AccessSerializer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Provides access arbitration to an exclusive resouce
+ /// </summary>
+ /// <typeparam name="TKey">The uinique identifier type for the resource</typeparam>
+ /// <typeparam name="TResource">The resource type</typeparam>
+ public sealed class AccessSerializer<TKey, TResource> where TResource : IExclusiveResource
+ {
+ private readonly SemaphoreSlim semaphore;
+ private readonly Func<TKey, TResource> Factory;
+ private readonly Action CompletedCb;
+ private int WaitingCount;
+ /// <summary>
+ /// Creates a new <see cref="AccessSerializer{K, T}"/> with the specified factory and completed callback
+ /// </summary>
+ /// <param name="factory">Factory function to genereate new <typeparamref name="TResource"/> objects from <typeparamref name="TKey"/> keys</param>
+ /// <param name="completedCb">Function to be invoked when the encapsulated objected is no longer in use</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public AccessSerializer(Func<TKey, TResource> factory, Action completedCb)
+ {
+ this.Factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ this.CompletedCb = completedCb;
+ //Setup semaphore for locking
+ this.semaphore = new SemaphoreSlim(1, 1);
+ this.WaitingCount = 0;
+ }
+
+ /// <summary>
+ /// Attempts to obtain an exclusive lock on the object
+ /// </summary>
+ /// <param name="key"></param>
+ /// <param name="wait">Time to wait for lock</param>
+ /// <param name="exObj"></param>
+ /// <returns>true if lock was obtained within the timeout, false if the lock was not obtained</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public bool TryWait(TKey key, TimeSpan wait, out ExclusiveResourceHandle<TResource> exObj)
+ {
+ //Increase waiting count while we wait
+ Interlocked.Increment(ref WaitingCount);
+ try
+ {
+ //Try to obtain the lock
+ if (semaphore.Wait(wait))
+ {
+ TResource get() => Factory(key);
+ //Create new exclusive lock handle that will generate a new that calls release when freed
+ exObj = new(get, Release);
+ return true;
+ }
+ //Lock not taken
+ exObj = null;
+ return false;
+ }
+ finally
+ {
+ //Decrease the waiting count since we are no longer waiting
+ Interlocked.Decrement(ref WaitingCount);
+ }
+ }
+ /// <summary>
+ /// Waits for exclusive access to the resource.
+ /// </summary>
+ /// <param name="key"></param>
+ /// <returns>An <see cref="ExclusiveResourceHandle{T}"/> encapsulating the resource</returns>
+ public ExclusiveResourceHandle<TResource> Wait(TKey key)
+ {
+ try
+ {
+ //Increase waiting count while we wait
+ Interlocked.Increment(ref WaitingCount);
+ //Try to obtain the lock
+ semaphore.Wait();
+ //Local function to generate the output value
+ TResource get() => Factory(key);
+ //Create new exclusive lock handle that will generate a new that calls release when freed
+ return new(get, Release);
+ }
+ finally
+ {
+ //Decrease the waiting count since we are no longer waiting
+ Interlocked.Decrement(ref WaitingCount);
+ }
+ }
+ /// <summary>
+ /// Asynchronously waits for exclusive access to the resource.
+ /// </summary>
+ /// <returns>An <see cref="ExclusiveResourceHandle{TResource}"/> encapsulating the resource</returns>
+ public async Task<ExclusiveResourceHandle<TResource>> WaitAsync(TKey key, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ //Increase waiting count while we wait
+ Interlocked.Increment(ref WaitingCount);
+ //Try to obtain the lock
+ await semaphore.WaitAsync(cancellationToken);
+ //Local function to generate the output value
+ TResource get() => Factory(key);
+ //Create new exclusive lock handle that will generate a new that calls release when freed
+ return new(get, Release);
+ }
+ finally
+ {
+ //Decrease the waiting count since we are no longer waiting
+ Interlocked.Decrement(ref WaitingCount);
+ }
+ }
+ /// <summary>
+ /// Releases an exclusive lock that is held on an object
+ /// </summary>
+ private void Release()
+ {
+ /*
+ * If objects are waiting for the current instance, then we will release
+ * the semaphore and exit, as we no longer have control over the context
+ */
+ if (WaitingCount > 0)
+ {
+ this.semaphore.Release();
+ }
+ else
+ {
+ //Do not release the sempahore, just dispose of the semaphore
+ this.semaphore.Dispose();
+ //call the completed function
+ CompletedCb?.Invoke();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Provides access arbitration to an <see cref="IExclusiveResource"/>
+ /// </summary>
+ /// <typeparam name="TKey">The uinique identifier type for the resource</typeparam>
+ /// <typeparam name="TArg">The type of the optional argument to be passed to the user-implemented factory function</typeparam>
+ /// <typeparam name="TResource">The resource type</typeparam>
+ public sealed class AccessSerializer<TKey, TArg, TResource> where TResource : IExclusiveResource
+ {
+ private readonly SemaphoreSlim semaphore;
+ private readonly Func<TKey, TArg, TResource> Factory;
+ private readonly Action CompletedCb;
+ private int WaitingCount;
+ /// <summary>
+ /// Creates a new <see cref="AccessSerializer{TKey, TArg, TResource}"/> with the specified factory and completed callback
+ /// </summary>
+ /// <param name="factory">Factory function to genereate new <typeparamref name="TResource"/> objects from <typeparamref name="TKey"/> keys</param>
+ /// <param name="completedCb">Function to be invoked when the encapsulated objected is no longer in use</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public AccessSerializer(Func<TKey, TArg, TResource> factory, Action completedCb)
+ {
+ this.Factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ this.CompletedCb = completedCb;
+ //Setup semaphore for locking
+ this.semaphore = new SemaphoreSlim(1, 1);
+ this.WaitingCount = 0;
+ }
+
+ /// <summary>
+ /// Attempts to obtain an exclusive lock on the object
+ /// </summary>
+ /// <param name="key"></param>
+ /// <param name="arg">The key identifying the resource</param>
+ /// <param name="wait">Time to wait for lock</param>
+ /// <param name="exObj"></param>
+ /// <returns>true if lock was obtained within the timeout, false if the lock was not obtained</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public bool TryWait(TKey key, TArg arg, TimeSpan wait, out ExclusiveResourceHandle<TResource> exObj)
+ {
+ //Increase waiting count while we wait
+ Interlocked.Increment(ref WaitingCount);
+ try
+ {
+ //Try to obtain the lock
+ if (semaphore.Wait(wait))
+ {
+ TResource get() => Factory(key, arg);
+ //Create new exclusive lock handle that will generate a new that calls release when freed
+ exObj = new(get, Release);
+ return true;
+ }
+ //Lock not taken
+ exObj = null;
+ return false;
+ }
+ finally
+ {
+ //Decrease the waiting count since we are no longer waiting
+ Interlocked.Decrement(ref WaitingCount);
+ }
+ }
+ /// <summary>
+ /// Waits for exclusive access to the resource.
+ /// </summary>
+ /// <param name="key">The unique key that identifies the resource</param>
+ /// <param name="arg">The state argument to pass to the factory function</param>
+ /// <returns>An <see cref="ExclusiveResourceHandle{TResource}"/> encapsulating the resource</returns>
+ public ExclusiveResourceHandle<TResource> Wait(TKey key, TArg arg)
+ {
+ try
+ {
+ //Increase waiting count while we wait
+ Interlocked.Increment(ref WaitingCount);
+ //Try to obtain the lock
+ semaphore.Wait();
+ //Local function to generate the output value
+ TResource get() => Factory(key, arg);
+ //Create new exclusive lock handle that will generate a new that calls release when freed
+ return new(get, Release);
+ }
+ finally
+ {
+ //Decrease the waiting count since we are no longer waiting
+ Interlocked.Decrement(ref WaitingCount);
+ }
+ }
+ /// <summary>
+ /// Asynchronously waits for exclusive access to the resource.
+ /// </summary>
+ /// <param name="key"></param>
+ /// <param name="arg">The state argument to pass to the factory function</param>
+ /// <param name="cancellationToken"></param>
+ /// <returns>An <see cref="ExclusiveResourceHandle{TResource}"/> encapsulating the resource</returns>
+ public async Task<ExclusiveResourceHandle<TResource>> WaitAsync(TKey key, TArg arg, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ //Increase waiting count while we wait
+ Interlocked.Increment(ref WaitingCount);
+ //Try to obtain the lock
+ await semaphore.WaitAsync(cancellationToken);
+ //Local function to generate the output value
+ TResource get() => Factory(key, arg);
+ //Create new exclusive lock handle that will generate a new that calls release when freed
+ return new(get, Release);
+ }
+ finally
+ {
+ //Decrease the waiting count since we are no longer waiting
+ Interlocked.Decrement(ref WaitingCount);
+ }
+ }
+
+ /// <summary>
+ /// Releases an exclusive lock that is held on an object
+ /// </summary>
+ private void Release()
+ {
+ /*
+ * If objects are waiting for the current instance, then we will release
+ * the semaphore and exit, as we no longer have control over the context
+ */
+ if (WaitingCount > 0)
+ {
+ this.semaphore.Release();
+ }
+ else
+ {
+ //Do not release the sempahore, just dispose of the semaphore
+ this.semaphore.Dispose();
+ //call the completed function
+ CompletedCb?.Invoke();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Async/AsyncExclusiveResource.cs b/Utils/src/Async/AsyncExclusiveResource.cs
new file mode 100644
index 0000000..18e2a42
--- /dev/null
+++ b/Utils/src/Async/AsyncExclusiveResource.cs
@@ -0,0 +1,169 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: AsyncExclusiveResource.cs
+*
+* AsyncExclusiveResource.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Provides a base class for resources that must be obtained exclusivly in a multi-threaded environment
+ /// but allow state update operations (and their exceptions) to be deferred to the next accessor.
+ /// </summary>
+ /// <typeparam name="TState">The state parameter type passed during updates</typeparam>
+ public abstract class AsyncExclusiveResource<TState> : VnDisposeable, IWaitHandle, IAsyncWaitHandle
+ {
+ /// <summary>
+ /// Main mutli-threading lock used for primary access synchronization
+ /// </summary>
+ protected SemaphoreSlim MainLock { get; } = new (1, 1);
+
+ private Task? LastUpdate;
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// <br></br>
+ /// <br></br>
+ /// If the previous call to <see cref="UpdateAndRelease"/> resulted in an asynchronous update, and exceptions occured, an <see cref="AsyncUpdateException"/>
+ /// will be thrown enclosing the exception
+ /// </summary>
+ /// <param name="millisecondsTimeout">Time in milliseconds to wait for exclusive access to the resource</param>
+ /// <exception cref="AsyncUpdateException"></exception>
+ /// <inheritdoc/>
+ public virtual bool WaitOne(int millisecondsTimeout)
+ {
+ //First wait for main lock
+ if (MainLock.Wait(millisecondsTimeout))
+ {
+ //Main lock has been taken
+ try
+ {
+ //Wait for async update if there is one pending(will throw exceptions if any occurred)
+ LastUpdate?.Wait();
+ return true;
+ }
+ catch (AggregateException ae) when (ae.InnerException != null)
+ {
+ //Release the main lock and re-throw the inner exception
+ _ = MainLock.Release();
+ //Throw a new async update exception
+ throw new AsyncUpdateException(ae.InnerException);
+ }
+ catch
+ {
+ //Release the main lock and re-throw the exception
+ _ = MainLock.Release();
+ throw;
+ }
+ }
+ return false;
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public virtual async Task WaitOneAsync(CancellationToken token = default)
+ {
+ //Wait for main lock
+ await MainLock.WaitAsync(token).ConfigureAwait(true);
+ //if the last update completed synchronously, return true
+ if (LastUpdate == null)
+ {
+ return;
+ }
+ try
+ {
+ //Await the last update task and catch its exceptions
+ await LastUpdate.ConfigureAwait(false);
+ }
+ catch
+ {
+ //Release the main lock and re-throw the exception
+ _ = MainLock.Release();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Requests a resource update and releases the exclusive lock on this resource. If a deferred update operation has any
+ /// exceptions during its last operation, they will be thrown here.
+ /// </summary>
+ /// <param name="defer">Specifies weather the update should be deferred or awaited on the current call</param>
+ /// <param name="state">A state parameter to be pased to the update function</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public async ValueTask UpdateAndRelease(bool defer, TState state)
+ {
+ //Otherwise wait and update on the current thread
+ try
+ {
+ //Dispose the update task
+ LastUpdate?.Dispose();
+ //Remove the referrence
+ LastUpdate = null;
+ //Run update on the current thread
+ LastUpdate = await UpdateResource(defer, state).ConfigureAwait(true);
+ //If the update is not deferred, await the results
+ if (!defer && LastUpdate != null)
+ {
+ await LastUpdate.ConfigureAwait(true);
+ }
+ }
+ finally
+ {
+ //Release the main lock
+ _ = MainLock.Release();
+ }
+ }
+
+ /// <summary>
+ /// <para>
+ /// When overrriden in a derived class, is responsible for updating the state of the instance if necessary.
+ /// </para>
+ /// <para>
+ /// If the result of the update retruns a <see cref="Task"/> that represents the deferred update, the next call to <see cref="WaitOne"/> will
+ /// block until the operation completes and will throw any exceptions that occured
+ /// </para>
+ /// </summary>
+ /// <param name="defer">true if the caller expects a resource update to be deferred, false if the caller expects the result of the update to be awaited</param>
+ /// <param name="state">State parameter passed when releasing</param>
+ /// <returns>A <see cref="Task"/> representing the async state update operation, or null if no async state update operation need's to be monitored</returns>
+ protected abstract ValueTask<Task?> UpdateResource(bool defer, TState state);
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ //Dispose lock
+ MainLock.Dispose();
+
+ //Try to cleanup the last update
+ if (LastUpdate != null && LastUpdate.IsCompletedSuccessfully)
+ {
+ LastUpdate.Dispose();
+ }
+
+ LastUpdate = null;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Async/AsyncQueue.cs b/Utils/src/Async/AsyncQueue.cs
new file mode 100644
index 0000000..ba45513
--- /dev/null
+++ b/Utils/src/Async/AsyncQueue.cs
@@ -0,0 +1,144 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: AsyncQueue.cs
+*
+* AsyncQueue.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Channels;
+using System.Diagnostics.CodeAnalysis;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Provides a <see cref="Channel{T}"/> based asynchronous queue
+ /// </summary>
+ /// <typeparam name="T">The event object type</typeparam>
+ public class AsyncQueue<T>
+ {
+ private readonly Channel<T> _channel;
+
+ /// <summary>
+ /// Initalizes a new multi-threaded bound channel queue, that accepts
+ /// the <paramref name="capacity"/> number of items before it will
+ /// return asynchronously, or fail to enqueue items
+ /// </summary>
+ /// <param name="capacity">The maxium number of items to allow in the queue</param>
+ public AsyncQueue(int capacity):this(false, false, capacity)
+ {}
+ /// <summary>
+ /// Initalizes a new multi-threaded unbound channel queue
+ /// </summary>
+ public AsyncQueue():this(false, false)
+ {}
+
+ /// <summary>
+ /// Initalizes a new queue that allows specifying concurrency requirements
+ /// and a bound/unbound channel capacity
+ /// </summary>
+ /// <param name="singleWriter">A value that specifies only a single thread be enqueing items?</param>
+ /// <param name="singleReader">A value that specifies only a single thread will be dequeing</param>
+ /// <param name="capacity">The maxium number of items to enque without failing</param>
+ public AsyncQueue(bool singleWriter, bool singleReader, int capacity = int.MaxValue)
+ {
+ if(capacity == int.MaxValue)
+ {
+ //Create unbounded
+ UnboundedChannelOptions opt = new()
+ {
+ SingleReader = singleReader,
+ SingleWriter = singleWriter,
+ AllowSynchronousContinuations = true,
+ };
+ _channel = Channel.CreateUnbounded<T>(opt);
+ }
+ else
+ {
+ //Create bounded
+ BoundedChannelOptions opt = new(capacity)
+ {
+ SingleReader = singleReader,
+ SingleWriter = singleWriter,
+ AllowSynchronousContinuations = true,
+ //Default wait for space
+ FullMode = BoundedChannelFullMode.Wait
+ };
+ _channel = Channel.CreateBounded<T>(opt);
+ }
+ }
+
+ /// <summary>
+ /// Initalizes a new unbound channel based queue
+ /// </summary>
+ /// <param name="ubOptions">Channel options</param>
+ public AsyncQueue(UnboundedChannelOptions ubOptions)
+ {
+ _channel = Channel.CreateUnbounded<T>(ubOptions);
+ }
+
+ /// <summary>
+ /// Initalizes a new bound channel based queue
+ /// </summary>
+ /// <param name="options">Channel options</param>
+ public AsyncQueue(BoundedChannelOptions options)
+ {
+ _channel = Channel.CreateBounded<T>(options);
+ }
+
+ /// <summary>
+ /// Attemts to enqeue an item if the queue has the capacity
+ /// </summary>
+ /// <param name="item">The item to eqneue</param>
+ /// <returns>True if the queue can accept another item, false otherwise</returns>
+ public bool TryEnque(T item) => _channel.Writer.TryWrite(item);
+ /// <summary>
+ /// Enqueues an item to the end of the queue and notifies a waiter that an item was enqueued
+ /// </summary>
+ /// <param name="item">The item to enqueue</param>
+ /// <param name="cancellationToken"></param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public ValueTask EnqueueAsync(T item, CancellationToken cancellationToken = default) => _channel.Writer.WriteAsync(item, cancellationToken);
+ /// <summary>
+ /// Asynchronously waits for an item to be Enqueued to the end of the queue.
+ /// </summary>
+ /// <returns>The item at the begining of the queue</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public ValueTask<T> DequeueAsync(CancellationToken cancellationToken = default) => _channel.Reader.ReadAsync(cancellationToken);
+ /// <summary>
+ /// Removes the object at the beginning of the queue and stores it to the result parameter. Without waiting for a change
+ /// event.
+ /// </summary>
+ /// <param name="result">The item that was at the begining of the queue</param>
+ /// <returns>True if the queue could be read synchronously, false if the lock could not be entered, or the queue contains no items</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public bool TryDequeue([MaybeNullWhen(false)] out T result) => _channel.Reader.TryRead(out result);
+ /// <summary>
+ /// Peeks the object at the beginning of the queue and stores it to the result parameter. Without waiting for a change
+ /// event.
+ /// </summary>
+ /// <param name="result">The item that was at the begining of the queue</param>
+ /// <returns>True if the queue could be read synchronously, false if the lock could not be entered, or the queue contains no items</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public bool TryPeek([MaybeNullWhen(false)] out T result) => _channel.Reader.TryPeek(out result);
+ }
+}
diff --git a/Utils/src/Async/AsyncUpdatableResource.cs b/Utils/src/Async/AsyncUpdatableResource.cs
new file mode 100644
index 0000000..b4ce519
--- /dev/null
+++ b/Utils/src/Async/AsyncUpdatableResource.cs
@@ -0,0 +1,111 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: AsyncUpdatableResource.cs
+*
+* AsyncUpdatableResource.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// A callback delegate used for updating a <see cref="AsyncUpdatableResource"/>
+ /// </summary>
+ /// <param name="source">The <see cref="AsyncUpdatableResource"/> to be updated</param>
+ /// <param name="data">The serialized data to be stored/updated</param>
+ /// <exception cref="ResourceUpdateFailedException"></exception>
+ public delegate Task AsyncUpdateCallback(object source, Stream data);
+ /// <summary>
+ /// A callback delegate invoked when a <see cref="AsyncUpdatableResource"/> delete is requested
+ /// </summary>
+ /// <param name="source">The <see cref="AsyncUpdatableResource"/> to be deleted</param>
+ /// <exception cref="ResourceDeleteFailedException"></exception>
+ public delegate Task AsyncDeleteCallback(object source);
+
+ /// <summary>
+ /// Implemented by a resource that is backed by an external data store, that when modified or deleted will
+ /// be reflected to the backing store.
+ /// </summary>
+ public abstract class AsyncUpdatableResource : BackedResourceBase, IAsyncExclusiveResource
+ {
+ protected abstract AsyncUpdateCallback UpdateCb { get; }
+ protected abstract AsyncDeleteCallback DeleteCb { get; }
+
+ /// <summary>
+ /// Releases the resource and flushes pending changes to its backing store.
+ /// </summary>
+ /// <returns>A task that represents the async operation</returns>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="ResourceDeleteFailedException"></exception>
+ /// <exception cref="ResourceUpdateFailedException"></exception>
+ public virtual async ValueTask ReleaseAsync()
+ {
+ //If resource has already been realeased, return
+ if (IsReleased)
+ {
+ return;
+ }
+ //If deleted flag is set, invoke the delete callback
+ if (Deleted)
+ {
+ await DeleteCb(this).ConfigureAwait(true);
+ }
+ //If the state has been modifed, flush changes to the store
+ else if (Modified)
+ {
+ await FlushPendingChangesAsync().ConfigureAwait(true);
+ }
+ //Set the released value
+ IsReleased = true;
+ }
+
+ /// <summary>
+ /// <para>
+ /// Writes the current state of the the resource to the backing store
+ /// immediatly by invoking the specified callback.
+ /// </para>
+ /// <para>
+ /// Only call this method if your store supports multiple state updates
+ /// </para>
+ /// </summary>
+ protected virtual async Task FlushPendingChangesAsync()
+ {
+ //Get the resource
+ object resource = GetResource();
+ //Open a memory stream to store data in
+ using VnMemoryStream data = new();
+ //Serialize and write to stream
+ VnEncoding.JSONSerializeToBinary(resource, data, resource.GetType(), base.JSO);
+ //Reset stream to begining
+ _ = data.Seek(0, SeekOrigin.Begin);
+ //Invoke update callback
+ await UpdateCb(this, data).ConfigureAwait(true);
+ //Clear modified flag
+ Modified = false;
+ }
+ }
+}
diff --git a/Utils/src/Async/Exceptions/AsyncUpdateException.cs b/Utils/src/Async/Exceptions/AsyncUpdateException.cs
new file mode 100644
index 0000000..de5a491
--- /dev/null
+++ b/Utils/src/Async/Exceptions/AsyncUpdateException.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: AsyncUpdateException.cs
+*
+* AsyncUpdateException.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Represents an exception that was raised during an asyncronous update of a resource. The <see cref="Exception.InnerException"/> stores the
+ /// details of the actual exception raised
+ /// </summary>
+ public sealed class AsyncUpdateException : ResourceUpdateFailedException
+ {
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="inner"></param>
+ public AsyncUpdateException(Exception inner) : base("", inner) { }
+
+ public AsyncUpdateException()
+ {}
+
+ public AsyncUpdateException(string message) : base(message)
+ {}
+
+ public AsyncUpdateException(string message, Exception innerException) : base(message, innerException)
+ {}
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Async/IAsyncExclusiveResource.cs b/Utils/src/Async/IAsyncExclusiveResource.cs
new file mode 100644
index 0000000..93157ce
--- /dev/null
+++ b/Utils/src/Async/IAsyncExclusiveResource.cs
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IAsyncExclusiveResource.cs
+*
+* IAsyncExclusiveResource.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.Threading.Tasks;
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// <inheritdoc/>
+ /// </summary>
+ public interface IAsyncExclusiveResource : IResource
+ {
+ /// <summary>
+ /// Releases the resource from use. Called when a <see cref="ExclusiveResourceHandle{T}"/> is disposed
+ /// </summary>
+ ValueTask ReleaseAsync();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Async/IAsyncWaitHandle.cs b/Utils/src/Async/IAsyncWaitHandle.cs
new file mode 100644
index 0000000..1cadc06
--- /dev/null
+++ b/Utils/src/Async/IAsyncWaitHandle.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IAsyncWaitHandle.cs
+*
+* IAsyncWaitHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Provides a synchronization handle that can be asynchronously aquired
+ /// </summary>
+ public interface IAsyncWaitHandle
+ {
+ /// <summary>
+ /// Waits for exclusive access to the resource until the <see cref="CancellationToken"/> expires
+ /// </summary>
+ /// <inheritdoc/>
+ Task WaitOneAsync(CancellationToken token = default);
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Async/IWaitHandle.cs b/Utils/src/Async/IWaitHandle.cs
new file mode 100644
index 0000000..85e8a2a
--- /dev/null
+++ b/Utils/src/Async/IWaitHandle.cs
@@ -0,0 +1,59 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IWaitHandle.cs
+*
+* IWaitHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Provides basic thread synchronization functions similar to <see cref="WaitHandle"/>
+ /// </summary>
+ public interface IWaitHandle
+ {
+ /// <summary>
+ /// Waits for exclusive access to the resource indefinitly. If the signal is never received this method never returns
+ /// </summary>
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <returns>true if the current thread received the signal</returns>
+ public virtual bool WaitOne() => WaitOne(Timeout.Infinite);
+ /// <summary>
+ /// Waits for exclusive access to the resource until the specified number of milliseconds
+ /// </summary>
+ /// <param name="millisecondsTimeout">Time in milliseconds to wait for exclusive access to the resource</param>
+ /// <returns>true if the current thread received the signal, false if the timout expired, and access was not granted</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ bool WaitOne(int millisecondsTimeout);
+ /// <summary>
+ /// Waits for exclusive access to the resource until the specified <see cref="TimeSpan"/>
+ /// </summary>
+ /// <returns>true if the current thread received the signal, false if the timout expired, and access was not granted</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public virtual bool WaitOne(TimeSpan timeout) => WaitOne(Convert.ToInt32(timeout.TotalMilliseconds));
+ }
+} \ No newline at end of file
diff --git a/Utils/src/BitField.cs b/Utils/src/BitField.cs
new file mode 100644
index 0000000..bc001df
--- /dev/null
+++ b/Utils/src/BitField.cs
@@ -0,0 +1,115 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: BitField.cs
+*
+* BitField.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Represents a field of 64 bits that can be set or cleared using unsigned or signed masks
+ /// </summary>
+ public class BitField
+ {
+ private ulong Field;
+ /// <summary>
+ /// The readonly value of the <see cref="BitField"/>
+ /// </summary>
+ public ulong Value => Field;
+ /// <summary>
+ /// Creates a new <see cref="BitField"/> initialized to the specified value
+ /// </summary>
+ /// <param name="initial">Initial value</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public BitField(ulong initial) => Field = initial;
+ /// <summary>
+ /// Creates a new <see cref="BitField"/> initialized to the specified value
+ /// </summary>
+ /// <param name="initial">Initial value</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public BitField(long initial) => Field = unchecked((ulong)initial);
+ /// <summary>
+ /// Determines if the specified flag is set
+ /// </summary>
+ /// <param name="mask">The mask to compare against the field value</param>
+ /// <returns>True if the flag(s) is currently set, false if flag is not set</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSet(ulong mask) => (Field & mask) != 0;
+ /// <summary>
+ /// Determines if the specified flag is set
+ /// </summary>
+ /// <param name="mask">The mask to compare against the field value</param>
+ /// <returns>True if the flag(s) is currently set, false if flag is not set</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSet(long mask) => (Field & unchecked((ulong)mask)) != 0;
+ /// <summary>
+ /// Determines if the specified flag is set
+ /// </summary>
+ /// <param name="mask">The mask to compare against the field value</param>
+ /// <returns>True if the flag(s) is currently set, false if flag is not set</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(ulong mask) => Field |= mask;
+ /// <summary>
+ /// Determines if the specified flag is set
+ /// </summary>
+ /// <param name="mask">The mask to compare against the field value</param>
+ /// <returns>True if the flag(s) is currently set, false if flag is not set</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(long mask) => Field |= unchecked((ulong)mask);
+ /// <summary>
+ /// Sets or clears a flag(s) indentified by a mask based on the value
+ /// </summary>
+ /// <param name="mask">Mask used to identify flags</param>
+ /// <param name="value">True to set a flag, false to clear a flag</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(ulong mask, bool value)
+ {
+ if (value)
+ {
+ Set(mask);
+ }
+ else
+ {
+ Clear(mask);
+ }
+ }
+ /// <summary>
+ /// Clears the flag identified by the specified mask
+ /// </summary>
+ /// <param name="mask">The mask used to clear the given flag</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear(ulong mask) => Field &= ~mask;
+ /// <summary>
+ /// Clears the flag identified by the specified mask
+ /// </summary>
+ /// <param name="mask">The mask used to clear the given flag</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear(long mask) => Field &= ~unchecked((ulong)mask);
+ /// <summary>
+ /// Clears all flags by setting the <see cref="Field"/> property value to 0
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ClearAll() => Field = 0;
+ }
+} \ No newline at end of file
diff --git a/Utils/src/ERRNO.cs b/Utils/src/ERRNO.cs
new file mode 100644
index 0000000..c3c61de
--- /dev/null
+++ b/Utils/src/ERRNO.cs
@@ -0,0 +1,152 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ERRNO.cs
+*
+* ERRNO.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Implements a C style integer error code type. Size is platform dependent
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential)]
+ public readonly struct ERRNO : IEquatable<ERRNO>, ISpanFormattable, IFormattable
+ {
+ /// <summary>
+ /// Represents a successfull error code (true)
+ /// </summary>
+ public static readonly ERRNO SUCCESS = true;
+
+ /// <summary>
+ /// Represents a failure error code (false)
+ /// </summary>
+ public static readonly ERRNO E_FAIL = false;
+
+ private readonly nint ErrorCode;
+ /// <summary>
+ /// Creates a new <see cref="ERRNO"/> from the specified error value
+ /// </summary>
+ /// <param name="errno">The value of the error to represent</param>
+ public ERRNO(nint errno) => ErrorCode = errno;
+ /// <summary>
+ /// Creates a new <see cref="ERRNO"/> from an <see cref="int"/> error code. null = 0 = false
+ /// </summary>
+ /// <param name="errorVal">Error code</param>
+ public static implicit operator ERRNO(int errorVal) => new (errorVal);
+ /// <summary>
+ /// Creates a new <see cref="ERRNO"/> from an <see cref="int"/> error code. null = 0 = false
+ /// </summary>
+ /// <param name="errorVal">Error code</param>
+ public static explicit operator ERRNO(int? errorVal) => new(errorVal ?? 0);
+ /// <summary>
+ /// Creates a new <see cref="ERRNO"/> from a booleam, 1 if true, 0 if false
+ /// </summary>
+ /// <param name="errorVal"></param>
+ public static implicit operator ERRNO(bool errorVal) => new(errorVal ? 1 : 0);
+ /// <summary>
+ /// Creates a new <see cref="ERRNO"/> from a pointer value
+ /// </summary>
+ /// <param name="errno">The pointer value representing an error code</param>
+ public static implicit operator ERRNO(nint errno) => new(errno);
+ /// <summary>
+ /// Error value as integer. Value of supplied error code or if cast from boolean 1 if true, 0 if false
+ /// </summary>
+ /// <param name="errorVal"><see cref="ERRNO"/> to get error code from</param>
+ public static implicit operator int(ERRNO errorVal) => (int)errorVal.ErrorCode;
+ /// <summary>
+ /// C style boolean conversion. false if 0, true otherwise
+ /// </summary>
+ /// <param name="errorVal"></param>
+ public static implicit operator bool(ERRNO errorVal) => errorVal != 0;
+ /// <summary>
+ /// Creates a new <see cref="IntPtr"/> from the value if the stored (nint) error code
+ /// </summary>
+ /// <param name="errno">The <see cref="ERRNO"/> contating the pointer value</param>
+ public static implicit operator IntPtr(ERRNO errno) => new(errno.ErrorCode);
+ /// <summary>
+ /// Creates a new <c>nint</c> from the value if the stored error code
+ /// </summary>
+ /// <param name="errno">The <see cref="ERRNO"/> contating the pointer value</param>
+ public static implicit operator nint(ERRNO errno) => errno.ErrorCode;
+
+ public static ERRNO operator +(ERRNO err, int add) => new(err.ErrorCode + add);
+ public static ERRNO operator +(ERRNO err, nint add) => new(err.ErrorCode + add);
+ public static ERRNO operator ++(ERRNO err) => new(err.ErrorCode + 1);
+ public static ERRNO operator --(ERRNO err) => new(err.ErrorCode - 1);
+ public static ERRNO operator -(ERRNO err, int subtract) => new(err.ErrorCode - subtract);
+ public static ERRNO operator -(ERRNO err, nint subtract) => new(err.ErrorCode - subtract);
+
+ public static bool operator >(ERRNO err, ERRNO other) => err.ErrorCode > other.ErrorCode;
+ public static bool operator <(ERRNO err, ERRNO other) => err.ErrorCode < other.ErrorCode;
+ public static bool operator >=(ERRNO err, ERRNO other) => err.ErrorCode >= other.ErrorCode;
+ public static bool operator <=(ERRNO err, ERRNO other) => err.ErrorCode <= other.ErrorCode;
+
+ public static bool operator >(ERRNO err, int other) => err.ErrorCode > other;
+ public static bool operator <(ERRNO err, int other) => err.ErrorCode < other;
+ public static bool operator >=(ERRNO err, int other) => err.ErrorCode >= other;
+ public static bool operator <=(ERRNO err, int other) => err.ErrorCode <= other;
+
+ public static bool operator >(ERRNO err, nint other) => err.ErrorCode > other;
+ public static bool operator <(ERRNO err, nint other) => err.ErrorCode < other;
+ public static bool operator >=(ERRNO err, nint other) => err.ErrorCode >= other;
+ public static bool operator <=(ERRNO err, nint other) => err.ErrorCode <= other;
+
+ public static bool operator ==(ERRNO err, ERRNO other) => err.ErrorCode == other.ErrorCode;
+ public static bool operator !=(ERRNO err, ERRNO other) => err.ErrorCode != other.ErrorCode;
+ public static bool operator ==(ERRNO err, int other) => err.ErrorCode == other;
+ public static bool operator !=(ERRNO err, int other) => err.ErrorCode != other;
+ public static bool operator ==(ERRNO err, nint other) => err.ErrorCode == other;
+ public static bool operator !=(ERRNO err, nint other) => err.ErrorCode != other;
+
+ public readonly bool Equals(ERRNO other) => ErrorCode == other.ErrorCode;
+ public readonly override bool Equals(object obj) => obj is ERRNO other && Equals(other);
+ public readonly override int GetHashCode() => ErrorCode.GetHashCode();
+
+ /// <summary>
+ /// The integer error value of the current instance in radix 10
+ /// </summary>
+ /// <returns></returns>
+ public readonly override string ToString()
+ {
+ //Return the string of the error code number
+ return ErrorCode.ToString();
+ }
+ public readonly string ToString(string format)
+ {
+ //Return the string of the error code number
+ return ErrorCode.ToString(format);
+ }
+
+ ///<inheritdoc/>
+ public readonly bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ return ErrorCode.TryFormat(destination, out charsWritten, format, provider);
+ }
+ ///<inheritdoc/>
+ public readonly string ToString(string format, IFormatProvider formatProvider)
+ {
+ return ErrorCode.ToString(format, formatProvider);
+ }
+ }
+}
diff --git a/Utils/src/Extensions/CacheExtensions.cs b/Utils/src/Extensions/CacheExtensions.cs
new file mode 100644
index 0000000..5485c2d
--- /dev/null
+++ b/Utils/src/Extensions/CacheExtensions.cs
@@ -0,0 +1,348 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: CacheExtensions.cs
+*
+* CacheExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using VNLib.Utils.Memory.Caching;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// Cache collection extensions
+ /// </summary>
+ public static class CacheExtensions
+ {
+ /// <summary>
+ /// <para>
+ /// Stores a new record. If an old record exists, the records are compared,
+ /// if they are not equal, the old record is evicted and the new record is stored
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T">A cachable object</typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <param name="record">The record to store</param>
+ /// <remarks>
+ /// Locks on the store parameter to provide mutual exclusion for non thread-safe
+ /// data structures.
+ /// </remarks>
+ public static void StoreRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, T record) where T : ICacheable
+ {
+ T ?oldRecord = default;
+ lock (store)
+ {
+ //See if an old record exists
+ if (!store.Remove(key, out oldRecord) || oldRecord == null)
+ {
+ //Old record doesnt exist, store and return
+ store[key] = record;
+ return;
+ }
+ //See if the old and new records and the same record
+ if (oldRecord.Equals(record))
+ {
+ //records are equal, so we can exit
+ return;
+ }
+ //Old record is not equal, so we can store the new record and evict the old on
+ store[key] = record;
+ }
+ //Call evict on the old record
+ oldRecord.Evicted();
+ }
+ /// <summary>
+ /// <para>
+ /// Stores a new record and updates the expiration date. If an old record exists, the records
+ /// are compared, if they are not equal, the old record is evicted and the new record is stored
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T">A cachable object</typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <param name="record">The record to store</param>
+ /// <param name="validFor">The new expiration time of the record</param>
+ /// <remarks>
+ /// Locks on the store parameter to provide mutual exclusion for non thread-safe
+ /// data structures.
+ /// </remarks>
+ public static void StoreRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, T record, TimeSpan validFor) where T : ICacheable
+ {
+ //Update the expiration time
+ record.Expires = DateTime.UtcNow.Add(validFor);
+ //Store
+ StoreRecord(store, key, record);
+ }
+ /// <summary>
+ /// <para>
+ /// Returns a stored record if it exists and is not expired. If the record exists
+ /// but has expired, it is evicted.
+ /// </para>
+ /// <para>
+ /// If a record is evicted, the return value evaluates to -1 and the value parameter
+ /// is set to the old record if the caller wished to inspect the record after the
+ /// eviction method completes
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T">A cachable object</typeparam>
+ /// <param name="store"></param>
+ /// <param name="key"></param>
+ /// <param name="value">The record</param>
+ /// <returns>
+ /// Gets a value indicating the reults of the operation. 0 if the record is not found, -1 if expired, 1 if
+ /// record is valid
+ /// </returns>
+ /// <remarks>
+ /// Locks on the store parameter to provide mutual exclusion for non thread-safe
+ /// data structures.
+ /// </remarks>
+ public static ERRNO TryGetOrEvictRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, out T? value) where T : ICacheable
+ {
+ value = default;
+ //Cache current date time before entering the lock
+ DateTime now = DateTime.UtcNow;
+ //Get value
+ lock (store)
+ {
+ //try to get the value
+ if (!store.TryGetValue(key, out value))
+ {
+ //not found
+ return 0;
+ }
+ //Not expired
+ if (value.Expires > now)
+ {
+ return true;
+ }
+ //Remove from store
+ _ = store.Remove(key);
+ }
+ //Call the evict func
+ value.Evicted();
+ return -1;
+ }
+ /// <summary>
+ /// Updates the expiration date on a record to the specified time if it exists, regardless
+ /// of its validity
+ /// </summary>
+ /// <typeparam name="TKey">Diction key type</typeparam>
+ /// <typeparam name="T">A cachable object</typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record to update</param>
+ /// <param name="extendedTime">The expiration time (time added to <see cref="DateTime.UtcNow"/>)</param>
+ /// <remarks>
+ /// Locks on the store parameter to provide mutual exclusion for non thread-safe
+ /// data structures.
+ /// </remarks>
+ public static void UpdateRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, TimeSpan extendedTime) where T : ICacheable
+ {
+ //Cacl the expiration time
+ DateTime expiration = DateTime.UtcNow.Add(extendedTime);
+ lock (store)
+ {
+ //Update the expiration time if the record exists
+ if (store.TryGetValue(key, out T? record) && record != null)
+ {
+ record.Expires = expiration;
+ }
+ }
+ }
+ /// <summary>
+ /// Evicts a stored record from the store. If the record is found, the eviction
+ /// method is executed
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <returns>True if the record was found and evicted</returns>
+ public static bool EvictRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key) where T : ICacheable
+ {
+ T? record = default;
+ lock (store)
+ {
+ //Try to remove the record
+ if (!store.Remove(key, out record) || record == null)
+ {
+ //No record found or null
+ return false;
+ }
+ }
+ //Call eviction mode
+ record.Evicted();
+ return true;
+ }
+ /// <summary>
+ /// Evicts all expired records from the store
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ public static void CollectRecords<TKey, T>(this IDictionary<TKey, T> store) where T : ICacheable
+ {
+ CollectRecords(store, DateTime.UtcNow);
+ }
+
+ /// <summary>
+ /// Evicts all expired records from the store
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="validAfter">A time that specifies the time which expired records should be evicted</param>
+ public static void CollectRecords<TKey, T>(this IDictionary<TKey, T> store, DateTime validAfter) where T : ICacheable
+ {
+ //Build a query to get the keys that belong to the expired records
+ IEnumerable<KeyValuePair<TKey, T>> expired = store.Where(s => s.Value.Expires < validAfter);
+ //temp list for expired records
+ IEnumerable<T> evicted;
+ //Take lock on store
+ lock (store)
+ {
+ KeyValuePair<TKey, T>[] kvp = expired.ToArray();
+ //enumerate to array so values can be removed while the lock is being held
+ foreach (KeyValuePair<TKey, T> pair in kvp)
+ {
+ //remove the record and call the eviction method
+ _ = store.Remove(pair);
+ }
+ //select values while lock held
+ evicted = kvp.Select(static v => v.Value);
+ }
+ //Iterrate over evicted records and call evicted method
+ foreach (T ev in evicted)
+ {
+ ev.Evicted();
+ }
+ }
+
+ /// <summary>
+ /// Allows for mutually exclusive use of a <see cref="ICacheable"/> record with a
+ /// state parameter
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ /// <typeparam name="State"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <param name="state">A user-token type state parameter to pass to the use callback method</param>
+ /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param>
+ public static void UseRecord<TKey, T, State>(this IDictionary<TKey, T> store, TKey key, State state, Action<T, State> useCtx) where T: ICacheable
+ {
+ lock (store)
+ {
+ //If the record exists
+ if(store.TryGetValue(key, out T record))
+ {
+ //Use it within the lock statement
+ useCtx(record, state);
+ }
+ }
+ }
+ /// <summary>
+ /// Allows for mutually exclusive use of a <see cref="ICacheable"/>
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param>
+ public static void UseRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, Action<T> useCtx) where T : ICacheable
+ {
+ lock (store)
+ {
+ //If the record exists
+ if (store.TryGetValue(key, out T record))
+ {
+ //Use it within the lock statement
+ useCtx(record);
+ }
+ }
+ }
+ /// <summary>
+ /// Allows for mutually exclusive use of a <see cref="ICacheable"/> record with a
+ /// state parameter, only if the found record is valid
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ /// <typeparam name="State"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <param name="state">A user-token type state parameter to pass to the use callback method</param>
+ /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param>
+ /// <remarks>If the record is found, but is expired, the record is evicted from the store. The callback is never invoked</remarks>
+ public static void UseIfValid<TKey, T, State>(this IDictionary<TKey, T> store, TKey key, State state, Action<T, State> useCtx) where T : ICacheable
+ {
+ DateTime now = DateTime.UtcNow;
+ T? record;
+ lock (store)
+ {
+ //If the record exists, check if its valid
+ if (store.TryGetValue(key, out record) && record.Expires < now)
+ {
+ //Use it within the lock statement
+ useCtx(record, state);
+ return;
+ }
+ //Record is no longer valid
+ _ = store.Remove(key);
+ }
+ //Call evicted method
+ record?.Evicted();
+ }
+ /// <summary>
+ /// Allows for mutually exclusive use of a <see cref="ICacheable"/> record with a
+ /// state parameter, only if the found record is valid
+ /// </summary>
+ /// <typeparam name="TKey"></typeparam>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="key">The unique key identifying the record</param>
+ /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param>
+ /// <remarks>If the record is found, but is expired, the record is evicted from the store. The callback is never invoked</remarks>
+ public static void UseIfValid<TKey, T>(this IDictionary<TKey, T> store, TKey key, Action<T> useCtx) where T : ICacheable
+ {
+ DateTime now = DateTime.UtcNow;
+ T? record;
+ lock (store)
+ {
+ //If the record exists, check if its valid
+ if (store.TryGetValue(key, out record) && record.Expires < now)
+ {
+ //Use it within the lock statement
+ useCtx(record);
+ return;
+ }
+ //Record is no longer valid
+ _ = store.Remove(key);
+ }
+ //Call evicted method
+ record?.Evicted();
+ }
+ }
+}
diff --git a/Utils/src/Extensions/CollectionExtensions.cs b/Utils/src/Extensions/CollectionExtensions.cs
new file mode 100644
index 0000000..e4ec459
--- /dev/null
+++ b/Utils/src/Extensions/CollectionExtensions.cs
@@ -0,0 +1,98 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: CollectionExtensions.cs
+*
+* CollectionExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Collections.Generic;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// Provides collection extension methods
+ /// </summary>
+ public static class CollectionExtensions
+ {
+ /// <summary>
+ /// Gets a previously-stored base32 encoded value-type from the lookup and returns its initialized structure from
+ /// the value stored
+ /// </summary>
+ /// <typeparam name="TKey">The key type used to index the lookup</typeparam>
+ /// <typeparam name="TValue">An unmanaged structure type</typeparam>
+ /// <param name="lookup"></param>
+ /// <param name="key">The key used to identify the value</param>
+ /// <returns>The initialized structure, or default if the lookup returns null/empty string</returns>
+ public static TValue GetValueType<TKey, TValue>(this IIndexable<TKey, string> lookup, TKey key) where TValue : unmanaged where TKey : notnull
+ {
+ //Get value
+ string value = lookup[key];
+ //If the string is set, recover the value and return it
+ return string.IsNullOrWhiteSpace(value) ? default : VnEncoding.FromBase32String<TValue>(value);
+ }
+
+ /// <summary>
+ /// Serializes a value-type in base32 encoding and stores it at the specified key
+ /// </summary>
+ /// <typeparam name="TKey">The key type used to index the lookup</typeparam>
+ /// <typeparam name="TValue">An unmanaged structure type</typeparam>
+ /// <param name="lookup"></param>
+ /// <param name="key">The key used to identify the value</param>
+ /// <param name="value">The value to serialze</param>
+ public static void SetValueType<TKey, TValue>(this IIndexable<TKey, string> lookup, TKey key, TValue value) where TValue : unmanaged where TKey : notnull
+ {
+ //encode string from value type and store in lookup
+ lookup[key] = VnEncoding.ToBase32String(value);
+ }
+ /// <summary>
+ /// Executes a handler delegate on every element of the list within a try-catch block
+ /// and rethrows exceptions as an <see cref="AggregateException"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="list"></param>
+ /// <param name="handler">An <see cref="Action"/> handler delegate to complete some operation on the elements within the list</param>
+ /// <exception cref="AggregateException"></exception>
+ public static void TryForeach<T>(this IEnumerable<T> list, Action<T> handler)
+ {
+ List<Exception>? exceptionList = null;
+ foreach(T item in list)
+ {
+ try
+ {
+ handler(item);
+ }
+ catch(Exception ex)
+ {
+ //Init new list and add the exception
+ exceptionList ??= new();
+ exceptionList.Add(ex);
+ }
+ }
+ //Raise aggregate exception for all caught exceptions
+ if(exceptionList?.Count > 0)
+ {
+ throw new AggregateException(exceptionList);
+ }
+ }
+ }
+}
diff --git a/Utils/src/Extensions/IoExtensions.cs b/Utils/src/Extensions/IoExtensions.cs
new file mode 100644
index 0000000..f312203
--- /dev/null
+++ b/Utils/src/Extensions/IoExtensions.cs
@@ -0,0 +1,345 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IoExtensions.cs
+*
+* IoExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.Versioning;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+
+using static VNLib.Utils.Memory.Memory;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// Provieds extension methods for common IO operations
+ /// </summary>
+ public static class IoExtensions
+ {
+ /// <summary>
+ /// Unlocks the entire file
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [UnsupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("macos")]
+ [UnsupportedOSPlatform("tvos")]
+ public static void Unlock(this FileStream fs)
+ {
+ _ = fs ?? throw new ArgumentNullException(nameof(fs));
+ //Unlock the entire file
+ fs.Unlock(0, fs.Length);
+ }
+
+ /// <summary>
+ /// Locks the entire file
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [UnsupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("macos")]
+ [UnsupportedOSPlatform("tvos")]
+ public static void Lock(this FileStream fs)
+ {
+ _ = fs ?? throw new ArgumentNullException(nameof(fs));
+ //Lock the entire length of the file
+ fs.Lock(0, fs.Length);
+ }
+
+ /// <summary>
+ /// Provides an async wrapper for copying data from the current stream to another using an unmanged
+ /// buffer.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="dest">The destination data stream to write data to</param>
+ /// <param name="bufferSize">The size of the buffer to use while copying data. (Value will be clamped to the size of the stream if seeking is available)</param>
+ /// <param name="heap">The <see cref="IUnmangedHeap"/> to allocate the buffer from</param>
+ /// <param name="token">A token that may cancel asynchronous operations</param>
+ /// <returns>A <see cref="ValueTask"/> that completes when the copy operation has completed</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ public static async ValueTask CopyToAsync(this Stream source, Stream dest, int bufferSize, IUnmangedHeap heap, CancellationToken token = default)
+ {
+ if (source.CanSeek)
+ {
+ bufferSize = (int)Math.Min(source.Length, bufferSize);
+ }
+ //Alloc a buffer
+ using IMemoryOwner<byte> buffer = heap.DirectAlloc<byte>(bufferSize);
+ //Wait for copy to complete
+ await CopyToAsync(source, dest, buffer.Memory, token);
+ }
+ /// <summary>
+ /// Provides an async wrapper for copying data from the current stream to another with a
+ /// buffer from the <paramref name="heap"/>
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="dest">The destination data stream to write data to</param>
+ /// <param name="bufferSize">The size of the buffer to use while copying data. (Value will be clamped to the size of the stream if seeking is available)</param>
+ /// <param name="count">The number of bytes to copy from the current stream to destination stream</param>
+ /// <param name="heap">The heap to alloc buffer from</param>
+ /// <param name="token">A token that may cancel asynchronous operations</param>
+ /// <returns>A <see cref="ValueTask"/> that completes when the copy operation has completed</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ public static async ValueTask CopyToAsync(this Stream source, Stream dest, long count, int bufferSize, IUnmangedHeap heap, CancellationToken token = default)
+ {
+ if (source.CanSeek)
+ {
+ bufferSize = (int)Math.Min(source.Length, bufferSize);
+ }
+ //Alloc a buffer
+ using IMemoryOwner<byte> buffer = heap.DirectAlloc<byte>(bufferSize);
+ //Wait for copy to complete
+ await CopyToAsync(source, dest, buffer.Memory, count, token);
+ }
+
+ /// <summary>
+ /// Copies data from one stream to another, using self managed buffers. May allocate up to 2MB.
+ /// </summary>
+ /// <param name="source">Source stream to read from</param>
+ /// <param name="dest">Destination stream to write data to</param>
+ /// <param name="heap">The heap to allocate buffers from</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void CopyTo(this Stream source, Stream dest, IUnmangedHeap? heap = null)
+ {
+ if (!source.CanRead)
+ {
+ throw new ArgumentException("Source stream is unreadable", nameof(source));
+ }
+ if (!dest.CanWrite)
+ {
+ throw new ArgumentException("Destination stream is unwritable", nameof(dest));
+ }
+ heap ??= Shared;
+ //Get a buffer size, maximum of 2mb buffer size if the stream supports seeking, otherwise, min buf size
+ int bufSize = source.CanSeek ? (int)Math.Min(source.Length, MAX_BUF_SIZE) : MIN_BUF_SIZE;
+ //Length must be 0, so return
+ if (bufSize == 0)
+ {
+ return;
+ }
+ //Alloc a buffer
+ using UnsafeMemoryHandle<byte> buffer = heap.UnsafeAlloc<byte>(bufSize);
+ int read;
+ do
+ {
+ //read
+ read = source.Read(buffer.Span);
+ //Guard
+ if (read == 0)
+ {
+ break;
+ }
+ //write only the data that was read (slice)
+ dest.Write(buffer.Span[..read]);
+ } while (true);
+ }
+ /// <summary>
+ /// Copies data from one stream to another, using self managed buffers. May allocate up to 2MB.
+ /// </summary>
+ /// <param name="source">Source stream to read from</param>
+ /// <param name="dest">Destination stream to write data to</param>
+ /// <param name="count">Number of bytes to read/write</param>
+ /// <param name="heap">The heap to allocate buffers from</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void CopyTo(this Stream source, Stream dest, long count, IUnmangedHeap? heap = null)
+ {
+ if (!source.CanRead)
+ {
+ throw new ArgumentException("Source stream is unreadable", nameof(source));
+ }
+ if (!dest.CanWrite)
+ {
+ throw new ArgumentException("Destination stream is unwritable", nameof(dest));
+ }
+ //Set default heap
+ heap ??= Shared;
+ //Get a buffer size, maximum of 2mb buffer size if the stream supports seeking, otherwise, min buf size
+ int bufSize = source.CanSeek ? (int)Math.Min(source.Length, MAX_BUF_SIZE) : MIN_BUF_SIZE;
+ //Length must be 0, so return
+ if (bufSize == 0)
+ {
+ return;
+ }
+ //Alloc a buffer
+ using UnsafeMemoryHandle<byte> buffer = heap.UnsafeAlloc<byte>(bufSize);
+ //wrapper around offset pointer
+ long total = 0;
+ int read;
+ do
+ {
+ Span<byte> wrapper = buffer.Span[..(int)Math.Min(bufSize, (count - total))];
+ //read
+ read = source.Read(wrapper);
+ //Guard
+ if (read == 0)
+ {
+ break;
+ }
+ //write only the data that was read (slice)
+ dest.Write(wrapper[..read]);
+ //Update total
+ total += read;
+ } while (true);
+ }
+
+ /// <summary>
+ /// Copies data from the current stream to the destination stream using the supplied memory buffer
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="dest">The destination data stream to write data to</param>
+ /// <param name="buffer">The buffer to use when copying data</param>
+ /// <param name="token">A token that may cancel asynchronous operations</param>
+ /// <returns>A <see cref="ValueTask"/> that completes when the copy operation has completed</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static async ValueTask CopyToAsync(this Stream source, Stream dest, Memory<byte> buffer, CancellationToken token = default)
+ {
+ //Make sure source can be read from, and dest can be written to
+ if (!source.CanRead)
+ {
+ throw new ArgumentException("Source stream is unreadable", nameof(source));
+ }
+ if (!dest.CanWrite)
+ {
+ throw new ArgumentException("Destination stream is unwritable", nameof(dest));
+ }
+ //Read in loop
+ int read;
+ while (true)
+ {
+ //read
+ read = await source.ReadAsync(buffer, token);
+ //Guard
+ if (read == 0)
+ {
+ break;
+ }
+ //write only the data that was read (slice)
+ await dest.WriteAsync(buffer[..read], token);
+ }
+ }
+
+ /// <summary>
+ /// Copies data from the current stream to the destination stream using the supplied memory buffer
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="dest">The destination data stream to write data to</param>
+ /// <param name="buffer">The buffer to use when copying data</param>
+ /// <param name="count">The number of bytes to copy from the current stream to destination stream</param>
+ /// <param name="token">A token that may cancel asynchronous operations</param>
+ /// <returns>A <see cref="ValueTask"/> that completes when the copy operation has completed</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static async ValueTask CopyToAsync(this Stream source, Stream dest, Memory<byte> buffer, long count, CancellationToken token = default)
+ {
+ //Make sure source can be read from, and dest can be written to
+ if (!source.CanRead)
+ {
+ throw new ArgumentException("Source stream is unreadable", nameof(source));
+ }
+ if (!dest.CanWrite)
+ {
+ throw new ArgumentException("Destination stream is unwritable", nameof(dest));
+ }
+ /*
+ * Track total count so we copy the exect number of
+ * bytes from the source
+ */
+ long total = 0;
+ int bufferSize = buffer.Length;
+ int read;
+ while (true)
+ {
+ //get offset wrapper of the total buffer or remaining count
+ Memory<byte> offset = buffer[..(int)Math.Min(bufferSize, count - total)];
+ //read
+ read = await source.ReadAsync(offset, token);
+ //Guard
+ if (read == 0)
+ {
+ break;
+ }
+ //write only the data that was read (slice)
+ await dest.WriteAsync(offset[..read], token);
+ //Update total
+ total += read;
+ }
+ }
+
+ /// <summary>
+ /// Opens a file within the current directory
+ /// </summary>
+ /// <param name="dir"></param>
+ /// <param name="fileName">The name of the file to open</param>
+ /// <param name="mode">The <see cref="FileMode"/> to open the file with</param>
+ /// <param name="access">The <see cref="FileAccess"/> to open the file with</param>
+ /// <param name="share"></param>
+ /// <param name="bufferSize">The size of the buffer to read/write with</param>
+ /// <param name="options"></param>
+ /// <returns>The <see cref="FileStream"/> of the opened file</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static FileStream OpenFile(this DirectoryInfo dir,
+ string fileName,
+ FileMode mode,
+ FileAccess access,
+ FileShare share = FileShare.None,
+ int bufferSize = 4096,
+ FileOptions options = FileOptions.None)
+ {
+ _ = dir ?? throw new ArgumentNullException(nameof(dir));
+ string fullPath = Path.Combine(dir.FullName, fileName);
+ return new FileStream(fullPath, mode, access, share, bufferSize, options);
+ }
+ /// <summary>
+ /// Deletes the speicifed file from the current directory
+ /// </summary>
+ /// <param name="dir"></param>
+ /// <param name="fileName">The name of the file to delete</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void DeleteFile(this DirectoryInfo dir, string fileName)
+ {
+ _ = dir ?? throw new ArgumentNullException(nameof(dir));
+ string fullPath = Path.Combine(dir.FullName, fileName);
+ File.Delete(fullPath);
+ }
+ /// <summary>
+ /// Determines if a file exists within the current directory
+ /// </summary>
+ /// <param name="dir"></param>
+ /// <param name="fileName">The name of the file to search for</param>
+ /// <returns>True if the file is found and the user has permission to access the file, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool FileExists(this DirectoryInfo dir, string fileName)
+ {
+ _ = dir ?? throw new ArgumentNullException(nameof(dir));
+ string fullPath = Path.Combine(dir.FullName, fileName);
+ return FileOperations.FileExists(fullPath);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/JsonExtensions.cs b/Utils/src/Extensions/JsonExtensions.cs
new file mode 100644
index 0000000..a27dcc0
--- /dev/null
+++ b/Utils/src/Extensions/JsonExtensions.cs
@@ -0,0 +1,215 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: JsonExtensions.cs
+*
+* JsonExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Text.Json;
+using System.Collections.Generic;
+
+using VNLib.Utils.IO;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// Specifies how to parse a timespan value from a <see cref="JsonDocument"/> element
+ /// </summary>
+ public enum TimeParseType
+ {
+ Milliseconds,
+ Seconds,
+ Minutes,
+ Hours,
+ Days,
+ Ticks
+ }
+
+ public static class JsonExtensions
+ {
+ /// <summary>
+ /// Converts a JSON encoded string to an object of the specified type
+ /// </summary>
+ /// <typeparam name="T">Output type of the object</typeparam>
+ /// <param name="value"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> to use during de-serialization</param>
+ /// <returns>The new object or default if the string is null or empty</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static T? AsJsonObject<T>(this string value, JsonSerializerOptions? options = null)
+ {
+ return !string.IsNullOrWhiteSpace(value) ? JsonSerializer.Deserialize<T>(value, options) : default;
+ }
+ /// <summary>
+ /// Converts a JSON encoded binary data to an object of the specified type
+ /// </summary>
+ /// <typeparam name="T">Output type of the object</typeparam>
+ /// <param name="utf8bin"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> to use during de-serialization</param>
+ /// <returns>The new object or default if the string is null or empty</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static T? AsJsonObject<T>(this in ReadOnlySpan<byte> utf8bin, JsonSerializerOptions? options = null)
+ {
+ return utf8bin.IsEmpty ? default : JsonSerializer.Deserialize<T>(utf8bin, options);
+ }
+ /// <summary>
+ /// Converts a JSON encoded binary data to an object of the specified type
+ /// </summary>
+ /// <typeparam name="T">Output type of the object</typeparam>
+ /// <param name="utf8bin"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> to use during de-serialization</param>
+ /// <returns>The new object or default if the string is null or empty</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static T? AsJsonObject<T>(this in ReadOnlyMemory<byte> utf8bin, JsonSerializerOptions? options = null)
+ {
+ return utf8bin.IsEmpty ? default : JsonSerializer.Deserialize<T>(utf8bin.Span, options);
+ }
+ /// <summary>
+ /// Converts a JSON encoded binary data to an object of the specified type
+ /// </summary>
+ /// <typeparam name="T">Output type of the object</typeparam>
+ /// <param name="utf8bin"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> to use during de-serialization</param>
+ /// <returns>The new object or default if the string is null or empty</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static T? AsJsonObject<T>(this byte[] utf8bin, JsonSerializerOptions? options = null)
+ {
+ return utf8bin == null ? default : JsonSerializer.Deserialize<T>(utf8bin.AsSpan(), options);
+ }
+ /// <summary>
+ /// Parses a json encoded string to a json documen
+ /// </summary>
+ /// <param name="jsonString"></param>
+ /// <param name="options"></param>
+ /// <returns>If the json string is null, returns null, otherwise the json document around the data</returns>
+ /// <exception cref="JsonException"></exception>
+ public static JsonDocument? AsJsonDocument(this string jsonString, JsonDocumentOptions options = default)
+ {
+ return jsonString == null ? null : JsonDocument.Parse(jsonString, options);
+ }
+ /// <summary>
+ /// Shortcut extension to <see cref="JsonElement.GetProperty(string)"/> and returns a string
+ /// </summary>
+ /// <param name="element"></param>
+ /// <param name="propertyName">The name of the property to get the string value of</param>
+ /// <returns>If the property exists, returns the string stored at that property</returns>
+ public static string? GetPropString(this in JsonElement element, string propertyName)
+ {
+ return element.TryGetProperty(propertyName, out JsonElement el) ? el.GetString() : null;
+ }
+ /// <summary>
+ /// Shortcut extension to <see cref="JsonElement.GetProperty(string)"/> and returns a string
+ /// </summary>
+ /// <param name="conf"></param>
+ /// <param name="propertyName">The name of the property to get the string value of</param>
+ /// <returns>If the property exists, returns the string stored at that property</returns>
+ public static string? GetPropString(this IReadOnlyDictionary<string, JsonElement> conf, string propertyName)
+ {
+ return conf.TryGetValue(propertyName, out JsonElement el) ? el.GetString() : null;
+ }
+
+ /// <summary>
+ /// Shortcut extension to <see cref="JsonElement.GetProperty(string)"/> and returns a string
+ /// </summary>
+ /// <param name="conf"></param>
+ /// <param name="propertyName">The name of the property to get the string value of</param>
+ /// <returns>If the property exists, returns the string stored at that property</returns>
+ public static string? GetPropString(this IDictionary<string, JsonElement> conf, string propertyName)
+ {
+ return conf.TryGetValue(propertyName, out JsonElement el) ? el.GetString() : null;
+ }
+
+ /// <summary>
+ /// Attemts to serialze an object to a JSON encoded string
+ /// </summary>
+ /// <param name="obj"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> to use during serialization</param>
+ /// <returns>A JSON encoded string of the serialized object, or null if the object is null</returns>
+ /// <exception cref="NotSupportedException"></exception>
+ public static string? ToJsonString<T>(this T obj, JsonSerializerOptions? options = null)
+ {
+ return obj == null ? null : JsonSerializer.Serialize(obj, options);
+ }
+
+ /// <summary>
+ /// Merges the current <see cref="JsonDocument"/> with another <see cref="JsonDocument"/> to
+ /// create a new document of combined properties
+ /// </summary>
+ /// <param name="initial"></param>
+ /// <param name="other">The <see cref="JsonDocument"/> to combine with the first document</param>
+ /// <param name="initalName">The name of the new element containing the initial document data</param>
+ /// <param name="secondName">The name of the new element containing the additional document data</param>
+ /// <returns>A new document with a parent root containing the combined objects</returns>
+ public static JsonDocument Merge(this JsonDocument initial, JsonDocument other, string initalName, string secondName)
+ {
+ //Open a new memory buffer
+ using VnMemoryStream ms = new();
+ //Encapuslate the memory stream in a writer
+ using (Utf8JsonWriter writer = new(ms))
+ {
+ //Write the starting
+ writer.WriteStartObject();
+ //Write the first object name
+ writer.WritePropertyName(initalName);
+ //Write the inital docuemnt to the stream
+ initial.WriteTo(writer);
+ //Write the second object property
+ writer.WritePropertyName(secondName);
+ //Write the merging document to the stream
+ other.WriteTo(writer);
+ //End the parent element
+ writer.WriteEndObject();
+ }
+ //rewind the buffer
+ _ = ms.Seek(0, System.IO.SeekOrigin.Begin);
+ //Parse the stream into the new document and return it
+ return JsonDocument.Parse(ms);
+ }
+
+ /// <summary>
+ /// Parses a number value into a <see cref="TimeSpan"/> of the specified time
+ /// </summary>
+ /// <param name="el"></param>
+ /// <param name="type">The <see cref="TimeParseType"/> the value represents</param>
+ /// <returns>The <see cref="TimeSpan"/> of the value</returns>
+ /// <exception cref="FormatException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static TimeSpan GetTimeSpan(this in JsonElement el, TimeParseType type)
+ {
+ return type switch
+ {
+ TimeParseType.Milliseconds => TimeSpan.FromMilliseconds(el.GetDouble()),
+ TimeParseType.Seconds => TimeSpan.FromSeconds(el.GetDouble()),
+ TimeParseType.Minutes => TimeSpan.FromMinutes(el.GetDouble()),
+ TimeParseType.Hours => TimeSpan.FromHours(el.GetDouble()),
+ TimeParseType.Days => TimeSpan.FromDays(el.GetDouble()),
+ TimeParseType.Ticks => TimeSpan.FromTicks(el.GetInt64()),
+ _ => throw new NotSupportedException(),
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/MemoryExtensions.cs b/Utils/src/Extensions/MemoryExtensions.cs
new file mode 100644
index 0000000..c8ee5ef
--- /dev/null
+++ b/Utils/src/Extensions/MemoryExtensions.cs
@@ -0,0 +1,769 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: MemoryExtensions.cs
+*
+* MemoryExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Text;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Utils.Extensions
+{
+ using Utils.Memory;
+ using VNLib.Utils.Resources;
+
+ /// <summary>
+ /// Provides memory based extensions to .NET and VNLib memory abstractions
+ /// </summary>
+ public static class MemoryExtensions
+ {
+ /// <summary>
+ /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
+ /// array when work is completed
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="pool"></param>
+ /// <param name="size">The minimum size array to allocate</param>
+ /// <param name="zero">Should elements from 0 to size be set to default(T)</param>
+ /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
+ public static UnsafeMemoryHandle<T> Lease<T>(this ArrayPool<T> pool, int size, bool zero = false) where T: unmanaged
+ {
+ //Pool buffer handles are considered "safe" so im reusing code for now
+ return new(pool, size, zero);
+ }
+
+ /// <summary>
+ /// Retreives a buffer that is at least the reqested length, and clears the array from 0-size.
+ /// <br></br>
+ /// The array may be larger than the requested size, and the entire buffer is zeroed
+ /// </summary>
+ /// <param name="pool"></param>
+ /// <param name="size">The minimum length of the array</param>
+ /// <param name="zero">True if contents should be zeroed</param>
+ /// <returns>The zeroed array</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T[] Rent<T>(this ArrayPool<T> pool, int size, bool zero)
+ {
+ //Rent the array
+ T[] arr = pool.Rent(size);
+ //If zero flag is set, zero only the used section
+ if (zero)
+ {
+ Array.Fill(arr, default);
+ }
+ return arr;
+ }
+
+ /// <summary>
+ /// Copies the characters within the memory handle to a <see cref="string"/>
+ /// </summary>
+ /// <returns>The string representation of the buffer</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string ToString<T>(this T charBuffer) where T: IMemoryHandle<char>
+ {
+ return charBuffer.Span.ToString();
+ }
+
+ /// <summary>
+ /// Wraps the <see cref="MemoryHandle{T}"/> instance in System.Buffers.MemoryManager
+ /// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles.
+ /// </summary>
+ /// <typeparam name="T">The unmanaged data type</typeparam>
+ /// <param name="handle"></param>
+ /// <param name="ownsHandle">
+ /// A value that indicates if the new <see cref="MemoryManager{T}"/> owns the handle.
+ /// When <c>true</c>, the new <see cref="MemoryManager{T}"/> maintains the lifetime of the handle.
+ /// </param>
+ /// <returns>The <see cref="MemoryManager{T}"/> wrapper</returns>
+ /// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryManager<T> ToMemoryManager<T>(this MemoryHandle<T> handle, bool ownsHandle = true) where T : unmanaged
+ {
+ _ = handle ?? throw new ArgumentNullException(nameof(handle));
+ return new SysBufferMemoryManager<T>(handle, ownsHandle);
+ }
+
+ /// <summary>
+ /// Wraps the <see cref="VnTempBuffer{T}"/> instance in System.Buffers.MemoryManager
+ /// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles.
+ /// </summary>
+ /// <typeparam name="T">The unmanaged data type</typeparam>
+ /// <param name="handle"></param>
+ /// <param name="ownsHandle">
+ /// A value that indicates if the new <see cref="MemoryManager{T}"/> owns the handle.
+ /// When <c>true</c>, the new <see cref="MemoryManager{T}"/> maintains the lifetime of the handle.
+ /// </param>
+ /// <returns>The <see cref="MemoryManager{T}"/> wrapper</returns>
+ /// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryManager<T> ToMemoryManager<T>(this VnTempBuffer<T> handle, bool ownsHandle = true) where T : unmanaged
+ {
+ _ = handle ?? throw new ArgumentNullException(nameof(handle));
+ return new SysBufferMemoryManager<T>(handle, ownsHandle);
+ }
+
+ /// <summary>
+ /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="PrivateHeap"/> instance
+ /// of the specified number of elements
+ /// </summary>
+ /// <typeparam name="T">The unmanaged data type</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="size">The number of elements to allocate on the heap</param>
+ /// <param name="zero">Optionally zeros conents of the block when allocated</param>
+ /// <returns>The <see cref="MemoryManager{T}"/> wrapper around the block of memory</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, ulong size, bool zero = false) where T : unmanaged
+ {
+ return new SysBufferMemoryManager<T>(heap, size, zero);
+ }
+
+ /// <summary>
+ /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="PrivateHeap"/> instance
+ /// of the specified number of elements
+ /// </summary>
+ /// <typeparam name="T">The unmanaged data type</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="size">The number of elements to allocate on the heap</param>
+ /// <param name="zero">Optionally zeros conents of the block when allocated</param>
+ /// <returns>The <see cref="MemoryManager{T}"/> wrapper around the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, long size, bool zero = false) where T : unmanaged
+ {
+ return size < 0 ? throw new ArgumentOutOfRangeException(nameof(size)) : DirectAlloc<T>(heap, (ulong)size, zero);
+ }
+ /// <summary>
+ /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks
+ /// </summary>
+ /// <param name="memory"></param>
+ /// <param name="elements">Number of elements of type to offset</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <returns><typeparamref name="T"/> pointer to the memory offset specified</returns>
+ /// [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe T* GetOffset<T>(this MemoryHandle<T> memory, long elements) where T : unmanaged
+ {
+ return elements < 0 ? throw new ArgumentOutOfRangeException(nameof(elements)) : memory.GetOffset((ulong)elements);
+ }
+ /// <summary>
+ /// Resizes the current handle on the heap
+ /// </summary>
+ /// <param name="memory"></param>
+ /// <param name="elements">Positive number of elemnts the current handle should referrence</param>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Resize<T>(this MemoryHandle<T> memory, long elements) where T : unmanaged
+ {
+ if (elements < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elements));
+ }
+ memory.Resize((ulong)elements);
+ }
+
+ /// <summary>
+ /// Resizes the target handle only if the handle is smaller than the requested element count
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="handle"></param>
+ /// <param name="count">The number of elements to resize to</param>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, long count) where T : unmanaged
+ {
+ if(count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ ResizeIfSmaller(handle, (ulong)count);
+ }
+
+ /// <summary>
+ /// Resizes the target handle only if the handle is smaller than the requested element count
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="handle"></param>
+ /// <param name="count">The number of elements to resize to</param>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, ulong count) where T : unmanaged
+ {
+ //Check handle size
+ if(handle.Length < count)
+ {
+ //handle too small, resize
+ handle.Resize(count);
+ }
+ }
+
+#if TARGET_64_BIT
+ /// <summary>
+ /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">The offset (in elements) from the begining of the block</param>
+ /// <param name="size">The size of the block (in elements)</param>
+ /// <returns>The offset span</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, ulong offset, int size) where T: unmanaged
+ {
+ _ = block ?? throw new ArgumentNullException(nameof(block));
+ if(size < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(size));
+ }
+ if(size == 0)
+ {
+ return Span<T>.Empty;
+ }
+ //Make sure the offset size is within the size of the block
+ if(offset + (ulong)size <= block.Length)
+ {
+ //Get long offset from the destination handle
+ void* ofPtr = block.GetOffset(offset);
+ return new Span<T>(ofPtr, size);
+ }
+ throw new ArgumentOutOfRangeException(nameof(size));
+ }
+ /// <summary>
+ /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">The offset (in elements) from the begining of the block</param>
+ /// <param name="size">The size of the block (in elements)</param>
+ /// <returns>The offset span</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, long offset, int size) where T : unmanaged
+ {
+ return offset < 0 ? throw new ArgumentOutOfRangeException(nameof(offset)) : block.GetOffsetSpan<T>((ulong)offset, size);
+ }
+
+
+ /// <summary>
+ /// Gets a <see cref="SubSequence{T}"/> window within the current block
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">An offset within the handle</param>
+ /// <param name="size">The size of the window</param>
+ /// <returns>The new <see cref="SubSequence{T}"/> within the block</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, ulong offset, int size) where T : unmanaged
+ {
+ return new SubSequence<T>(block, offset, size);
+ }
+#else
+
+ /// <summary>
+ /// Gets a <see cref="SubSequence{T}"/> window within the current block
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">An offset within the handle</param>
+ /// <param name="size">The size of the window</param>
+ /// <returns>The new <see cref="SubSequence{T}"/> within the block</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, int offset, int size) where T : unmanaged
+ {
+ return new SubSequence<T>(block, offset, size);
+ }
+
+ /// <summary>
+ /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">The offset (in elements) from the begining of the block</param>
+ /// <param name="size">The size of the block (in elements)</param>
+ /// <returns>The offset span</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, long offset, int size) where T : unmanaged
+ {
+ //TODO fix 32bit/64 bit, this is a safe lazy workaround
+ return block.Span.Slice(checked((int) offset), size);
+ }
+#endif
+
+ /// <summary>
+ /// Wraps the current instance with a <see cref="MemoryPool{T}"/> wrapper
+ /// to allow System.Memory buffer rentals.
+ /// </summary>
+ /// <typeparam name="T">The unmanged data type to provide allocations from</typeparam>
+ /// <returns>The new <see cref="MemoryPool{T}"/> heap wrapper.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap) where T : unmanaged
+ {
+ return new PrivateBuffersMemoryPool<T>(heap);
+ }
+
+ /// <summary>
+ /// Allocates a structure of the specified type on the current unmanged heap and zero's its memory
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="heap"></param>
+ /// <returns>A pointer to the structure ready for use.</returns>
+ /// <remarks>Allocations must be freed with <see cref="StructFree{T}(IUnmangedHeap, T*)"/></remarks>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged
+ {
+ //Allocate the struct on the heap and zero memory it points to
+ IntPtr handle = heap.Alloc(1, (uint)sizeof(T), true);
+ //returns the handle
+ return (T*)handle;
+ }
+ /// <summary>
+ /// Frees a structure at the specified address from the this heap.
+ /// This must be the same heap the structure was allocated from
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="structPtr">A pointer to the structure</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void StructFree<T>(this IUnmangedHeap heap, T* structPtr) where T : unmanaged
+ {
+ IntPtr block = new(structPtr);
+ //Free block from heap
+ heap.Free(ref block);
+ //Clear ref
+ *structPtr = default;
+ }
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, ulong elements, bool zero = false) where T : unmanaged
+ {
+ //Minimum of one element
+ elements = Math.Max(elements, 1);
+ //Get element size
+ uint elementSize = (uint)sizeof(T);
+ //If zero flag is set then specify zeroing memory
+ IntPtr block = heap.Alloc(elements, elementSize, zero);
+ //Return handle wrapper
+ return new MemoryHandle<T>(heap, block, elements, zero);
+ }
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, long elements, bool zero = false) where T : unmanaged
+ {
+ return elements < 0 ? throw new ArgumentOutOfRangeException(nameof(elements)) : Alloc<T>(heap, (ulong)elements, zero);
+ }
+ /// <summary>
+ /// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="heap"></param>
+ /// <param name="initialData">The initial data to set the buffer to</param>
+ /// <returns>The initalized <see cref="MemoryHandle{T}"/> block</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T:unmanaged
+ {
+ MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
+ Memory.Copy(initialData, handle, 0);
+ return handle;
+ }
+
+ /// <summary>
+ /// Copies data from the input buffer to the current handle and resizes the handle to the
+ /// size of the buffer
+ /// </summary>
+ /// <typeparam name="T">The unamanged value type</typeparam>
+ /// <param name="handle"></param>
+ /// <param name="input">The input buffer to copy data from</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteAndResize<T>(this MemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged
+ {
+ handle.Resize(input.Length);
+ Memory.Copy(input, handle, 0);
+ }
+
+ /// <summary>
+ /// Allocates a block of unamanged memory of the number of elements of an unmanaged type, and
+ /// returns the <see cref="UnsafeMemoryHandle{T}"/> that must be used cautiously
+ /// </summary>
+ /// <typeparam name="T">The unamanged value type</typeparam>
+ /// <param name="heap">The heap to allocate block from</param>
+ /// <param name="elements">The number of elements to allocate</param>
+ /// <param name="zero">A flag to zero the initial contents of the buffer</param>
+ /// <returns>The allocated handle of the specified number of elements</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ {
+ if (elements < 1)
+ {
+ throw new ArgumentException("Elements must be greater than 0", nameof(elements));
+ }
+ //Minimum of one element
+ elements = Math.Max(elements, 1);
+ //Get element size
+ uint elementSize = (uint)sizeof(T);
+ //If zero flag is set then specify zeroing memory
+ IntPtr block = heap.Alloc((uint)elements, elementSize, zero);
+ //handle wrapper
+ return new (heap, block, elements);
+ }
+
+ #region VnBufferWriter
+
+ /// <summary>
+ /// Formats and appends a value type to the writer with proper endianess
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="value">The value to format and append to the buffer</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Append<T>(this ref ForwardOnlyWriter<byte> buffer, T value) where T: unmanaged
+ {
+ //Calc size of structure and fix te size of the buffer
+ int size = Unsafe.SizeOf<T>();
+ Span<byte> output = buffer.Remaining[..size];
+
+ //Format value and write to buffer
+ MemoryMarshal.Write(output, ref value);
+
+ //If byte order is reversed, reverse elements
+ if (!BitConverter.IsLittleEndian)
+ {
+ output.Reverse();
+ }
+
+ //Update written posiion
+ buffer.Advance(size);
+ }
+
+ /// <summary>
+ /// Formats and appends a value type to the writer with proper endianess
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="value">The value to format and append to the buffer</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Append<T>(this ref ForwardOnlyMemoryWriter<byte> buffer, T value) where T : struct
+ {
+ //Format value and write to buffer
+ int size = Unsafe.SizeOf<T>();
+ Span<byte> output = buffer.Remaining.Span[..size];
+
+ //Format value and write to buffer
+ MemoryMarshal.Write(output, ref value);
+
+ //If byte order is reversed, reverse elements
+ if (BitConverter.IsLittleEndian)
+ {
+ output.Reverse();
+ }
+
+ //Update written posiion
+ buffer.Advance(size);
+ }
+
+ /// <summary>
+ /// Formats and appends the value to end of the buffer
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="value">The value to format and append to the buffer</param>
+ /// <param name="format">An optional format argument</param>
+ /// <param name="formatProvider"></param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this ref ForwardOnlyWriter<char> buffer, T value, ReadOnlySpan<char> format = default, IFormatProvider? formatProvider = default) where T : ISpanFormattable
+ {
+ //Format value and write to buffer
+ if (!value.TryFormat(buffer.Remaining, out int charsWritten, format, formatProvider))
+ {
+ throw new ArgumentOutOfRangeException(nameof(buffer), "The value could not be formatted and appended to the buffer, because there is not enough available space");
+ }
+ //Update written posiion
+ buffer.Advance(charsWritten);
+ }
+
+ /// <summary>
+ /// Formats and appends the value to end of the buffer
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="value">The value to format and append to the buffer</param>
+ /// <param name="format">An optional format argument</param>
+ /// <param name="formatProvider"></param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this ref ForwardOnlyMemoryWriter<char> buffer, T value, ReadOnlySpan<char> format = default, IFormatProvider? formatProvider = default) where T : ISpanFormattable
+ {
+ //Format value and write to buffer
+ if (!value.TryFormat(buffer.Remaining.Span, out int charsWritten, format, formatProvider))
+ {
+ throw new ArgumentOutOfRangeException(nameof(buffer), "The value could not be formatted and appended to the buffer, because there is not enough available space");
+ }
+ //Update written posiion
+ buffer.Advance(charsWritten);
+ }
+
+
+
+ /// <summary>
+ /// Encodes a set of characters in the input characters span and any characters
+ /// in the internal buffer into a sequence of bytes that are stored in the input
+ /// byte span. A parameter indicates whether to clear the internal state of the
+ /// encoder after the conversion.
+ /// </summary>
+ /// <param name="enc"></param>
+ /// <param name="chars">Character buffer to encode</param>
+ /// <param name="offset">The offset in the char buffer to begin encoding chars from</param>
+ /// <param name="charCount">The number of characers to encode</param>
+ /// <param name="writer">The buffer writer to use</param>
+ /// <param name="flush">true to clear the internal state of the encoder after the conversion; otherwise, false.</param>
+ /// <returns>The actual number of bytes written at the location indicated by the bytes parameter.</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetBytes(this Encoder enc, char[] chars, int offset, int charCount, ref ForwardOnlyWriter<byte> writer, bool flush)
+ {
+ return GetBytes(enc, chars.AsSpan(offset, charCount), ref writer, flush);
+ }
+ /// <summary>
+ /// Encodes a set of characters in the input characters span and any characters
+ /// in the internal buffer into a sequence of bytes that are stored in the input
+ /// byte span. A parameter indicates whether to clear the internal state of the
+ /// encoder after the conversion.
+ /// </summary>
+ /// <param name="enc"></param>
+ /// <param name="chars">The character buffer to encode</param>
+ /// <param name="writer">The buffer writer to use</param>
+ /// <param name="flush">true to clear the internal state of the encoder after the conversion; otherwise, false.</param>
+ /// <returns>The actual number of bytes written at the location indicated by the bytes parameter.</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetBytes(this Encoder enc, ReadOnlySpan<char> chars, ref ForwardOnlyWriter<byte> writer, bool flush)
+ {
+ //Encode the characters
+ int written = enc.GetBytes(chars, writer.Remaining, flush);
+ //Update the writer position
+ writer.Advance(written);
+ return written;
+ }
+ /// <summary>
+ /// Encodes a set of characters in the input characters span and any characters
+ /// in the internal buffer into a sequence of bytes that are stored in the input
+ /// byte span.
+ /// </summary>
+ /// <param name="encoding"></param>
+ /// <param name="chars">The character buffer to encode</param>
+ /// <param name="writer">The buffer writer to use</param>
+ /// <returns>The actual number of bytes written at the location indicated by the bytes parameter.</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static int GetBytes(this Encoding encoding, ReadOnlySpan<char> chars, ref ForwardOnlyWriter<byte> writer)
+ {
+ //Encode the characters
+ int written = encoding.GetBytes(chars, writer.Remaining);
+ //Update the writer position
+ writer.Advance(written);
+ return written;
+ }
+ /// <summary>
+ /// Decodes a character buffer in the input characters span and any characters
+ /// in the internal buffer into a sequence of bytes that are stored in the input
+ /// byte span.
+ /// </summary>
+ /// <param name="encoding"></param>
+ /// <param name="bytes">The binary buffer to decode</param>
+ /// <param name="writer">The buffer writer to use</param>
+ /// <returns>The actual number of *characters* written at the location indicated by the chars parameter.</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static int GetChars(this Encoding encoding, ReadOnlySpan<byte> bytes, ref ForwardOnlyWriter<char> writer)
+ {
+ int charCount = encoding.GetCharCount(bytes);
+ //Encode the characters
+ _ = encoding.GetChars(bytes, writer.Remaining);
+ //Update the writer position
+ writer.Advance(charCount);
+ return charCount;
+ }
+
+ /// <summary>
+ /// Converts the buffer data to a <see cref="PrivateString"/>
+ /// </summary>
+ /// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns>
+ public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true);
+ /// <summary>
+ /// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer
+ /// </summary>
+ /// <returns>A <see cref="Span{T}"/> over the modified data</returns>
+ public static Span<T> AsSpan<T>(this ref ForwardOnlyWriter<T> buffer) => buffer.Buffer[..buffer.Written];
+
+
+ #endregion
+
+ /// <summary>
+ /// Slices the current array by the specified starting offset to the end
+ /// of the array
+ /// </summary>
+ /// <typeparam name="T">The array type</typeparam>
+ /// <param name="arr"></param>
+ /// <param name="start">The start offset of the new array slice</param>
+ /// <returns>The sliced array</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static T[] Slice<T>(this T[] arr, int start)
+ {
+ if(start < 0 || start > arr.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(start));
+ }
+ Range sliceRange = new(start, arr.Length - start);
+ return RuntimeHelpers.GetSubArray(arr, sliceRange);
+ }
+ /// <summary>
+ /// Slices the current array by the specified starting offset to including the
+ /// speciifed number of items
+ /// </summary>
+ /// <typeparam name="T">The array type</typeparam>
+ /// <param name="arr"></param>
+ /// <param name="start">The start offset of the new array slice</param>
+ /// <param name="count">The size of the new array</param>
+ /// <returns>The sliced array</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static T[] Slice<T>(this T[] arr, int start, int count)
+ {
+ if(start < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(start));
+ }
+ if(count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if(start + count >= arr.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if(count == 0)
+ {
+ return Array.Empty<T>();
+ }
+ //Calc the slice range
+ Range sliceRange = new(start, start + count);
+ return RuntimeHelpers.GetSubArray(arr, sliceRange);
+ }
+
+ /// <summary>
+ /// Creates a new sub-sequence over the target handle. (allows for convient sub span)
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="handle"></param>
+ /// <param name="start">Intial offset into the handle</param>
+ /// <returns>The sub-sequence of the current handle</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this IMemoryHandle<T> handle, int start) => handle.Span[start..];
+
+ /// <summary>
+ /// Creates a new sub-sequence over the target handle. (allows for convient sub span)
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="handle"></param>
+ /// <param name="start">Intial offset into the handle</param>
+ /// <param name="count">The number of elements within the new sequence</param>
+ /// <returns>The sub-sequence of the current handle</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this IMemoryHandle<T> handle, int start, int count) => handle.Span.Slice(start, count);
+
+ /// <summary>
+ /// Creates a new sub-sequence over the target handle. (allows for convient sub span)
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="handle"></param>
+ /// <param name="start">Intial offset into the handle</param>
+ /// <returns>The sub-sequence of the current handle</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this in UnsafeMemoryHandle<T> handle, int start) where T: unmanaged => handle.Span[start..];
+
+ /// <summary>
+ /// Creates a new sub-sequence over the target handle. (allows for convient sub span)
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="handle"></param>
+ /// <param name="start">Intial offset into the handle</param>
+ /// <param name="count">The number of elements within the new sequence</param>
+ /// <returns>The sub-sequence of the current handle</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this in UnsafeMemoryHandle<T> handle, int start, int count) where T : unmanaged => handle.Span.Slice(start, count);
+
+ /// <summary>
+ /// Raises an <see cref="ObjectDisposedException"/> if the current handle
+ /// has been disposed or set as invalid
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ThrowIfClosed(this SafeHandle handle)
+ {
+ if (handle.IsClosed || handle.IsInvalid)
+ {
+ throw new ObjectDisposedException(handle.GetType().Name);
+ }
+ }
+ }
+}
diff --git a/Utils/src/Extensions/MutexReleaser.cs b/Utils/src/Extensions/MutexReleaser.cs
new file mode 100644
index 0000000..84dd60f
--- /dev/null
+++ b/Utils/src/Extensions/MutexReleaser.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: MutexReleaser.cs
+*
+* MutexReleaser.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// Represents a releaser handle for a <see cref="Mutex"/>
+ /// that has been entered and will be released. Best if used
+ /// within a using() statment
+ /// </summary>
+ public readonly struct MutexReleaser : IDisposable, IEquatable<MutexReleaser>
+ {
+ private readonly Mutex _mutext;
+ internal MutexReleaser(Mutex mutex) => _mutext = mutex;
+ /// <summary>
+ /// Releases the held System.Threading.Mutex once.
+ /// </summary>
+ public readonly void Dispose() => _mutext.ReleaseMutex();
+ /// <summary>
+ /// Releases the held System.Threading.Mutex once.
+ /// </summary>
+ public readonly void ReleaseMutext() => _mutext.ReleaseMutex();
+
+ ///<inheritdoc/>
+ public bool Equals(MutexReleaser other) => _mutext.Equals(other._mutext);
+
+ ///<inheritdoc/>
+ public override bool Equals(object? obj) => obj is MutexReleaser releaser && Equals(releaser);
+
+ ///<inheritdoc/>
+ public override int GetHashCode() => _mutext.GetHashCode();
+
+ ///<inheritdoc/>
+ public static bool operator ==(MutexReleaser left, MutexReleaser right) => left.Equals(right);
+ ///<inheritdoc/>
+ public static bool operator !=(MutexReleaser left, MutexReleaser right) => !(left == right);
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/SafeLibraryExtensions.cs b/Utils/src/Extensions/SafeLibraryExtensions.cs
new file mode 100644
index 0000000..8866059
--- /dev/null
+++ b/Utils/src/Extensions/SafeLibraryExtensions.cs
@@ -0,0 +1,103 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SafeLibraryExtensions.cs
+*
+* SafeLibraryExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Reflection;
+
+using VNLib.Utils.Native;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// When applied to a delegate, specifies the name of the native method to load
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Delegate)]
+ public sealed class SafeMethodNameAttribute : Attribute
+ {
+ /// <summary>
+ /// Creates a new <see cref="SafeMethodNameAttribute"/>
+ /// </summary>
+ /// <param name="MethodName">The name of the native method</param>
+ public SafeMethodNameAttribute(string MethodName) => this.MethodName = MethodName;
+ /// <summary>
+ /// Creates a new <see cref="SafeMethodNameAttribute"/>, that uses the
+ /// delegate name as the native method name
+ /// </summary>
+ public SafeMethodNameAttribute() => MethodName = null;
+ /// <summary>
+ /// The name of the native method
+ /// </summary>
+ public string? MethodName { get; }
+ }
+
+
+ /// <summary>
+ /// Contains native library extension methods
+ /// </summary>
+ public static class SafeLibraryExtensions
+ {
+ const string _missMemberExceptionMessage = $"The delegate type is missing the required {nameof(SafeMethodNameAttribute)} to designate the native method to load";
+
+ /// <summary>
+ /// Loads a native method from the current <see cref="SafeLibraryHandle"/>
+ /// that has a <see cref="SafeMethodNameAttribute"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="library"></param>
+ /// <returns></returns>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static SafeMethodHandle<T> GetMethod<T>(this SafeLibraryHandle library) where T : Delegate
+ {
+ Type t = typeof(T);
+ //Get the method name attribute
+ SafeMethodNameAttribute? attr = t.GetCustomAttribute<SafeMethodNameAttribute>();
+ _ = attr ?? throw new MissingMemberException(_missMemberExceptionMessage);
+ return library.GetMethod<T>(attr.MethodName ?? t.Name);
+ }
+ /// <summary>
+ /// Loads a native method from the current <see cref="SafeLibraryHandle"/>
+ /// that has a <see cref="SafeMethodNameAttribute"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="library"></param>
+ /// <returns></returns>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ /// <remarks>
+ /// The libraries handle count is left unmodified
+ /// </remarks>
+ public static T DangerousGetMethod<T>(this SafeLibraryHandle library) where T: Delegate
+ {
+ Type t = typeof(T);
+ //Get the method name attribute
+ SafeMethodNameAttribute? attr = t.GetCustomAttribute<SafeMethodNameAttribute>();
+ return string.IsNullOrWhiteSpace(attr?.MethodName)
+ ? throw new MissingMemberException(_missMemberExceptionMessage)
+ : library.DangerousGetMethod<T>(attr.MethodName);
+ }
+ }
+}
diff --git a/Utils/src/Extensions/SemSlimReleaser.cs b/Utils/src/Extensions/SemSlimReleaser.cs
new file mode 100644
index 0000000..c8a22fe
--- /dev/null
+++ b/Utils/src/Extensions/SemSlimReleaser.cs
@@ -0,0 +1,51 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SemSlimReleaser.cs
+*
+* SemSlimReleaser.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+
+namespace VNLib.Utils.Extensions
+{
+ /// <summary>
+ /// Represents a releaser handle for a <see cref="SemaphoreSlim"/>
+ /// that has been entered and will be released. Best if used
+ /// within a using() statment
+ /// </summary>
+ public readonly struct SemSlimReleaser : IDisposable
+ {
+ private readonly SemaphoreSlim _semaphore;
+ internal SemSlimReleaser(SemaphoreSlim semaphore) => _semaphore = semaphore;
+ /// <summary>
+ /// Releases the System.Threading.SemaphoreSlim object once.
+ /// </summary>
+ public readonly void Dispose() => _semaphore.Release();
+ /// <summary>
+ /// Releases the System.Threading.SemaphoreSlim object once.
+ /// </summary>
+ /// <returns>The previous count of the <see cref="SemaphoreSlim"/></returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="SemaphoreFullException"></exception>
+ public readonly int Release() => _semaphore.Release();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/StringExtensions.cs b/Utils/src/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..09d6517
--- /dev/null
+++ b/Utils/src/Extensions/StringExtensions.cs
@@ -0,0 +1,481 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: StringExtensions.cs
+*
+* StringExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.Extensions
+{
+ public delegate void StatelessSpanAction(ReadOnlySpan<char> line);
+
+ /// <summary>
+ /// Extention methods for string (character buffer)
+ /// </summary>
+ public static class StringExtensions
+ {
+ /// <summary>
+ /// Split a string based on split value and insert into the specified list
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The value to split the string on</param>
+ /// <param name="output">The list to output data to</param>
+ /// <param name="options">String split options</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split<T>(this string value, string splitter, T output, StringSplitOptions options) where T : ICollection<string>
+ {
+ Split(value, splitter.AsSpan(), output, options);
+ }
+ /// <summary>
+ /// Split a string based on split value and insert into the specified list
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The value to split the string on</param>
+ /// <param name="output">The list to output data to</param>
+ /// <param name="options">String split options</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split<T>(this string value, char splitter, T output, StringSplitOptions options) where T: ICollection<string>
+ {
+ //Create span from char pointer
+ ReadOnlySpan<char> cs = MemoryMarshal.CreateReadOnlySpan(ref splitter, 1);
+ //Call the split function on the span
+ Split(value, cs, output, options);
+ }
+ /// <summary>
+ /// Split a string based on split value and insert into the specified list
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The value to split the string on</param>
+ /// <param name="output">The list to output data to</param>
+ /// <param name="options">String split options</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split<T>(this string value, ReadOnlySpan<char> splitter, T output, StringSplitOptions options) where T : ICollection<string>
+ {
+ Split(value.AsSpan(), splitter, output, options);
+ }
+ /// <summary>
+ /// Split a string based on split value and insert into the specified list
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The value to split the string on</param>
+ /// <param name="output">The list to output data to</param>
+ /// <param name="options">String split options</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split<T>(this in ReadOnlySpan<char> value, char splitter, T output, StringSplitOptions options) where T : ICollection<string>
+ {
+ //Create span from char pointer
+ ReadOnlySpan<char> cs = MemoryMarshal.CreateReadOnlySpan(ref splitter, 1);
+ //Call the split function on the span
+ Split(in value, cs, output, options);
+ }
+ /// <summary>
+ /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and insert into the specified list
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The value to split the string on</param>
+ /// <param name="output">The list to output data to</param>
+ /// <param name="options">String split options</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split<T>(this in ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, T output, StringSplitOptions options) where T : ICollection<string>
+ {
+ //Create a local function that adds the split strings to the list
+ static void SplitFound(ReadOnlySpan<char> split, T output) => output.Add(split.ToString());
+ //Invoke the split function with the local callback method
+ Split(in value, splitter, options, SplitFound, output);
+ }
+ /// <summary>
+ /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The sequence to split the string on</param>
+ /// <param name="options">String split options</param>
+ /// <param name="splitCb">The action to invoke when a split segment has been found</param>
+ /// <param name="state">The state to pass to the callback handler</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Split<T>(this in ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, StringSplitOptions options, ReadOnlySpanAction<char, T> splitCb, T state)
+ {
+ _ = splitCb ?? throw new ArgumentNullException(nameof(splitCb));
+ //Get span over string
+ ForwardOnlyReader<char> reader = new(value);
+ //No string options
+ if (options == 0)
+ {
+ do
+ {
+ //Find index of the splitter
+ int start = reader.Window.IndexOf(splitter);
+ //guard
+ if (start == -1)
+ {
+ break;
+ }
+ //Trim and add it regardless of length
+ splitCb(reader.Window[..start], state);
+ //shift window
+ reader.Advance(start + splitter.Length);
+ } while (true);
+ //Trim remaining and add it regardless of length
+ splitCb(reader.Window, state);
+ }
+ //Trim but do not remove empties
+ else if ((options & StringSplitOptions.RemoveEmptyEntries) == 0)
+ {
+ do
+ {
+ //Find index of the splitter
+ int start = reader.Window.IndexOf(splitter);
+ //guard
+ if (start == -1)
+ {
+ break;
+ }
+ //Trim and add it regardless of length
+ splitCb(reader.Window[..start].Trim(), state);
+ //shift window
+ reader.Advance(start + splitter.Length);
+ } while (true);
+ //Trim remaining and add it regardless of length
+ splitCb(reader.Window.Trim(), state);
+ }
+ //Remove empty entires but do not trim them
+ else if ((options & StringSplitOptions.TrimEntries) == 0)
+ {
+ //Get data before splitter and trim it
+ ReadOnlySpan<char> data;
+ do
+ {
+ //Find index of the splitter
+ int start = reader.Window.IndexOf(splitter);
+ //guard
+ if (start == -1)
+ {
+ break;
+ }
+ //Get data before splitter and trim it
+ data = reader.Window[..start];
+ //If its not empty, then add it to the list
+ if (!data.IsEmpty)
+ {
+ splitCb(data, state);
+ }
+ //shift window
+ reader.Advance(start + splitter.Length);
+ } while (true);
+ //Add if not empty
+ if (reader.WindowSize > 0)
+ {
+ splitCb(reader.Window, state);
+ }
+ }
+ //Must mean remove and trim
+ else
+ {
+ //Get data before splitter and trim it
+ ReadOnlySpan<char> data;
+ do
+ {
+ //Find index of the splitter
+ int start = reader.Window.IndexOf(splitter);
+ //guard
+ if (start == -1)
+ {
+ break;
+ }
+ //Get data before splitter and trim it
+ data = reader.Window[..start].Trim();
+ //If its not empty, then add it to the list
+ if (!data.IsEmpty)
+ {
+ splitCb(data, state);
+ }
+ //shift window
+ reader.Advance(start + splitter.Length);
+ } while (true);
+ //Trim remaining
+ data = reader.Window.Trim();
+ //Add if not empty
+ if (!data.IsEmpty)
+ {
+ splitCb(data, state);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The character to split the string on</param>
+ /// <param name="options">String split options</param>
+ /// <param name="splitCb">The action to invoke when a split segment has been found</param>
+ /// <param name="state"></param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split<T>(this in ReadOnlySpan<char> value, char splitter, StringSplitOptions options, ReadOnlySpanAction<char, T> splitCb, T state)
+ {
+ //Alloc a span for char
+ ReadOnlySpan<char> cs = MemoryMarshal.CreateReadOnlySpan(ref splitter, 1);
+ //Call the split function on the span
+ Split(in value, cs, options, splitCb, state);
+ }
+ /// <summary>
+ /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The sequence to split the string on</param>
+ /// <param name="options">String split options</param>
+ /// <param name="splitCb">The action to invoke when a split segment has been found</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split(this in ReadOnlySpan<char> value, ReadOnlySpan<char> splitter, StringSplitOptions options, StatelessSpanAction splitCb)
+ {
+ //Create a SpanSplitDelegate with the non-typed delegate as the state argument
+ static void ssplitcb(ReadOnlySpan<char> param, StatelessSpanAction callback) => callback(param);
+ //Call split with the new callback delegate
+ Split(in value, splitter, options, ssplitcb, splitCb);
+ }
+ /// <summary>
+ /// Split a <see cref="ReadOnlySpan{T}"/> based on split value and pass it to the split delegate handler
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="splitter">The character to split the string on</param>
+ /// <param name="options">String split options</param>
+ /// <param name="splitCb">The action to invoke when a split segment has been found</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Split(this in ReadOnlySpan<char> value, char splitter, StringSplitOptions options, StatelessSpanAction splitCb)
+ {
+ //Create a SpanSplitDelegate with the non-typed delegate as the state argument
+ static void ssplitcb(ReadOnlySpan<char> param, StatelessSpanAction callback) => callback(param);
+ //Call split with the new callback delegate
+ Split(in value, splitter, options, ssplitcb, splitCb);
+ }
+
+ /// <summary>
+ /// Gets the index of the end of the found sequence
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">Sequence to search for within the current sequence</param>
+ /// <returns>the index of the end of the sequenc</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int EndOf(this in ReadOnlySpan<char> data, ReadOnlySpan<char> search)
+ {
+ int index = data.IndexOf(search);
+ return index > -1 ? index + search.Length : -1;
+ }
+ /// <summary>
+ /// Gets the index of the end of the found character
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">Character to search for within the current sequence</param>
+ /// <returns>the index of the end of the sequence</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int EndOf(this in ReadOnlySpan<char> data, char search)
+ {
+ int index = data.IndexOf(search);
+ return index > -1 ? index + 1 : -1;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf(this in Memory<byte> data, byte search) => data.Span.IndexOf(search);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf(this in Memory<byte> data, ReadOnlySpan<byte> search) => data.Span.IndexOf(search);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf(this in Memory<byte> data, ReadOnlyMemory<byte> search) => IndexOf(data, search.Span);
+
+ /// <summary>
+ /// Slices the current span from the begining of the segment to the first occurrance of the specified character.
+ /// If the character is not found, the entire segment is returned
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">The delimiting character</param>
+ /// <returns>The segment of data before the search character, or the entire segment if not found</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> SliceBeforeParam(this in ReadOnlySpan<char> data, char search)
+ {
+ //Find the index of the specified data
+ int index = data.IndexOf(search);
+ //Return the slice of data before the index, or an empty span if it was not found
+ return index > -1 ? data[..index] : data;
+ }
+ /// <summary>
+ /// Slices the current span from the begining of the segment to the first occurrance of the specified character sequence.
+ /// If the character sequence is not found, the entire segment is returned
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">The delimiting character sequence</param>
+ /// <returns>The segment of data before the search character, or the entire <paramref name="data"/> if the seach sequence is not found</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> SliceBeforeParam(this in ReadOnlySpan<char> data, ReadOnlySpan<char> search)
+ {
+ //Find the index of the specified data
+ int index = data.IndexOf(search);
+ //Return the slice of data before the index, or an empty span if it was not found
+ return index > -1 ? data[..index] : data;
+ }
+ /// <summary>
+ /// Gets the remaining segment of data after the specified search character or <see cref="ReadOnlySpan{T}.Empty"/>
+ /// if the search character is not found within the current segment
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">The character to search for within the segment</param>
+ /// <returns>The segment of data after the search character or <see cref="ReadOnlySpan{T}.Empty"/> if not found</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> SliceAfterParam(this in ReadOnlySpan<char> data, char search)
+ {
+ //Find the index of the specified data
+ int index = EndOf(in data, search);
+ //Return the slice of data after the index, or an empty span if it was not found
+ return index > -1 ? data[index..] : ReadOnlySpan<char>.Empty;
+ }
+ /// <summary>
+ /// Gets the remaining segment of data after the specified search sequence or <see cref="ReadOnlySpan{T}.Empty"/>
+ /// if the search sequence is not found within the current segment
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">The sequence to search for within the segment</param>
+ /// <returns>The segment of data after the search sequence or <see cref="ReadOnlySpan{T}.Empty"/> if not found</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> SliceAfterParam(this in ReadOnlySpan<char> data, ReadOnlySpan<char> search)
+ {
+ //Find the index of the specified data
+ int index = EndOf(data, search);
+ //Return the slice of data after the index, or an empty span if it was not found
+ return index > -1 ? data[index..] : ReadOnlySpan<char>.Empty;
+ }
+ /// <summary>
+ /// Trims any leading or trailing <c>'\r'|'\n'|' '</c>(whitespace) characters from the segment
+ /// </summary>
+ /// <returns>The trimmed segment</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> TrimCRLF(this in ReadOnlySpan<char> data)
+ {
+ int start = 0, end = data.Length;
+ //trim leading \r\n chars
+ while(start < end)
+ {
+ char t = data[start];
+ //If character \r or \n slice it off
+ if (t != '\r' && t != '\n' && t != ' ') {
+ break;
+ }
+ //Shift
+ start++;
+ }
+ //remove trailing crlf characters
+ while (end > start)
+ {
+ char t = data[end - 1];
+ //If character \r or \n slice it off
+ if (t != '\r' && t != '\n' && t != ' ') {
+ break;
+ }
+ end--;
+ }
+ return data[start..end];
+ }
+
+ /// <summary>
+ /// Replaces a character sequence within the buffer
+ /// </summary>
+ /// <param name="buffer">The character buffer to process</param>
+ /// <param name="search">The sequence to search for</param>
+ /// <param name="replace">The sequence to write in the place of the search parameter</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static int Replace(this ref Span<char> buffer, ReadOnlySpan<char> search, ReadOnlySpan<char> replace)
+ {
+ ForwardOnlyWriter<char> writer = new (buffer);
+ writer.Replace(search, replace);
+ return writer.Written;
+ }
+
+ /// <summary>
+ /// Replaces a character sequence within the writer
+ /// </summary>
+ /// <param name="writer"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <param name="replace">The sequence to write in the place of the search parameter</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static void Replace(this ref ForwardOnlyWriter<char> writer, ReadOnlySpan<char> search, ReadOnlySpan<char> replace)
+ {
+ Span<char> buffer = writer.AsSpan();
+ //If the search and replacment parameters are the same length
+ if (search.Length == replace.Length)
+ {
+ buffer.ReplaceInPlace(search, replace);
+ return;
+ }
+ //Search and replace are not the same length
+ int searchLen = search.Length, start = buffer.IndexOf(search);
+ if(start == -1)
+ {
+ return;
+ }
+ //Replacment might be empty
+ writer.Reset();
+ do
+ {
+ //Append the data before the split character
+ writer.Append(buffer[..start]);
+ //Append the replacment
+ writer.Append(replace);
+ //Shift buffer to the end of the
+ buffer = buffer[(start + searchLen)..];
+ //search for next index
+ start = buffer.IndexOf(search);
+ } while (start > -1);
+ //Write remaining data
+ writer.Append(replace);
+ }
+ /// <summary>
+ /// Replaces very ocurrance of character sequence within a buffer with another sequence of the same length
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <param name="replace">The sequence to replace the found sequence with</param>
+ /// <exception cref="ArgumentException"></exception>
+ public static void ReplaceInPlace(this Span<char> buffer, ReadOnlySpan<char> search, ReadOnlySpan<char> replace)
+ {
+ if(search.Length != replace.Length)
+ {
+ throw new ArgumentException("Search parameter and replacment parameter must be the same length");
+ }
+ int start = buffer.IndexOf(search);
+ while(start > -1)
+ {
+ //Shift the buffer to the begining of the search parameter
+ buffer = buffer[start..];
+ //Overwite the search parameter
+ replace.CopyTo(buffer);
+ //Search for next index of the search character
+ start = buffer.IndexOf(search);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/ThreadingExtensions.cs b/Utils/src/Extensions/ThreadingExtensions.cs
new file mode 100644
index 0000000..cc9fab9
--- /dev/null
+++ b/Utils/src/Extensions/ThreadingExtensions.cs
@@ -0,0 +1,226 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ThreadingExtensions.cs
+*
+* ThreadingExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Extensions
+{
+
+ /// <summary>
+ /// Provides extension methods to common threading and TPL library operations
+ /// </summary>
+ public static class ThreadingExtensions
+ {
+ /// <summary>
+ /// Allows an <see cref="OpenResourceHandle{TResource}"/> to execute within a scope limited context
+ /// </summary>
+ /// <typeparam name="TResource">The resource type</typeparam>
+ /// <param name="rh"></param>
+ /// <param name="safeCallback">The function body that will execute with controlled access to the resource</param>
+ public static void EnterSafeContext<TResource>(this OpenResourceHandle<TResource> rh, Action<TResource> safeCallback)
+ {
+ using (rh)
+ {
+ safeCallback(rh.Resource);
+ }
+ }
+
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/> while observing a <see cref="CancellationToken"/>
+ /// and getting a releaser handle
+ /// </summary>
+ /// <param name="semaphore"></param>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A releaser handle that may be disposed to release the semaphore</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="OperationCanceledException"></exception>
+ public static async Task<SemSlimReleaser> GetReleaserAsync(this SemaphoreSlim semaphore, CancellationToken cancellationToken = default)
+ {
+ await semaphore.WaitAsync(cancellationToken);
+ return new SemSlimReleaser(semaphore);
+ }
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/> using a 32-bit signed integer to measure the time intervale
+ /// and getting a releaser handle
+ /// </summary>
+ /// <param name="semaphore"></param>
+ /// <param name="timeout">A the maximum amount of time in milliseconds to wait to enter the semaphore</param>
+ /// <returns>A releaser handle that may be disposed to release the semaphore</returns>
+ /// <exception cref="TimeoutException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static async Task<SemSlimReleaser> GetReleaserAsync(this SemaphoreSlim semaphore, int timeout)
+ {
+ if (await semaphore.WaitAsync(timeout))
+ {
+ return new SemSlimReleaser(semaphore);
+ }
+ throw new TimeoutException("Failed to enter the semaphore before the specified timeout period");
+ }
+
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>
+ /// </summary>
+ /// <param name="semaphore"></param>
+ /// <returns>A releaser handler that releases the semaphore when disposed</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static SemSlimReleaser GetReleaser(this SemaphoreSlim semaphore)
+ {
+ semaphore.Wait();
+ return new SemSlimReleaser(semaphore);
+ }
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>
+ /// </summary>
+ /// <param name="semaphore"></param>
+ /// <param name="timeout">A the maximum amount of time in milliseconds to wait to enter the semaphore</param>
+ /// <returns>A releaser handler that releases the semaphore when disposed</returns>
+ /// <exception cref="TimeoutException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static SemSlimReleaser GetReleaser(this SemaphoreSlim semaphore, int timeout)
+ {
+ if (semaphore.Wait(timeout))
+ {
+ return new SemSlimReleaser(semaphore);
+ }
+ throw new TimeoutException("Failed to enter the semaphore before the specified timeout period");
+ }
+
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="Mutex"/>
+ /// </summary>
+ /// <param name="mutex"></param>
+ /// <returns>A releaser handler that releases the semaphore when disposed</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="AbandonedMutexException"></exception>
+ public static MutexReleaser Enter(this Mutex mutex)
+ {
+ mutex.WaitOne();
+ return new MutexReleaser(mutex);
+ }
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>
+ /// </summary>
+ /// <param name="mutex"></param>
+ /// <param name="timeout">A the maximum amount of time in milliseconds to wait to enter the semaphore</param>
+ /// <returns>A releaser handler that releases the semaphore when disposed</returns>
+ /// <exception cref="TimeoutException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static MutexReleaser Enter(this Mutex mutex, int timeout)
+ {
+ if (mutex.WaitOne(timeout))
+ {
+ return new MutexReleaser(mutex);
+ }
+ throw new TimeoutException("Failed to enter the semaphore before the specified timeout period");
+ }
+
+ private static readonly Task<bool> TrueCompleted = Task.FromResult(true);
+ private static readonly Task<bool> FalseCompleted = Task.FromResult(false);
+
+ /// <summary>
+ /// Asynchronously waits for a the <see cref="WaitHandle"/> to receive a signal. This method spins until
+ /// a thread yield will occur, then asynchronously yields.
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="timeoutMs">The timeout interval in milliseconds</param>
+ /// <returns>
+ /// A task that compeletes when the wait handle receives a signal or times-out,
+ /// the result of the awaited task will be <c>true</c> if the signal is received, or
+ /// <c>false</c> if the timeout interval expires
+ /// </returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static Task<bool> WaitAsync(this WaitHandle handle, int timeoutMs = Timeout.Infinite)
+ {
+ _ = handle ?? throw new ArgumentNullException(nameof(handle));
+ //test non-blocking handle state
+ if (handle.WaitOne(0))
+ {
+ return TrueCompleted;
+ }
+ //When timeout is 0, wh will block, return false
+ else if(timeoutMs == 0)
+ {
+ return FalseCompleted;
+ }
+ //Init short lived spinwait
+ SpinWait sw = new();
+ //Spin until yield occurs
+ while (!sw.NextSpinWillYield)
+ {
+ sw.SpinOnce();
+ //Check handle state
+ if (handle.WaitOne(0))
+ {
+ return TrueCompleted;
+ }
+ }
+ //Completion source used to signal the awaiter when the wait handle is signaled
+ TaskCompletionSource<bool> completion = new(TaskCreationOptions.None);
+ //Register wait on threadpool to complete the task source
+ RegisteredWaitHandle registration = ThreadPool.RegisterWaitForSingleObject(handle, TaskCompletionCallback, completion, timeoutMs, true);
+ //Register continuation to cleanup
+ _ = completion.Task.ContinueWith(CleanupContinuation, registration, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
+ .ConfigureAwait(false);
+ return completion.Task;
+ }
+
+ private static void CleanupContinuation(Task<bool> task, object? taskCompletion)
+ {
+ RegisteredWaitHandle registration = (taskCompletion as RegisteredWaitHandle)!;
+ registration.Unregister(null);
+ task.Dispose();
+ }
+ private static void TaskCompletionCallback(object? tcsState, bool timedOut)
+ {
+ TaskCompletionSource<bool> completion = (tcsState as TaskCompletionSource<bool>)!;
+ //Set the result of the wait handle timeout
+ _ = completion.TrySetResult(!timedOut);
+ }
+
+
+ /// <summary>
+ /// Registers a callback method that will be called when the token has been cancelled.
+ /// This method waits indefinitely for the token to be cancelled.
+ /// </summary>
+ /// <param name="token"></param>
+ /// <param name="callback">The callback method to invoke when the token has been cancelled</param>
+ /// <returns>A task that may be unobserved, that completes when the token has been cancelled</returns>
+ public static Task RegisterUnobserved(this CancellationToken token, Action callback)
+ {
+ //Call callback when the wait handle is set
+ return token.WaitHandle.WaitAsync()
+ .ContinueWith(static (t, callback) => (callback as Action)!.Invoke(),
+ callback,
+ CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously,
+ TaskScheduler.Default
+ );
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/TimerExtensions.cs b/Utils/src/Extensions/TimerExtensions.cs
new file mode 100644
index 0000000..a980d63
--- /dev/null
+++ b/Utils/src/Extensions/TimerExtensions.cs
@@ -0,0 +1,66 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: TimerExtensions.cs
+*
+* TimerExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Extensions
+{
+ public static class TimerExtensions
+ {
+ /// <summary>
+ /// Attempts to stop the timer
+ /// </summary>
+ /// <returns>True if the timer was successfully modified, false otherwise</returns>
+ public static bool Stop(this Timer timer) => timer.Change(Timeout.Infinite, Timeout.Infinite);
+
+ /// <summary>
+ /// Attempts to stop an active timer and prepare a <see cref="OpenHandle"/> configured to restore the state of the timer to the specified timespan
+ /// </summary>
+ /// <param name="timer"></param>
+ /// <param name="resumeTime"><see cref="TimeSpan"/> representing the amount of time the timer should wait before invoking the callback function</param>
+ /// <returns>A new <see cref="OpenHandle"/> if the timer was stopped successfully that will resume the timer when closed, null otherwise</returns>
+ public static OpenHandle Stop(this Timer timer, TimeSpan resumeTime)
+ {
+ return timer.Change(Timeout.Infinite, Timeout.Infinite) ? new CallbackOpenHandle(() => timer.Change(resumeTime, Timeout.InfiniteTimeSpan)) : null;
+ }
+
+ /// <summary>
+ /// Attempts to reset and start a timer
+ /// </summary>
+ /// <param name="timer"></param>
+ /// <param name="wait"><see cref="TimeSpan"/> to wait before the timer event is fired</param>
+ /// <returns>True if the timer was successfully modified</returns>
+ public static bool Restart(this Timer timer, TimeSpan wait) => timer.Change(wait, Timeout.InfiniteTimeSpan);
+
+ /// <summary>
+ /// Attempts to reset and start a timer
+ /// </summary>
+ /// <param name="timer"></param>
+ /// <param name="waitMilliseconds">Time in milliseconds to wait before the timer event is fired</param>
+ /// <returns>True if the timer was successfully modified</returns>
+ public static bool Restart(this Timer timer, int waitMilliseconds) => timer.Change(waitMilliseconds, Timeout.Infinite);
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Extensions/VnStringExtensions.cs b/Utils/src/Extensions/VnStringExtensions.cs
new file mode 100644
index 0000000..285fc4f
--- /dev/null
+++ b/Utils/src/Extensions/VnStringExtensions.cs
@@ -0,0 +1,418 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnStringExtensions.cs
+*
+* VnStringExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.Extensions
+{
+ [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "<Pending>")]
+ public static class VnStringExtensions
+ {
+ /// <summary>
+ /// Derermines if the character exists within the instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="value">The value to find</param>
+ /// <returns>True if the character exists within the instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static bool Contains(this VnString str, char value) => str.AsSpan().Contains(value);
+ /// <summary>
+ /// Derermines if the sequence exists within the instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="value">The sequence to find</param>
+ /// <param name="stringComparison"></param>
+ /// <returns>True if the character exists within the instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+
+ public static bool Contains(this VnString str, ReadOnlySpan<char> value, StringComparison stringComparison) => str.AsSpan().Contains(value, stringComparison);
+
+ /// <summary>
+ /// Searches for the first occurrance of the specified character within the current instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="value">The character to search for within the instance</param>
+ /// <returns>The 0 based index of the occurance, -1 if the character was not found</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, char value) => str.IsEmpty ? -1 : str.AsSpan().IndexOf(value);
+ /// <summary>
+ /// Searches for the first occurrance of the specified sequence within the current instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <returns>The 0 based index of the occurance, -1 if the sequence was not found</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, ReadOnlySpan<char> search)
+ {
+ //Using spans to avoid memory leaks...
+ ReadOnlySpan<char> self = str.AsSpan();
+ return self.IndexOf(search);
+ }
+ /// <summary>
+ /// Searches for the first occurrance of the specified sequence within the current instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <param name="comparison">The <see cref="StringComparison"/> type to use in searchr</param>
+ /// <returns>The 0 based index of the occurance, -1 if the sequence was not found</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, ReadOnlySpan<char> search, StringComparison comparison)
+ {
+ //Using spans to avoid memory leaks...
+ ReadOnlySpan<char> self = str.AsSpan();
+ return self.IndexOf(search, comparison);
+ }
+ /// <summary>
+ /// Searches for the 0 based index of the first occurance of the search parameter after the start index.
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence of data to search for</param>
+ /// <param name="start">The lower boundry of the search area</param>
+ /// <returns>The absolute index of the first occurrance within the instance, -1 if the sequency was not found in the windowed segment</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, ReadOnlySpan<char> search, int start)
+ {
+ if (start < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(start), "Start cannot be less than 0");
+ }
+ //Get shifted window
+ ReadOnlySpan<char> self = str.AsSpan()[start..];
+ //Check indexof
+ int index = self.IndexOf(search);
+ return index > -1 ? index + start : -1;
+ }
+
+ /// <summary>
+ /// Returns the realtive index after the specified sequence within the <see cref="VnString"/> instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <returns>The index after the found sequence within the string, -1 if the sequence was not found within the instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int EndOf(this VnString str, ReadOnlySpan<char> search)
+ {
+ //Try to get the index of the data
+ int index = IndexOf(str, search);
+ //If the data was found, add the length to get the end of the string
+ return index > -1 ? index + search.Length : -1;
+ }
+
+ /// <summary>
+ /// Allows for trimming whitespace characters in a realtive sequence from
+ /// within a <see cref="VnString"/> buffer defined by the start and end parameters
+ /// and returning the trimmed entry.
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="start">The starting position within the sequence to trim</param>
+ /// <param name="end">The end of the sequence to trim</param>
+ /// <returns>The trimmed <see cref="VnString"/> instance as a child of the original entry</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ public static VnString AbsoluteTrim(this VnString data, int start, int end)
+ {
+ AbsoluteTrim(data, ref start, ref end);
+ return data[start..end];
+ }
+ /// <summary>
+ /// Finds whitespace characters within the sequence defined between start and end parameters
+ /// and adjusts the specified window to "trim" whitespace
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="start">The starting position within the sequence to trim</param>
+ /// <param name="end">The end of the sequence to trim</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ public static void AbsoluteTrim(this VnString data, ref int start, ref int end)
+ {
+ ReadOnlySpan<char> trimmed = data.AsSpan();
+ //trim leading whitespace
+ while (start < end)
+ {
+ //If whitespace character shift start up
+ if (trimmed[start] != ' ')
+ {
+ break;
+ }
+ //Shift
+ start++;
+ }
+ //remove trailing whitespace characters
+ while (end > start)
+ {
+ //If whiterspace character shift end param down
+ if (trimmed[end - 1] != ' ')
+ {
+ break;
+ }
+ end--;
+ }
+ }
+ /// <summary>
+ /// Allows for trimming whitespace characters in a realtive sequence from
+ /// within a <see cref="VnString"/> buffer and returning the trimmed entry.
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="start">The starting position within the sequence to trim</param>
+ /// <returns>The trimmed <see cref="VnString"/> instance as a child of the original entry</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ public static VnString AbsoluteTrim(this VnString data, int start) => AbsoluteTrim(data, start, data.Length);
+ /// <summary>
+ /// Trims leading or trailing whitespace characters and returns a new child instance
+ /// without leading or trailing whitespace
+ /// </summary>
+ /// <returns>A child <see cref="VnString"/> of the current instance without leading or trailing whitespaced</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static VnString RelativeTirm(this VnString data) => AbsoluteTrim(data, 0);
+
+ /// <summary>
+ /// Allows for enumeration of segments of data within the specified <see cref="VnString"/> instance that are
+ /// split by the search parameter
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">The sequence of data to delimit segments</param>
+ /// <param name="options">The options used to split the string instances</param>
+ /// <returns>An iterator to enumerate the split segments</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IEnumerable<VnString> Split(this VnString data, ReadOnlyMemory<char> search, StringSplitOptions options = StringSplitOptions.None)
+ {
+ int lowerBound = 0;
+ //Make sure the length of the search param is not 0
+ if(search.IsEmpty)
+ {
+ //Return the entire string
+ yield return data;
+ }
+ //No string options
+ else if (options == 0)
+ {
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ yield return data[lowerBound..splitIndex];
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Return remaining data
+ yield return data[lowerBound..];
+ }
+ //Trim but do not remove empties
+ else if ((options & StringSplitOptions.RemoveEmptyEntries) == 0)
+ {
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ //trim and return
+ yield return data.AbsoluteTrim(lowerBound, splitIndex);
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Return remaining data
+ yield return data.AbsoluteTrim(lowerBound);
+ }
+ //Remove empty entires but do not trim them
+ else if ((options & StringSplitOptions.TrimEntries) == 0)
+ {
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ //If the split index is the next sequence, then the result is empty, so exclude it
+ else if(splitIndex > 0)
+ {
+ yield return data[lowerBound..splitIndex];
+ }
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Return remaining data if available
+ if (lowerBound < data.Length)
+ {
+ yield return data[lowerBound..];
+ }
+ }
+ //Must mean remove and trim
+ else
+ {
+ //Get stack varables to pass to trim function
+ int trimStart, trimEnd;
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ //Get stack varables to pass to trim function
+ trimStart = lowerBound;
+ trimEnd = splitIndex; //End of the segment is the relative split index + the lower bound of the window
+ //Trim whitespace chars
+ data.AbsoluteTrim(ref trimStart, ref trimEnd);
+ //See if the string has data
+ if((trimEnd - trimStart) > 0)
+ {
+ yield return data[trimStart..trimEnd];
+ }
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Trim remaining
+ trimStart = lowerBound;
+ trimEnd = data.Length;
+ data.AbsoluteTrim(ref trimStart, ref trimEnd);
+ //If the remaining string is not empty return it
+ if ((trimEnd - trimStart) > 0)
+ {
+ yield return data[trimStart..trimEnd];
+ }
+ }
+ }
+
+ /// <summary>
+ /// Trims any leading or trailing <c>'\r'|'\n'|' '</c>(whitespace) characters from the segment
+ /// </summary>
+ /// <returns>The trimmed segment</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static VnString TrimCRLF(this VnString data)
+ {
+ ReadOnlySpan<char> trimmed = data.AsSpan();
+ int start = 0, end = trimmed.Length;
+ //trim leading \r\n chars
+ while (start < end)
+ {
+ char t = trimmed[start];
+ //If character \r or \n slice it off
+ if (t != '\r' && t != '\n' && t != ' ') {
+ break;
+ }
+ //Shift
+ start++;
+ }
+ //remove trailing crlf characters
+ while (end > start)
+ {
+ char t = trimmed[end - 1];
+ //If character \r or \n slice it off
+ if (t != '\r' && t != '\n' && t != ' ') {
+ break;
+ }
+ end--;
+ }
+ return data[start..end];
+ }
+
+ /// <summary>
+ /// Unoptimized character enumerator. You should use <see cref="VnString.AsSpan"/> to enumerate the unerlying data.
+ /// </summary>
+ /// <returns>The next character in the sequence</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IEnumerator<char> GetEnumerator(this VnString data)
+ {
+ int index = 0;
+ while (index < data.Length)
+ {
+ yield return data[index++];
+ }
+ }
+ /// <summary>
+ /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper
+ /// for a memory handle
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="length">The number of characters from the handle to reference (length of the string)</param>
+ /// <returns>The new <see cref="VnString"/> wrapper</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ToVnString(this MemoryHandle<char> handle, int length)
+ {
+ if(handle.Length > int.MaxValue)
+ {
+ throw new OverflowException("The handle is larger than 2GB in size");
+ }
+ return VnString.ConsumeHandle(handle, 0, length);
+ }
+ /// <summary>
+ /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper
+ /// for a memory handle
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <returns>The new <see cref="VnString"/> wrapper</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ToVnString(this MemoryHandle<char> handle)
+ {
+ return VnString.ConsumeHandle(handle, 0, handle.IntLength);
+ }
+ /// <summary>
+ /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper
+ /// for a memory handle
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="offset">The offset in characters that represents the begining of the string</param>
+ /// <param name="length">The number of characters from the handle to reference (length of the string)</param>
+ /// <returns>The new <see cref="VnString"/> wrapper</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ToVnString(this MemoryHandle<char> handle,
+#if TARGET_64_BIT
+ ulong offset,
+#else
+ int offset,
+#endif
+ int length)
+ {
+ if (handle.Length > int.MaxValue)
+ {
+ throw new OverflowException("The handle is larger than 2GB in size");
+ }
+ return VnString.ConsumeHandle(handle, offset, length);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IIndexable.cs b/Utils/src/IIndexable.cs
new file mode 100644
index 0000000..129d703
--- /dev/null
+++ b/Utils/src/IIndexable.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IIndexable.cs
+*
+* IIndexable.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Provides an interface that provides an indexer
+ /// </summary>
+ /// <typeparam name="TKey">The lookup Key</typeparam>
+ /// <typeparam name="TValue">The lookup value</typeparam>
+ public interface IIndexable<TKey, TValue>
+ {
+ /// <summary>
+ /// Gets or sets the value at the specified index in the collection
+ /// </summary>
+ /// <param name="key">The key to lookup the value at</param>
+ /// <returns>The value at the specified key</returns>
+ TValue this[TKey key] { get; set;}
+ }
+}
diff --git a/Utils/src/IO/ArrayPoolStreamBuffer.cs b/Utils/src/IO/ArrayPoolStreamBuffer.cs
new file mode 100644
index 0000000..df366e3
--- /dev/null
+++ b/Utils/src/IO/ArrayPoolStreamBuffer.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ArrayPoolStreamBuffer.cs
+*
+* ArrayPoolStreamBuffer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+
+namespace VNLib.Utils.IO
+{
+ internal class ArrayPoolStreamBuffer<T> : ISlindingWindowBuffer<T>
+ {
+ private readonly ArrayPool<T> _pool;
+ private T[] _buffer;
+
+ public ArrayPoolStreamBuffer(ArrayPool<T> pool, int bufferSize)
+ {
+ _pool = pool;
+ _buffer = _pool.Rent(bufferSize);
+ }
+
+ public int WindowStartPos { get; set; }
+ public int WindowEndPos { get; set; }
+
+ public Memory<T> Buffer => _buffer.AsMemory();
+
+ public void Advance(int count)
+ {
+ WindowEndPos += count;
+ }
+
+ public void AdvanceStart(int count)
+ {
+ WindowStartPos += count;
+ }
+
+ public void Close()
+ {
+ //Return buffer to pool
+ _pool.Return(_buffer);
+ _buffer = null;
+ }
+
+ public void Reset()
+ {
+ //Reset window positions
+ WindowStartPos = 0;
+ WindowEndPos = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/BackingStream.cs b/Utils/src/IO/BackingStream.cs
new file mode 100644
index 0000000..cb56b09
--- /dev/null
+++ b/Utils/src/IO/BackingStream.cs
@@ -0,0 +1,181 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: BackingStream.cs
+*
+* BackingStream.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Provides basic stream support sync/async stream operations to a
+ /// backing stream with virtual event methods. Provides a pass-through
+ /// as best as possbile.
+ /// </summary>
+ public abstract class BackingStream<T> : Stream where T: Stream
+ {
+ /// <summary>
+ /// The backing/underlying stream operations are being performed on
+ /// </summary>
+ protected T BaseStream { get; set; }
+ /// <summary>
+ /// A value that will cause all calls to write to throw <see cref="NotSupportedException"/>
+ /// </summary>
+ protected bool ForceReadOnly { get; set; }
+ ///<inheritdoc/>
+ public override bool CanRead => BaseStream.CanRead;
+ ///<inheritdoc/>
+ public override bool CanSeek => BaseStream.CanSeek;
+ ///<inheritdoc/>
+ public override bool CanWrite => BaseStream.CanWrite && !ForceReadOnly;
+ ///<inheritdoc/>
+ public override long Length => BaseStream.Length;
+ ///<inheritdoc/>
+ public override int WriteTimeout { get => BaseStream.WriteTimeout; set => BaseStream.WriteTimeout = value; }
+ ///<inheritdoc/>
+ public override int ReadTimeout { get => BaseStream.ReadTimeout; set => BaseStream.ReadTimeout = value; }
+ ///<inheritdoc/>
+ public override long Position { get => BaseStream.Position; set => BaseStream.Position = value; }
+ ///<inheritdoc/>
+ public override void Flush()
+ {
+ BaseStream.Flush();
+ OnFlush();
+ }
+ ///<inheritdoc/>
+ public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count);
+ ///<inheritdoc/>
+ public override int Read(Span<byte> buffer) => BaseStream.Read(buffer);
+ ///<inheritdoc/>
+ public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin);
+ ///<inheritdoc/>
+ public override void SetLength(long value) => BaseStream.SetLength(value);
+ ///<inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ BaseStream.Write(buffer, offset, count);
+ //Call onwrite function
+ OnWrite(count);
+ }
+ ///<inheritdoc/>
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ BaseStream.Write(buffer);
+ //Call onwrite function
+ OnWrite(buffer.Length);
+ }
+ ///<inheritdoc/>
+ public override void Close()
+ {
+ BaseStream.Close();
+ //Call on close function
+ OnClose();
+ }
+
+ /// <summary>
+ /// Raised directly after the base stream is closed, when a call to close is made
+ /// </summary>
+ protected virtual void OnClose() { }
+ /// <summary>
+ /// Raised directly after the base stream is flushed, when a call to flush is made
+ /// </summary>
+ protected virtual void OnFlush() { }
+ /// <summary>
+ /// Raised directly after a successfull write operation.
+ /// </summary>
+ /// <param name="count">The number of bytes written to the stream</param>
+ protected virtual void OnWrite(int count) { }
+
+ ///<inheritdoc/>
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return BaseStream.ReadAsync(buffer, offset, count, cancellationToken);
+ }
+ ///<inheritdoc/>
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return BaseStream.ReadAsync(buffer, cancellationToken);
+ }
+ ///<inheritdoc/>
+ public override void CopyTo(Stream destination, int bufferSize) => BaseStream.CopyTo(destination, bufferSize);
+ ///<inheritdoc/>
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ return BaseStream.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
+ ///<inheritdoc/>
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ //We want to maintain pass through as much as possible, so supress warning
+#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
+ await BaseStream.WriteAsync(buffer, offset, count, cancellationToken);
+#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
+
+ //Call on-write and pass the number of bytes written
+ OnWrite(count);
+ }
+ ///<inheritdoc/>
+ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ if (ForceReadOnly)
+ {
+ throw new NotSupportedException("Stream is set to readonly mode");
+ }
+ await BaseStream.WriteAsync(buffer, cancellationToken);
+ //Call on-write and pass the length
+ OnWrite(buffer.Length);
+ }
+ ///<inheritdoc/>
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ await BaseStream.FlushAsync(cancellationToken);
+ //Call onflush
+ OnFlush();
+ }
+
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ //Dispose the base stream and await it
+ await BaseStream.DisposeAsync();
+ //Call onclose
+ OnClose();
+ //Suppress finalize
+ GC.SuppressFinalize(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/FileOperations.cs b/Utils/src/IO/FileOperations.cs
new file mode 100644
index 0000000..e040da4
--- /dev/null
+++ b/Utils/src/IO/FileOperations.cs
@@ -0,0 +1,105 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: FileOperations.cs
+*
+* FileOperations.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Contains cross-platform optimized filesystem operations.
+ /// </summary>
+ public static class FileOperations
+ {
+ public const int INVALID_FILE_ATTRIBUTES = -1;
+
+ [DllImport("Shlwapi", SetLastError = true, CharSet = CharSet.Auto)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [return:MarshalAs(UnmanagedType.Bool)]
+ private static unsafe extern bool PathFileExists(char* path);
+ [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [return:MarshalAs(UnmanagedType.I4)]
+ private static unsafe extern int GetFileAttributes(char* path);
+
+ static readonly bool IsWindows = OperatingSystem.IsWindows();
+ /// <summary>
+ /// Determines if a file exists. If application is current running in the Windows operating system, Shlwapi.PathFileExists is invoked,
+ /// otherwise <see cref="File.Exists(string?)"/> is invoked
+ /// </summary>
+ /// <param name="filePath">the path to the file</param>
+ /// <returns>True if the file can be opened, false otherwise</returns>
+ public static bool FileExists(string filePath)
+ {
+ //If windows is detected, use the unmanged function
+ if (!IsWindows)
+ {
+ return File.Exists(filePath);
+ }
+ unsafe
+ {
+ //Get a char pointer to the file path
+ fixed (char* path = filePath)
+ {
+ //Invoke the winap file function
+ return PathFileExists(path);
+ }
+ }
+ }
+
+ /// <summary>
+ /// If Windows is detected at load time, gets the attributes for the specified file.
+ /// </summary>
+ /// <param name="filePath">The path to the existing file</param>
+ /// <returns>The attributes of the file </returns>
+ /// <exception cref="PathTooLongException"></exception>
+ /// <exception cref="FileNotFoundException"></exception>
+ /// <exception cref="UnauthorizedAccessException"></exception>
+ public static FileAttributes GetAttributes(string filePath)
+ {
+ //If windows is detected, use the unmanged function
+ if (!IsWindows)
+ {
+ return File.GetAttributes(filePath);
+ }
+ unsafe
+ {
+ //Get a char pointer to the file path
+ fixed (char* path = filePath)
+ {
+ //Invoke the winap file function and cast the returned int value to file attributes
+ int attr = GetFileAttributes(path);
+ //Check for error
+ if (attr == INVALID_FILE_ATTRIBUTES)
+ {
+ throw new FileNotFoundException("The requested file was not found", filePath);
+ }
+ //Cast to file attributes and return
+ return (FileAttributes)attr;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/IDataAccumulator.cs b/Utils/src/IO/IDataAccumulator.cs
new file mode 100644
index 0000000..5129a55
--- /dev/null
+++ b/Utils/src/IO/IDataAccumulator.cs
@@ -0,0 +1,64 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IDataAccumulator.cs
+*
+* IDataAccumulator.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// A data structure that represents a sliding window over a buffer
+ /// for resetable forward-only reading or writing
+ /// </summary>
+ /// <typeparam name="T">The accumuation data type</typeparam>
+ public interface IDataAccumulator<T>
+ {
+ /// <summary>
+ /// Gets the number of available items within the buffer
+ /// </summary>
+ int AccumulatedSize { get; }
+ /// <summary>
+ /// The number of elements remaining in the buffer
+ /// </summary>
+ int RemainingSize { get; }
+ /// <summary>
+ /// The remaining space in the internal buffer as a contiguous segment
+ /// </summary>
+ Span<T> Remaining { get; }
+ /// <summary>
+ /// The buffer window over the accumulated data
+ /// </summary>
+ Span<T> Accumulated { get; }
+
+ /// <summary>
+ /// Advances the accumulator buffer window by the specified amount
+ /// </summary>
+ /// <param name="count">The number of elements accumulated</param>
+ void Advance(int count);
+
+ /// <summary>
+ /// Resets the internal state of the accumulator
+ /// </summary>
+ void Reset();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/ISlindingWindowBuffer.cs b/Utils/src/IO/ISlindingWindowBuffer.cs
new file mode 100644
index 0000000..ff4e142
--- /dev/null
+++ b/Utils/src/IO/ISlindingWindowBuffer.cs
@@ -0,0 +1,91 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ISlindingWindowBuffer.cs
+*
+* ISlindingWindowBuffer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents a sliding window buffer for reading/wiriting data
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public interface ISlindingWindowBuffer<T> : IDataAccumulator<T>
+ {
+ /// <summary>
+ /// The number of elements remaining in the buffer
+ /// </summary>
+ int IDataAccumulator<T>.RemainingSize => Buffer.Length - WindowEndPos;
+ /// <summary>
+ /// The remaining space in the internal buffer as a contiguous segment
+ /// </summary>
+ Span<T> IDataAccumulator<T>.Remaining => RemainingBuffer.Span;
+ /// <summary>
+ /// The buffer window over the accumulated data
+ /// </summary>
+ Span<T> IDataAccumulator<T>.Accumulated => AccumulatedBuffer.Span;
+ /// <summary>
+ /// Gets the number of available items within the buffer
+ /// </summary>
+ int IDataAccumulator<T>.AccumulatedSize => WindowEndPos - WindowStartPos;
+
+ /// <summary>
+ /// The starting positon of the available data within the buffer
+ /// </summary>
+ int WindowStartPos { get; }
+ /// <summary>
+ /// The ending position of the available data within the buffer
+ /// </summary>
+ int WindowEndPos { get; }
+ /// <summary>
+ /// Buffer memory wrapper
+ /// </summary>
+ Memory<T> Buffer { get; }
+
+ /// <summary>
+ /// Releases resources used by the current instance
+ /// </summary>
+ void Close();
+ /// <summary>
+ /// <para>
+ /// Advances the begining of the accumulated data window.
+ /// </para>
+ /// <para>
+ /// This method is used during reading to singal that data
+ /// has been read from the internal buffer and the
+ /// accumulator window can be shifted.
+ /// </para>
+ /// </summary>
+ /// <param name="count">The number of elements to shift by</param>
+ void AdvanceStart(int count);
+
+ /// <summary>
+ /// Gets a window within the buffer of available buffered data
+ /// </summary>
+ Memory<T> AccumulatedBuffer => Buffer[WindowStartPos..WindowEndPos];
+ /// <summary>
+ /// Gets the available buffer window to write data to
+ /// </summary>
+ Memory<T> RemainingBuffer => Buffer[WindowEndPos..];
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/IVnTextReader.cs b/Utils/src/IO/IVnTextReader.cs
new file mode 100644
index 0000000..625ba78
--- /dev/null
+++ b/Utils/src/IO/IVnTextReader.cs
@@ -0,0 +1,72 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IVnTextReader.cs
+*
+* IVnTextReader.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents a streaming text reader with internal buffers
+ /// </summary>
+ public interface IVnTextReader
+ {
+ /// <summary>
+ /// The base stream to read data from
+ /// </summary>
+ Stream BaseStream { get; }
+ /// <summary>
+ /// The character encoding used by the TextReader
+ /// </summary>
+ Encoding Encoding { get; }
+ /// <summary>
+ /// Number of available bytes of buffered data within the current buffer window
+ /// </summary>
+ int Available { get; }
+ /// <summary>
+ /// Gets or sets the line termination used to deliminate a line of data
+ /// </summary>
+ ReadOnlyMemory<byte> LineTermination { get; }
+ /// <summary>
+ /// The unread/available data within the internal buffer
+ /// </summary>
+ Span<byte> BufferedDataWindow { get; }
+ /// <summary>
+ /// Shifts the sliding buffer window by the specified number of bytes.
+ /// </summary>
+ /// <param name="count">The number of bytes read from the buffer</param>
+ void Advance(int count);
+ /// <summary>
+ /// Reads data from the stream into the remaining buffer space for processing
+ /// </summary>
+ void FillBuffer();
+ /// <summary>
+ /// Compacts the available buffer space back to the begining of the buffer region
+ /// and determines if there is room for more data to be buffered
+ /// </summary>
+ /// <returns>The remaining buffer space if any</returns>
+ ERRNO CompactBufferWindow();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/InMemoryTemplate.cs b/Utils/src/IO/InMemoryTemplate.cs
new file mode 100644
index 0000000..12f9092
--- /dev/null
+++ b/Utils/src/IO/InMemoryTemplate.cs
@@ -0,0 +1,196 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: InMemoryTemplate.cs
+*
+* InMemoryTemplate.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents a lazily loaded file stored in memory, with a change mointor
+ /// that reloads the template if the file was modified in the filesystem
+ /// </summary>
+ public abstract class InMemoryTemplate : VnDisposeable
+ {
+ protected ManualResetEventSlim TemplateLock;
+ private readonly FileSystemWatcher? Watcher;
+ private bool Modified;
+ private VnMemoryStream templateBuffer;
+ protected readonly FileInfo TemplateFile;
+
+ /// <summary>
+ /// Gets the name of the template
+ /// </summary>
+ public abstract string TemplateName { get; }
+
+ /// <summary>
+ /// Creates a new in-memory copy of a file that will detect changes and refresh
+ /// </summary>
+ /// <param name="listenForChanges">Should changes to the template file be moniored for changes, and reloaded as necessary</param>
+ /// <param name="path">The path of the file template</param>
+ protected InMemoryTemplate(string path, bool listenForChanges = true)
+ {
+ TemplateFile = new FileInfo(path);
+ TemplateLock = new(true);
+ //Make sure the file exists
+ if (!TemplateFile.Exists)
+ {
+ throw new FileNotFoundException("Template file does not exist");
+ }
+ if (listenForChanges)
+ {
+ //Setup a watcher to reload the template when modified
+ Watcher = new FileSystemWatcher(TemplateFile.DirectoryName)
+ {
+ EnableRaisingEvents = true,
+ IncludeSubdirectories = false,
+ NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size
+ };
+ Watcher.Changed += Watcher_Changed;
+ }
+ //Set modified flag to make sure the template is read on first use
+ this.Modified = true;
+ }
+
+ private void Watcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ //Make sure the event was raied for this template
+ if (!e.FullPath.Equals(TemplateFile.FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ TemplateLock.Reset();
+ try
+ {
+ //Set modified flag
+ Modified = true;
+ //Refresh the fileinfo object
+ TemplateFile.Refresh();
+ //Invoke onmodifed function
+ OnModifed();
+ }
+ finally
+ {
+ TemplateLock.Set();
+ }
+ }
+
+ /// <summary>
+ /// Gets a cached copy of the template data
+ /// </summary>
+ protected VnMemoryStream GetTemplateData()
+ {
+ //Make sure access is synchronized incase the file gets updated during access on another thread
+ TemplateLock.Wait();
+ //Determine if the file has been modified and needs to be reloaded
+ if (Modified)
+ {
+ TemplateLock.Reset();
+ try
+ {
+ //Read a new copy of the templte into mem
+ ReadFile();
+ }
+ finally
+ {
+ TemplateLock.Set();
+ }
+ }
+ //Return a copy of the memory stream
+ return templateBuffer.GetReadonlyShallowCopy();
+ }
+ /// <summary>
+ /// Updates the internal copy of the file to its memory representation
+ /// </summary>
+ protected void ReadFile()
+ {
+ //Open the file stream
+ using FileStream fs = TemplateFile.OpenRead();
+ //Dispose the old template buffer
+ templateBuffer?.Dispose();
+ //Create a new stream for storing the cached copy
+ VnMemoryStream newBuf = new();
+ try
+ {
+ fs.CopyTo(newBuf, null);
+ }
+ catch
+ {
+ newBuf.Dispose();
+ throw;
+ }
+ //Create the readonly copy
+ templateBuffer = VnMemoryStream.CreateReadonly(newBuf);
+ //Clear the modified flag
+ Modified = false;
+ }
+ /// <summary>
+ /// Updates the internal copy of the file to its memory representation, asynchronously
+ /// </summary>
+ /// <param name="cancellationToken"></param>
+ /// <returns>A task that completes when the file has been copied into memory</returns>
+ protected async Task ReadFileAsync(CancellationToken cancellationToken = default)
+ {
+ //Open the file stream
+ await using FileStream fs = TemplateFile.OpenRead();
+ //Dispose the old template buffer
+ templateBuffer?.Dispose();
+ //Create a new stream for storing the cached copy
+ VnMemoryStream newBuf = new();
+ try
+ {
+ //Copy async
+ await fs.CopyToAsync(newBuf, 8192, Memory.Memory.Shared, cancellationToken);
+ }
+ catch
+ {
+ newBuf.Dispose();
+ throw;
+ }
+ //Create the readonly copy
+ templateBuffer = VnMemoryStream.CreateReadonly(newBuf);
+ //Clear the modified flag
+ Modified = false;
+ }
+
+ /// <summary>
+ /// Invoked when the template file has been modifed. Note: This event is raised
+ /// while the <see cref="TemplateLock"/> is held.
+ /// </summary>
+ protected abstract void OnModifed();
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ //Dispose the watcher
+ Watcher?.Dispose();
+ //free the stream
+ templateBuffer?.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/IsolatedStorageDirectory.cs b/Utils/src/IO/IsolatedStorageDirectory.cs
new file mode 100644
index 0000000..65460ff
--- /dev/null
+++ b/Utils/src/IO/IsolatedStorageDirectory.cs
@@ -0,0 +1,154 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IsolatedStorageDirectory.cs
+*
+* IsolatedStorageDirectory.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.IO.IsolatedStorage;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Represents an open directory within an <see cref="IsolatedStorageFile"/> store for which files can be created, opened, or deleted.
+ /// </summary>
+ public sealed class IsolatedStorageDirectory : IsolatedStorage
+ {
+ private readonly string DirectoryPath;
+ private readonly IsolatedStorageFile Storage;
+ /// <summary>
+ /// Creates a new <see cref="IsolatedStorageDirectory"/> within the specified file using the directory name.
+ /// </summary>
+ /// <param name="storage">A configured and open <see cref="IsolatedStorageFile"/></param>
+ /// <param name="dir">The directory name to open or create within the store</param>
+ public IsolatedStorageDirectory(IsolatedStorageFile storage, string dir)
+ {
+ this.Storage = storage;
+ this.DirectoryPath = dir;
+ //If the directory doesnt exist, create it
+ if (!this.Storage.DirectoryExists(dir))
+ this.Storage.CreateDirectory(dir);
+ }
+
+ private IsolatedStorageDirectory(IsolatedStorageDirectory parent, string dirName)
+ {
+ //Store ref to parent dir
+ Parent = parent;
+ //Referrence store
+ this.Storage = parent.Storage;
+ //Add the name of this dir to the end of the specified dir path
+ this.DirectoryPath = Path.Combine(parent.DirectoryPath, dirName);
+ }
+
+ /// <summary>
+ /// Creates a file by its path name within the currnet directory
+ /// </summary>
+ /// <param name="fileName">The name of the file</param>
+ /// <returns>The open file</returns>
+ /// <exception cref="IsolatedStorageException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="DirectoryNotFoundException"></exception>
+ public IsolatedStorageFileStream CreateFile(string fileName)
+ {
+ return this.Storage.CreateFile(Path.Combine(DirectoryPath, fileName));
+ }
+ /// <summary>
+ /// Removes a file from the current directory
+ /// </summary>
+ /// <param name="fileName">The path of the file to remove</param>
+ /// <exception cref="IsolatedStorageException"></exception>
+ public void DeleteFile(string fileName)
+ {
+ this.Storage.DeleteFile(Path.Combine(this.DirectoryPath, fileName));
+ }
+ /// <summary>
+ /// Opens a file that exists within the current directory
+ /// </summary>
+ /// <param name="fileName">Name with extension of the file</param>
+ /// <param name="mode">File mode</param>
+ /// <param name="access">File access</param>
+ /// <returns>The open <see cref="IsolatedStorageFileStream"/> from the current directory</returns>
+ public IsolatedStorageFileStream OpenFile(string fileName, FileMode mode, FileAccess access)
+ {
+ return this.Storage.OpenFile(Path.Combine(DirectoryPath, fileName), mode, access);
+ }
+ /// <summary>
+ /// Opens a file that exists within the current directory
+ /// </summary>
+ /// <param name="fileName">Name with extension of the file</param>
+ /// <param name="mode">File mode</param>
+ /// <param name="access">File access</param>
+ /// <param name="share">The file shareing mode</param>
+ /// <returns>The open <see cref="IsolatedStorageFileStream"/> from the current directory</returns>
+ public IsolatedStorageFileStream OpenFile(string fileName, FileMode mode, FileAccess access, FileShare share)
+ {
+ return this.Storage.OpenFile(Path.Combine(DirectoryPath, fileName), mode, access, share);
+ }
+
+ /// <summary>
+ /// Determiens if the specified file path refers to an existing file within the directory
+ /// </summary>
+ /// <param name="fileName">The name of the file to search for</param>
+ /// <returns>True if the file exists within the current directory</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IsolatedStorageException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public bool FileExists(string fileName)
+ {
+ return this.Storage.FileExists(Path.Combine(this.DirectoryPath, fileName));
+ }
+
+ /// <summary>
+ /// Removes the directory and its contents from the store
+ /// </summary>
+ public override void Remove()
+ {
+ Storage.DeleteDirectory(this.DirectoryPath);
+ }
+
+ public override long AvailableFreeSpace => Storage.AvailableFreeSpace;
+ public override long Quota => Storage.Quota;
+ public override long UsedSize => Storage.UsedSize;
+ public override bool IncreaseQuotaTo(long newQuotaSize) => Storage.IncreaseQuotaTo(newQuotaSize);
+
+ /// <summary>
+ /// The parent <see cref="IsolatedStorageDirectory"/> this directory is a child within. null if there are no parent directories
+ /// above this dir
+ /// </summary>
+
+ public IsolatedStorageDirectory? Parent { get; }
+#nullable disable
+
+ /// <summary>
+ /// Creates a child directory within the current directory
+ /// </summary>
+ /// <param name="directoryName">The name of the child directory</param>
+ /// <returns>A new <see cref="IsolatedStorageDirectory"/> for which <see cref="IsolatedStorageFileStream"/>s can be opened/created</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public IsolatedStorageDirectory CreateChildDirectory(string directoryName)
+ {
+ return new IsolatedStorageDirectory(this, directoryName);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/SlidingWindowBufferExtensions.cs b/Utils/src/IO/SlidingWindowBufferExtensions.cs
new file mode 100644
index 0000000..0509061
--- /dev/null
+++ b/Utils/src/IO/SlidingWindowBufferExtensions.cs
@@ -0,0 +1,213 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SlidingWindowBufferExtensions.cs
+*
+* SlidingWindowBufferExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Extention methods for <see cref="ISlindingWindowBuffer{T}"/>
+ /// </summary>
+ public static class SlidingWindowBufferExtensions
+ {
+ /// <summary>
+ /// Shifts/resets the current buffered data window down to the
+ /// begining of the buffer if the buffer window is shifted away
+ /// from the begining.
+ /// </summary>
+ /// <returns>The number of bytes of available space in the buffer</returns>
+ public static ERRNO CompactBufferWindow<T>(this ISlindingWindowBuffer<T> sBuf)
+ {
+ //Nothing to compact if the starting data pointer is at the beining of the window
+ if (sBuf.WindowStartPos > 0)
+ {
+ //Get span over engire buffer
+ Span<T> buffer = sBuf.Buffer.Span;
+ //Get data within window
+ Span<T> usedData = sBuf.Accumulated;
+ //Copy remaining to the begining of the buffer
+ usedData.CopyTo(buffer);
+
+ //Reset positions, then advance to the specified size
+ sBuf.Reset();
+ sBuf.Advance(usedData.Length);
+ }
+ //Return the number of bytes of available space
+ return sBuf.RemainingSize;
+ }
+
+ /// <summary>
+ /// Appends the specified data to the end of the buffer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sBuf"></param>
+ /// <param name="val">The value to append to the end of the buffer</param>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<T> sBuf, T val)
+ {
+ //Set the value at first position
+ sBuf.Remaining[0] = val;
+ //Advance by 1
+ sBuf.Advance(1);
+ }
+ /// <summary>
+ /// Appends the specified data to the end of the buffer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sBuf"></param>
+ /// <param name="val">The value to append to the end of the buffer</param>
+ /// <exception cref="ArgumentException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<T> sBuf, ReadOnlySpan<T> val)
+ {
+ val.CopyTo(sBuf.Remaining);
+ sBuf.Advance(val.Length);
+ }
+ /// <summary>
+ /// Formats and appends a value type to the accumulator with proper endianess
+ /// </summary>
+ /// <typeparam name="T">The value type to appent</typeparam>
+ /// <param name="accumulator">The binary accumulator to append</param>
+ /// <param name="value">The value type to append</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<byte> accumulator, T value) where T: unmanaged
+ {
+ //Use forward reader for the memory extension to append a value type to a binary accumulator
+ ForwardOnlyWriter<byte> w = new(accumulator.Remaining);
+ w.Append(value);
+ accumulator.Advance(w.Written);
+ }
+
+ /// <summary>
+ /// Attempts to write as much data as possible to the remaining space
+ /// in the buffer and returns the number of bytes accumulated.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="accumulator"></param>
+ /// <param name="value">The value to accumulate</param>
+ /// <returns>The number of bytes accumulated</returns>
+ public static ERRNO TryAccumulate<T>(this IDataAccumulator<T> accumulator, ReadOnlySpan<T> value)
+ {
+ //Calc data size and reserve space for final crlf
+ int dataToCopy = Math.Min(value.Length, accumulator.RemainingSize);
+
+ //Write as much data as possible
+ accumulator.Append(value[..dataToCopy]);
+
+ //Return number of bytes not written
+ return dataToCopy;
+ }
+
+ /// <summary>
+ /// Appends a <see cref="ISpanFormattable"/> instance to the end of the accumulator
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="accumulator"></param>
+ /// <param name="formattable">The formattable instance to write to the accumulator</param>
+ /// <param name="format">The format arguments</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Append<T>(this IDataAccumulator<char> accumulator, in T formattable, ReadOnlySpan<char> format = default) where T : struct, ISpanFormattable
+ {
+ ForwardOnlyWriter<char> writer = new(accumulator.Remaining);
+ writer.Append(formattable, format);
+ accumulator.Advance(writer.Written);
+ }
+
+ /// <summary>
+ /// Uses the remaining data buffer to compile a <see cref="IStringSerializeable"/>
+ /// instance, then advances the accumulator by the number of characters used.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="accumulator"></param>
+ /// <param name="compileable">The <see cref="IStringSerializeable"/> instance to compile</param>
+ public static void Append<T>(this IDataAccumulator<char> accumulator, in T compileable) where T : IStringSerializeable
+ {
+ //Write directly to the remaining space
+ int written = compileable.Compile(accumulator.Remaining);
+ //Advance the writer
+ accumulator.Advance(written);
+ }
+
+ /// <summary>
+ /// Reads available data from the current window and writes as much as possible it to the supplied buffer
+ /// and advances the buffer window
+ /// </summary>
+ /// <typeparam name="T">Element type</typeparam>
+ /// <param name="sBuf"></param>
+ /// <param name="buffer">The output buffer to write data to</param>
+ /// <returns>The number of elements written to the buffer</returns>
+ public static ERRNO Read<T>(this ISlindingWindowBuffer<T> sBuf, in Span<T> buffer)
+ {
+ //Calculate the amount of data to copy
+ int dataToCopy = Math.Min(buffer.Length, sBuf.AccumulatedSize);
+ //Copy the data to the buffer
+ sBuf.Accumulated[..dataToCopy].CopyTo(buffer);
+ //Advance the window
+ sBuf.AdvanceStart(dataToCopy);
+ //Return the number of bytes copied
+ return dataToCopy;
+ }
+
+ /// <summary>
+ /// Fills the remaining window space of the current accumulator with
+ /// data from the specified stream asynchronously.
+ /// </summary>
+ /// <param name="accumulator"></param>
+ /// <param name="input">The stream to read data from</param>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A value task representing the operation</returns>
+ public static async ValueTask AccumulateDataAsync(this ISlindingWindowBuffer<byte> accumulator, Stream input, CancellationToken cancellationToken)
+ {
+ //Get a buffer from the end of the current window to the end of the buffer
+ Memory<byte> bufWindow = accumulator.RemainingBuffer;
+ //Read from stream async
+ int read = await input.ReadAsync(bufWindow, cancellationToken);
+ //Update the end of the buffer window to the end of the read data
+ accumulator.Advance(read);
+ }
+ /// <summary>
+ /// Fills the remaining window space of the current accumulator with
+ /// data from the specified stream.
+ /// </summary>
+ /// <param name="accumulator"></param>
+ /// <param name="input">The stream to read data from</param>
+ public static void AccumulateData(this IDataAccumulator<byte> accumulator, Stream input)
+ {
+ //Get a buffer from the end of the current window to the end of the buffer
+ Span<byte> bufWindow = accumulator.Remaining;
+ //Read from stream async
+ int read = input.Read(bufWindow);
+ //Update the end of the buffer window to the end of the read data
+ accumulator.Advance(read);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/TemporayIsolatedFile.cs b/Utils/src/IO/TemporayIsolatedFile.cs
new file mode 100644
index 0000000..3bee92b
--- /dev/null
+++ b/Utils/src/IO/TemporayIsolatedFile.cs
@@ -0,0 +1,57 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: TemporayIsolatedFile.cs
+*
+* TemporayIsolatedFile.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.IO.IsolatedStorage;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Allows for temporary files to be generated, used, then removed from an <see cref="IsolatedStorageFile"/>
+ /// </summary>
+ public sealed class TemporayIsolatedFile : BackingStream<IsolatedStorageFileStream>
+ {
+ private readonly IsolatedStorageDirectory Storage;
+ private readonly string Filename;
+ /// <summary>
+ /// Creates a new temporary filestream within the specified <see cref="IsolatedStorageFile"/>
+ /// </summary>
+ /// <param name="storage">The file store to genreate temporary files within</param>
+ public TemporayIsolatedFile(IsolatedStorageDirectory storage)
+ {
+ //Store ref
+ this.Storage = storage;
+ //Creaet a new random filename
+ this.Filename = Path.GetRandomFileName();
+ //try to created a new file within the isolaged storage
+ this.BaseStream = storage.CreateFile(this.Filename);
+ }
+ protected override void OnClose()
+ {
+ //Remove the file from the storage
+ Storage.DeleteFile(this.Filename);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/VnMemoryStream.cs b/Utils/src/IO/VnMemoryStream.cs
new file mode 100644
index 0000000..389a7da
--- /dev/null
+++ b/Utils/src/IO/VnMemoryStream.cs
@@ -0,0 +1,469 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnMemoryStream.cs
+*
+* VnMemoryStream.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+
+ using Utils.Memory;
+
+ /// <summary>
+ /// Provides an unmanaged memory stream. Desigend to help reduce garbage collector load for
+ /// high frequency memory operations. Similar to <see cref="UnmanagedMemoryStream"/>
+ /// </summary>
+ public sealed class VnMemoryStream : Stream, ICloneable
+ {
+ private long _position;
+ private long _length;
+ //Memory
+ private readonly MemoryHandle<byte> _buffer;
+ private bool IsReadonly;
+ //Default owns handle
+ private readonly bool OwnsHandle = true;
+
+ /// <summary>
+ /// Creates a new <see cref="VnMemoryStream"/> pointing to the begining of memory, and consumes the handle.
+ /// </summary>
+ /// <param name="handle"><see cref="MemoryHandle{T}"/> to consume</param>
+ /// <param name="length">Length of the stream</param>
+ /// <param name="readOnly">Should the stream be readonly?</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns>
+ public static VnMemoryStream ConsumeHandle(MemoryHandle<byte> handle, Int64 length, bool readOnly)
+ {
+ handle.ThrowIfClosed();
+ return new VnMemoryStream(handle, length, readOnly, true);
+ }
+
+ /// <summary>
+ /// Converts a writable <see cref="VnMemoryStream"/> to readonly to allow shallow copies
+ /// </summary>
+ /// <param name="stream">The stream to make readonly</param>
+ /// <returns>The readonly stream</returns>
+ public static VnMemoryStream CreateReadonly(VnMemoryStream stream)
+ {
+ //Set the readonly flag
+ stream.IsReadonly = true;
+ //Return the stream
+ return stream;
+ }
+
+ /// <summary>
+ /// Creates a new memory stream
+ /// </summary>
+ public VnMemoryStream() : this(Memory.Shared) { }
+ /// <summary>
+ /// Create a new memory stream where buffers will be allocated from the specified heap
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public VnMemoryStream(IUnmangedHeap heap) : this(heap, 0, false) { }
+
+ /// <summary>
+ /// Creates a new memory stream and pre-allocates the internal
+ /// buffer of the specified size on the specified heap to avoid resizing.
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param>
+ /// <param name="bufferSize">Number of bytes (length) of the stream if known</param>
+ /// <param name="zero">Zero memory allocations during buffer expansions</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public VnMemoryStream(IUnmangedHeap heap, long bufferSize, bool zero)
+ {
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ _buffer = heap.Alloc<byte>(bufferSize, zero);
+ }
+
+ /// <summary>
+ /// Creates a new memory stream from the data provided
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param>
+ /// <param name="data">Initial data</param>
+ public VnMemoryStream(IUnmangedHeap heap, ReadOnlySpan<byte> data)
+ {
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ //Alloc the internal buffer to match the data stream
+ _buffer = heap.AllocAndCopy(data);
+ //Set length
+ _length = data.Length;
+ //Position will default to 0 cuz its dotnet :P
+ return;
+ }
+
+ /// <summary>
+ /// WARNING: Dangerous constructor, make sure read-only and owns hanlde are set accordingly
+ /// </summary>
+ /// <param name="buffer">The buffer to referrence directly</param>
+ /// <param name="length">The length property of the stream</param>
+ /// <param name="readOnly">Is the stream readonly (should mostly be true!)</param>
+ /// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param>
+ private VnMemoryStream(MemoryHandle<byte> buffer, long length, bool readOnly, bool ownsHandle)
+ {
+ OwnsHandle = ownsHandle;
+ _buffer = buffer; //Consume the handle
+ _length = length; //Store length of the buffer
+ IsReadonly = readOnly;
+ }
+
+ /// <summary>
+ /// UNSAFE Number of bytes between position and length. Never negative
+ /// </summary>
+ private long LenToPosDiff => Math.Max(_length - _position, 0);
+
+ /// <summary>
+ /// If the current stream is a readonly stream, creates an unsafe shallow copy for reading only.
+ /// </summary>
+ /// <returns>New stream shallow copy of the internal stream</returns>
+ /// <exception cref="NotSupportedException"></exception>
+ public VnMemoryStream GetReadonlyShallowCopy()
+ {
+ //Create a new readonly copy (stream does not own the handle)
+ return !IsReadonly
+ ? throw new NotSupportedException("This stream is not readonly. Cannot create shallow copy on a mutable stream")
+ : new VnMemoryStream(_buffer, _length, true, false);
+ }
+
+ /// <summary>
+ /// Writes data directly to the destination stream from the internal buffer
+ /// without allocating or copying any data.
+ /// </summary>
+ /// <param name="destination">The stream to write data to</param>
+ /// <param name="bufferSize">The size of the chunks to write to the destination stream</param>
+ /// <exception cref="IOException"></exception>
+ public override void CopyTo(Stream destination, int bufferSize)
+ {
+ _ = destination ?? throw new ArgumentNullException(nameof(destination));
+
+ if (!destination.CanWrite)
+ {
+ throw new IOException("The destinaion stream is not writeable");
+ }
+
+ do
+ {
+ //Calc the remaining bytes to read no larger than the buffer size
+ int bytesToRead = (int)Math.Min(LenToPosDiff, bufferSize);
+
+ //Create a span wrapper by using the offet function to support memory handles larger than 2gb
+ ReadOnlySpan<byte> span = _buffer.GetOffsetSpan(_position, bytesToRead);
+
+ destination.Write(span);
+
+ //Update position
+ _position += bytesToRead;
+
+ } while (LenToPosDiff > 0);
+ }
+
+ /// <summary>
+ /// Allocates a temporary buffer of the desired size, copies data from the internal
+ /// buffer and writes it to the destination buffer asynchronously.
+ /// </summary>
+ /// <param name="destination">The stream to write output data to</param>
+ /// <param name="bufferSize">The size of the buffer to use when copying data</param>
+ /// <param name="cancellationToken">A token to cancel the opreation</param>
+ /// <returns>A task that resolves when the remaining data in the stream has been written to the destination</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ _ = destination ?? throw new ArgumentNullException(nameof(destination));
+
+ if (!destination.CanWrite)
+ {
+ throw new IOException("The destinaion stream is not writeable");
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ /*
+ * Alloc temp copy buffer. This is a requirement because
+ * the stream may be larger than an int32 so it must be
+ * copied by segment
+ */
+
+ using VnTempBuffer<byte> copyBuffer = new(bufferSize);
+
+ do
+ {
+ //read from internal stream
+ int read = Read(copyBuffer);
+
+ if(read <= 0)
+ {
+ break;
+ }
+
+ //write async
+ await destination.WriteAsync(copyBuffer.AsMemory(0, read), cancellationToken);
+
+ } while (true);
+
+ }
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// <para>
+ /// This property is always true
+ /// </para>
+ /// </summary>
+ public override bool CanRead => true;
+ /// <summary>
+ /// <inheritdoc/>
+ /// <para>
+ /// This propery is always true
+ /// </para>
+ /// </summary>
+ public override bool CanSeek => true;
+ /// <summary>
+ /// True unless the stream is (or has been converted to) a readonly
+ /// stream.
+ /// </summary>
+ public override bool CanWrite => !IsReadonly;
+ ///<inheritdoc/>
+ public override long Length => _length;
+ ///<inheritdoc/>
+ public override bool CanTimeout => false;
+
+ ///<inheritdoc/>
+ public override long Position
+ {
+ get => _position;
+ set => Seek(value, SeekOrigin.Begin);
+ }
+ /// <summary>
+ /// Closes the stream and frees the internal allocated memory blocks
+ /// </summary>
+ public override void Close()
+ {
+ //Only dispose buffer if we own it
+ if (OwnsHandle)
+ {
+ _buffer.Dispose();
+ }
+ }
+ ///<inheritdoc/>
+ public override void Flush() { }
+ // Override to reduce base class overhead
+ ///<inheritdoc/>
+ public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+ ///<inheritdoc/>
+ public override int Read(byte[] buffer, int offset, int count) => Read(new Span<byte>(buffer, offset, count));
+ ///<inheritdoc/>
+ public override int Read(Span<byte> destination)
+ {
+ if (destination.Length == 0)
+ {
+ return 0;
+ }
+ //Number of bytes to read from memory buffer
+ int bytesToRead = checked((int)Math.Min(LenToPosDiff, destination.Length));
+ //Copy bytes to buffer
+ Memory.Copy(_buffer, _position, destination, 0, bytesToRead);
+ //Increment buffer position
+ _position += bytesToRead;
+ //Bytestoread should never be larger than int.max because span length is an integer
+ return bytesToRead;
+ }
+
+ /*
+ * Async reading will always run synchronously in a memory stream,
+ * so overrides are just so avoid base class overhead
+ */
+ ///<inheritdoc/>
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ //Read synchronously and return a completed task
+ int read = Read(buffer.Span);
+ return ValueTask.FromResult(read);
+ }
+ ///<inheritdoc/>
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ //Read synchronously and return a completed task
+ int read = Read(buffer.AsSpan(offset, count));
+ return Task.FromResult(read);
+ }
+ ///<inheritdoc/>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "Offset cannot be less than 0");
+ }
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ //Length will never be greater than int.Max so output will never exceed int.max
+ _position = Math.Min(_length, offset);
+ return _position;
+ case SeekOrigin.Current:
+ long newPos = _position + offset;
+ //Length will never be greater than int.Max so output will never exceed length
+ _position = Math.Min(_length, newPos);
+ return newPos;
+ case SeekOrigin.End:
+ long real_index = _length - offset;
+ //If offset moves the position negative, just set the position to 0 and continue
+ _position = Math.Min(real_index, 0);
+ return real_index;
+ default:
+ throw new ArgumentException("Stream operation is not supported on current stream");
+ }
+ }
+
+
+ /// <summary>
+ /// Resizes the internal buffer to the exact size (in bytes) of the
+ /// value argument. A value of 0 will free the entire buffer. A value
+ /// greater than zero will resize the buffer (and/or alloc)
+ /// </summary>
+ /// <param name="value">The size of the stream (and internal buffer)</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public override void SetLength(long value)
+ {
+ if (IsReadonly)
+ {
+ throw new NotSupportedException("This stream is readonly");
+ }
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be less than 0");
+ }
+ //Resize the buffer to the specified length
+ _buffer.Resize(value);
+ //Set length
+ _length = value;
+ //Make sure the position is not pointing outside of the buffer
+ _position = Math.Min(_position, _length);
+ return;
+ }
+ ///<inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count) => Write(new ReadOnlySpan<byte>(buffer, offset, count));
+ ///<inheritdoc/>
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (IsReadonly)
+ {
+ throw new NotSupportedException("Write operation is not allowed on readonly stream!");
+ }
+ //Calculate the new final position
+ long newPos = (_position + buffer.Length);
+ //Determine if the buffer needs to be expanded
+ if (buffer.Length > LenToPosDiff)
+ {
+ //Expand buffer if required
+ _buffer.ResizeIfSmaller(newPos);
+ //Update length
+ _length = newPos;
+ }
+ //Copy the input buffer to the internal buffer
+ Memory.Copy(buffer, _buffer, _position);
+ //Update the position
+ _position = newPos;
+ return;
+ }
+ ///<inheritdoc/>
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ //Write synchronously and return a completed task
+ Write(buffer, offset, count);
+ return Task.CompletedTask;
+ }
+ ///<inheritdoc/>
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ //Write synchronously and return a completed task
+ Write(buffer.Span);
+ return ValueTask.CompletedTask;
+ }
+ ///<inheritdoc/>
+ public override void WriteByte(byte value)
+ {
+ Span<byte> buf = MemoryMarshal.CreateSpan(ref value, 1);
+ Write(buf);
+ }
+
+ /// <summary>
+ /// Allocates and copies internal buffer to new managed byte[]
+ /// </summary>
+ /// <returns>Copy of internal buffer</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public byte[] ToArray()
+ {
+ //Alloc a new array of the size of the internal buffer
+ byte[] data = new byte[_length];
+ //Copy data from the internal buffer to the output buffer
+ _buffer.Span.CopyTo(data);
+ return data;
+
+ }
+ /// <summary>
+ /// Returns a <see cref="ReadOnlySpan{T}"/> window over the data within the entire stream
+ /// </summary>
+ /// <returns>A <see cref="ReadOnlySpan{T}"/> of the data within the entire stream</returns>
+ /// <exception cref="OverflowException"></exception>
+ public ReadOnlySpan<byte> AsSpan()
+ {
+ ReadOnlySpan<byte> output = _buffer.Span;
+ return output[..(int)_length];
+ }
+
+ /// <summary>
+ /// If the current stream is a readonly stream, creates a shallow copy for reading only.
+ /// </summary>
+ /// <returns>New stream shallow copy of the internal stream</returns>
+ /// <exception cref="InvalidOperationException"></exception>
+ public object Clone() => GetReadonlyShallowCopy();
+
+ /*
+ * Override the Dispose async method to avoid the base class overhead
+ * and task allocation since this will always be a syncrhonous
+ * operation (freeing memory)
+ */
+
+ ///<inheritdoc/>
+ public override ValueTask DisposeAsync()
+ {
+ //Dispose and return completed task
+ base.Dispose(true);
+ GC.SuppressFinalize(this);
+ return ValueTask.CompletedTask;
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/VnStreamReader.cs b/Utils/src/IO/VnStreamReader.cs
new file mode 100644
index 0000000..70b9734
--- /dev/null
+++ b/Utils/src/IO/VnStreamReader.cs
@@ -0,0 +1,180 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnStreamReader.cs
+*
+* VnStreamReader.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Binary based buffered text reader, optimized for reading network streams
+ /// </summary>
+ public class VnStreamReader : TextReader, IVnTextReader
+ {
+ private bool disposedValue;
+
+ private readonly ISlindingWindowBuffer<byte> _buffer;
+ ///<inheritdoc/>
+ public virtual Stream BaseStream { get; }
+ ///<inheritdoc/>
+ public Encoding Encoding { get; }
+
+ /// <summary>
+ /// Number of available bytes of buffered data within the current buffer window
+ /// </summary>
+ public int Available => _buffer.AccumulatedSize;
+ /// <summary>
+ /// Gets or sets the line termination used to deliminate a line of data
+ /// </summary>
+ public ReadOnlyMemory<byte> LineTermination { get; set; }
+ Span<byte> IVnTextReader.BufferedDataWindow => _buffer.Accumulated;
+
+ /// <summary>
+ /// Creates a new <see cref="TextReader"/> that reads encoded data from the base.
+ /// Internal buffers will be alloced from <see cref="ArrayPool{T}.Shared"/>
+ /// </summary>
+ /// <param name="baseStream">The underlying stream to read data from</param>
+ /// <param name="enc">The <see cref="Encoding"/> to use when reading from the stream</param>
+ /// <param name="bufferSize">The size of the internal binary buffer</param>
+ public VnStreamReader(Stream baseStream, Encoding enc, int bufferSize)
+ {
+ BaseStream = baseStream;
+ Encoding = enc;
+ //Init a new buffer
+ _buffer = InitializeBuffer(bufferSize);
+ }
+
+ /// <summary>
+ /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size.
+ /// </summary>
+ /// <param name="bufferSize">The requested size of the buffer to alloc</param>
+ /// <remarks>By default requests the buffer from the <see cref="ArrayPool{T}.Shared"/> instance</remarks>
+ protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) => new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize);
+
+ ///<inheritdoc/>
+ public override async Task<string?> ReadLineAsync()
+ {
+ //If buffered data is available, check for line termination
+ if (Available > 0)
+ {
+ //Get current buffer window
+ Memory<byte> buffered = _buffer.AccumulatedBuffer;
+ //search for line termination in current buffer
+ int term = buffered.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ Memory<byte> line = buffered[..term];
+ //Shift the window to the end of the line (excluding the termination)
+ _buffer.AdvanceStart(term + LineTermination.Length);
+ //Decode the line to a string
+ return Encoding.GetString(line.Span);
+ }
+ //Termination not found
+ }
+ //Compact the buffer window and see if space is avialble to buffer more data
+ if (_buffer.CompactBufferWindow())
+ {
+ //There is room, so buffer more data
+ await _buffer.AccumulateDataAsync(BaseStream, CancellationToken.None);
+ //Check again to see if more data is buffered
+ if (Available <= 0)
+ {
+ //No string found
+ return null;
+ }
+ //Get current buffer window
+ Memory<byte> buffered = _buffer.AccumulatedBuffer;
+ //search for line termination in current buffer
+ int term = buffered.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ Memory<byte> line = buffered[..term];
+ //Shift the window to the end of the line (excluding the termination)
+ _buffer.AdvanceStart(term + LineTermination.Length);
+ //Decode the line to a string
+ return Encoding.GetString(line.Span);
+ }
+ }
+ //Termination not found within the entire buffer, so buffer space has been exhausted
+
+ //OOM is raised in the TextReader base class, the standard is preserved
+#pragma warning disable CA2201 // Do not raise reserved exception types
+ throw new OutOfMemoryException("A line termination was not found within the buffer");
+#pragma warning restore CA2201 // Do not raise reserved exception types
+ }
+
+ ///<inheritdoc/>
+ public override int Read(char[] buffer, int index, int count) => Read(buffer.AsSpan(index, count));
+ ///<inheritdoc/>
+ public override int Read(Span<char> buffer)
+ {
+ if (Available <= 0)
+ {
+ return 0;
+ }
+ //Get current buffer window
+ Span<byte> buffered = _buffer.Accumulated;
+ //Convert all avialable data
+ int encoded = Encoding.GetChars(buffered, buffer);
+ //Shift buffer window to the end of the converted data
+ _buffer.AdvanceStart(encoded);
+ //return the number of chars written
+ return Encoding.GetCharCount(buffered);
+ }
+ ///<inheritdoc/>
+ public override void Close() => _buffer.Close();
+ ///<inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ Close();
+ disposedValue = true;
+ }
+ base.Dispose(disposing);
+ }
+
+ /// <summary>
+ /// Resets the internal buffer window
+ /// </summary>
+ protected void ClearBuffer()
+ {
+ _buffer.Reset();
+ }
+
+ void IVnTextReader.Advance(int count) => _buffer.AdvanceStart(count);
+ void IVnTextReader.FillBuffer() => _buffer.AccumulateData(BaseStream);
+ ERRNO IVnTextReader.CompactBufferWindow() => _buffer.CompactBufferWindow();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/VnStreamWriter.cs b/Utils/src/IO/VnStreamWriter.cs
new file mode 100644
index 0000000..37d700c
--- /dev/null
+++ b/Utils/src/IO/VnStreamWriter.cs
@@ -0,0 +1,292 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnStreamWriter.cs
+*
+* VnStreamWriter.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Provides a memory optimized <see cref="TextWriter"/> implementation. Optimized for writing
+ /// to network streams
+ /// </summary>
+ public class VnStreamWriter : TextWriter
+ {
+ private readonly Encoder Enc;
+
+ private readonly ISlindingWindowBuffer<byte> _buffer;
+
+ private bool closed;
+
+ /// <summary>
+ /// Gets the underlying stream that interfaces with the backing store
+ /// </summary>
+ public virtual Stream BaseStream { get; }
+ ///<inheritdoc/>
+ public override Encoding Encoding { get; }
+
+ /// <summary>
+ /// Line termination to use when writing lines to the output
+ /// </summary>
+ public ReadOnlyMemory<byte> LineTermination { get; set; }
+ ///<inheritdoc/>
+ public override string NewLine
+ {
+ get => Encoding.GetString(LineTermination.Span);
+ set => LineTermination = Encoding.GetBytes(value);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="VnStreamWriter"/> that writes formatted data
+ /// to the specified base stream
+ /// </summary>
+ /// <param name="baseStream">The stream to write data to</param>
+ /// <param name="encoding">The <see cref="Encoding"/> to use when writing data</param>
+ /// <param name="bufferSize">The size of the internal buffer used to buffer binary data before writing to the base stream</param>
+ public VnStreamWriter(Stream baseStream, Encoding encoding, int bufferSize = 1024)
+ {
+ //Store base stream
+ BaseStream = baseStream;
+ Encoding = encoding;
+ //Get an encoder
+ Enc = encoding.GetEncoder();
+ _buffer = InitializeBuffer(bufferSize);
+ }
+
+ /// <summary>
+ /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size.
+ /// </summary>
+ /// <param name="bufferSize">The requested size of the buffer to alloc</param>
+ /// <remarks>By default requests the buffer from the <see cref="MemoryPool{T}.Shared"/> instance</remarks>
+ protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) => new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize);
+ ///<inheritdoc/>
+ public void Write(byte value)
+ {
+ //See if there is room in the binary buffer
+ if (_buffer.AccumulatedSize == 0)
+ {
+ //There is not enough room to store the single byte
+ Flush();
+ }
+ //Store at the end of the window
+ _buffer.Append(value);
+ }
+ ///<inheritdoc/>
+ public override void Write(char value)
+ {
+ ReadOnlySpan<char> tbuf = MemoryMarshal.CreateSpan(ref value, 0x01);
+ Write(tbuf);
+ }
+ ///<inheritdoc/>
+ public override void Write(object value) => Write(value.ToString());
+ ///<inheritdoc/>
+ public override void Write(string value) => Write(value.AsSpan());
+ ///<inheritdoc/>
+ public override void Write(ReadOnlySpan<char> buffer)
+ {
+ Check();
+
+ ForwardOnlyReader<char> reader = new(buffer);
+
+ //Create a variable for a character buffer window
+ bool completed;
+ do
+ {
+ //Get an available buffer window to store characters in and convert the characters to binary
+ Enc.Convert(reader.Window, _buffer.Remaining, true, out int charsUsed, out int bytesUsed, out completed);
+ //Update byte position
+ _buffer.Advance(bytesUsed);
+ //Update char position
+ reader.Advance(charsUsed);
+
+ //Converting did not complete because the buffer was too small
+ if (!completed || reader.WindowSize == 0)
+ {
+ //Flush the buffer and continue
+ Flush();
+ }
+
+ } while (!completed);
+ //Reset the encoder
+ Enc.Reset();
+ }
+ ///<inheritdoc/>
+ public override async Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ Check();
+ //Create a variable for a character buffer window
+ bool completed;
+ ForwardOnlyMemoryReader<char> reader = new(buffer);
+ do
+ {
+ //Get an available buffer window to store characters in and convert the characters to binary
+ Enc.Convert(reader.Window.Span, _buffer.Remaining, true, out int charsUsed, out int bytesUsed, out completed);
+ //Update byte position
+ _buffer.Advance(bytesUsed);
+ //Update char position
+ reader.Advance(charsUsed);
+ //Converting did not complete because the buffer was too small
+ if (!completed || reader.WindowSize == 0)
+ {
+ //Flush the buffer and continue
+ await FlushWriterAsync(cancellationToken);
+ }
+ } while (!completed);
+ //Reset the encoder
+ Enc.Reset();
+ }
+
+ ///<inheritdoc/>
+ public override void WriteLine()
+ {
+ Check();
+ //See if there is room in the binary buffer
+ if (_buffer.RemainingSize < LineTermination.Length)
+ {
+ //There is not enough room to store the termination, so we need to flush the buffer
+ Flush();
+ }
+ _buffer.Append(LineTermination.Span);
+ }
+ ///<inheritdoc/>
+ public override void WriteLine(object value) => WriteLine(value.ToString());
+ ///<inheritdoc/>
+ public override void WriteLine(string value) => WriteLine(value.AsSpan());
+ ///<inheritdoc/>
+ public override void WriteLine(ReadOnlySpan<char> buffer)
+ {
+ //Write the value itself
+ Write(buffer);
+ //Write the line termination
+ WriteLine();
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public override void Flush()
+ {
+ Check();
+ //If data is available to be written, write it to the base stream
+ if (_buffer.AccumulatedSize > 0)
+ {
+ //Write all buffered data to stream
+ BaseStream.Write(_buffer.Accumulated);
+ //Reset the buffer
+ _buffer.Reset();
+ }
+ }
+ /// <summary>
+ /// Asynchronously flushes the internal buffers to the <see cref="BaseStream"/>, and resets the internal buffer state
+ /// </summary>
+ /// <returns>A <see cref="ValueTask"/> that represents the asynchronous flush operation</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public async ValueTask FlushWriterAsync(CancellationToken cancellationToken = default)
+ {
+ Check();
+ if (_buffer.AccumulatedSize > 0)
+ {
+ //Flush current window to the stream
+ await BaseStream.WriteAsync(_buffer.AccumulatedBuffer, cancellationToken);
+ //Reset the buffer
+ _buffer.Reset();
+ }
+ }
+
+ ///<inheritdoc/>
+ public override Task FlushAsync() => FlushWriterAsync().AsTask();
+
+ /// <summary>
+ /// Resets internal properies for resuse
+ /// </summary>
+ protected void Reset()
+ {
+ _buffer.Reset();
+ Enc.Reset();
+ }
+ ///<inheritdoc/>
+ public override void Close()
+ {
+ //Only invoke close once
+ if (closed)
+ {
+ return;
+ }
+ try
+ {
+ Flush();
+ }
+ finally
+ {
+ //Release the memory handle if its set
+ _buffer.Close();
+ //Set closed flag
+ closed = true;
+ }
+ }
+ ///<inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ Close();
+ base.Dispose(disposing);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Check()
+ {
+ if (closed)
+ {
+ throw new ObjectDisposedException("The stream is closed");
+ }
+ }
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ //Only invoke close once
+ if (closed)
+ {
+ return;
+ }
+ try
+ {
+ await FlushWriterAsync();
+ }
+ finally
+ {
+ //Set closed flag
+ closed = true;
+ //Release the memory handle if its set
+ _buffer.Close();
+ }
+ GC.SuppressFinalize(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/VnTextReaderExtensions.cs b/Utils/src/IO/VnTextReaderExtensions.cs
new file mode 100644
index 0000000..119461b
--- /dev/null
+++ b/Utils/src/IO/VnTextReaderExtensions.cs
@@ -0,0 +1,223 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnTextReaderExtensions.cs
+*
+* VnTextReaderExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// Extension methods to help reuse code for used TextReader implementations
+ /// </summary>
+ public static class VnTextReaderExtensions
+ {
+ public const int E_BUFFER_TOO_SMALL = -1;
+
+
+ /*
+ * Generic extensions provide constained compiler method invocation
+ * for structs the implement the IVNtextReader
+ */
+
+ /// <summary>
+ /// Attempts to read a line from the stream and store it in the specified buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="charBuffer">The character buffer to write data to</param>
+ /// <returns>Returns the number of bytes read, <see cref="E_BUFFER_TOO_SMALL"/>
+ /// if the buffer was not large enough, 0 if no data was available</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <remarks>Allows reading lines of data from the stream without allocations</remarks>
+ public static ERRNO ReadLine<T>(this ref T reader, Span<char> charBuffer) where T:struct, IVnTextReader
+ {
+ return readLine(ref reader, charBuffer);
+ }
+ /// <summary>
+ /// Attempts to read a line from the stream and store it in the specified buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="charBuffer">The character buffer to write data to</param>
+ /// <returns>Returns the number of bytes read, <see cref="E_BUFFER_TOO_SMALL"/>
+ /// if the buffer was not large enough, 0 if no data was available</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <remarks>Allows reading lines of data from the stream without allocations</remarks>
+ public static ERRNO ReadLine<T>(this T reader, Span<char> charBuffer) where T : class, IVnTextReader
+ {
+ return readLine(ref reader, charBuffer);
+ }
+
+ /// <summary>
+ /// Fill a buffer with reamining buffered data
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <param name="offset">Offset in buffer to begin writing</param>
+ /// <param name="count">Number of bytes to read</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ public static int ReadRemaining<T>(this ref T reader, byte[] buffer, int offset, int count) where T : struct, IVnTextReader
+ {
+ return reader.ReadRemaining(buffer.AsSpan(offset, count));
+ }
+ /// <summary>
+ /// Fill a buffer with reamining buffered data
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <param name="offset">Offset in buffer to begin writing</param>
+ /// <param name="count">Number of bytes to read</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ public static int ReadRemaining<T>(this T reader, byte[] buffer, int offset, int count) where T : class, IVnTextReader
+ {
+ return reader.ReadRemaining(buffer.AsSpan(offset, count));
+ }
+
+ /// <summary>
+ /// Fill a buffer with reamining buffered data, up to
+ /// the size of the supplied buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ /// <remarks>You should use the <see cref="IVnTextReader.Available"/> property to know how much remaining data is buffered</remarks>
+ public static int ReadRemaining<T>(this ref T reader, Span<byte> buffer) where T : struct, IVnTextReader
+ {
+ return readRemaining(ref reader, buffer);
+ }
+ /// <summary>
+ /// Fill a buffer with reamining buffered data, up to
+ /// the size of the supplied buffer
+ /// </summary>
+ /// <param name="reader"></param>
+ /// <param name="buffer">Buffer to copy data to</param>
+ /// <returns>The number of bytes copied to the input buffer</returns>
+ /// <remarks>You should use the <see cref="IVnTextReader.Available"/> property to know how much remaining data is buffered</remarks>
+ public static int ReadRemaining<T>(this T reader, Span<byte> buffer) where T : class, IVnTextReader
+ {
+ return readRemaining(ref reader, buffer);
+ }
+
+ private static ERRNO readLine<T>(ref T reader, Span<char> chars) where T: IVnTextReader
+ {
+ /*
+ * I am aware of a potential bug, the line decoding process
+ * shifts the interal buffer by the exact number of bytes to
+ * the end of the line, without considering if the decoder failed
+ * to properly decode the entire line.
+ *
+ * I dont expect this to be an issue unless there is a bug within the specified
+ * encoder implementation
+ */
+ ReadOnlySpan<byte> LineTermination = reader.LineTermination.Span;
+ //If buffered data is available, check for line termination
+ if (reader.Available > 0)
+ {
+ //Get current buffer window
+ ReadOnlySpan<byte> bytes = reader.BufferedDataWindow;
+ //search for line termination in current buffer
+ int term = bytes.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ ReadOnlySpan<byte> line = bytes[..term];
+ //Get the number ot chars
+ int charCount = reader.Encoding.GetCharCount(line);
+ //See if the buffer is large enough
+ if (bytes.Length < charCount)
+ {
+ return E_BUFFER_TOO_SMALL;
+ }
+ //Use the decoder to convert the data
+ _ = reader.Encoding.GetChars(line, chars);
+ //Shift the window to the end of the line (excluding the termination, regardless of the conversion result)
+ reader.Advance(term + LineTermination.Length);
+ //Return the number of characters
+ return charCount;
+ }
+ //Termination not found but there may be more data waiting
+ }
+ //Compact the buffer window and make sure it was compacted so there is room to fill the buffer
+ if (reader.CompactBufferWindow())
+ {
+ //There is room, so buffer more data
+ reader.FillBuffer();
+ //Check again to see if more data is buffered
+ if (reader.Available <= 0)
+ {
+ //No data avialable
+ return 0;
+ }
+ //Get current buffer window
+ ReadOnlySpan<byte> bytes = reader.BufferedDataWindow;
+ //search for line termination in current buffer
+ int term = bytes.IndexOf(LineTermination);
+ //Termination found in buffer window
+ if (term > -1)
+ {
+ //Capture the line from the begining of the window to the termination
+ ReadOnlySpan<byte> line = bytes[..term];
+ //Get the number ot chars
+ int charCount = reader.Encoding.GetCharCount(line);
+ //See if the buffer is large enough
+ if (bytes.Length < charCount)
+ {
+ return E_BUFFER_TOO_SMALL;
+ }
+ //Use the decoder to convert the data
+ _ = reader.Encoding.GetChars(line, chars);
+ //Shift the window to the end of the line (excluding the termination, regardless of the conversion result)
+ reader.Advance(term + LineTermination.Length);
+ //Return the number of characters
+ return charCount;
+ }
+ }
+
+ //Termination not found within the entire buffer, so buffer space has been exhausted
+
+ //Supress as this response is expected when the buffer is exhausted,
+#pragma warning disable CA2201 // Do not raise reserved exception types
+ throw new OutOfMemoryException("The line was not found within the current buffer, cannot continue");
+#pragma warning restore CA2201 // Do not raise reserved exception types
+ }
+
+ private static int readRemaining<T>(ref T reader, Span<byte> buffer) where T: IVnTextReader
+ {
+ //guard for empty buffer
+ if (buffer.Length == 0 || reader.Available == 0)
+ {
+ return 0;
+ }
+ //get the remaining bytes in the reader
+ Span<byte> remaining = reader.BufferedDataWindow;
+ //Calculate the number of bytes to copy
+ int canCopy = Math.Min(remaining.Length, buffer.Length);
+ //Copy remaining bytes to buffer
+ remaining[..canCopy].CopyTo(buffer);
+ //Shift the window by the number of bytes copied
+ reader.Advance(canCopy);
+ return canCopy;
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/IO/WriteOnlyBufferedStream.cs b/Utils/src/IO/WriteOnlyBufferedStream.cs
new file mode 100644
index 0000000..5e7faa1
--- /dev/null
+++ b/Utils/src/IO/WriteOnlyBufferedStream.cs
@@ -0,0 +1,255 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: WriteOnlyBufferedStream.cs
+*
+* WriteOnlyBufferedStream.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.IO
+{
+ /// <summary>
+ /// A basic accumulator style write buffered stream
+ /// </summary>
+ public class WriteOnlyBufferedStream : Stream
+ {
+ private readonly ISlindingWindowBuffer<byte> _buffer;
+ private readonly bool LeaveOpen;
+
+ /// <summary>
+ /// Gets the underlying stream that interfaces with the backing store
+ /// </summary>
+ public Stream BaseStream { get; init; }
+
+ /// <summary>
+ /// Initalizes a new <see cref="WriteOnlyBufferedStream"/> using the
+ /// specified backing stream, using the specified buffer size, and
+ /// optionally leaves the stream open
+ /// </summary>
+ /// <param name="baseStream">The backing stream to write buffered data to</param>
+ /// <param name="bufferSize">The size of the internal buffer</param>
+ /// <param name="leaveOpen">A value indicating of the stream should be left open when the buffered stream is closed</param>
+ public WriteOnlyBufferedStream(Stream baseStream, int bufferSize, bool leaveOpen = false)
+ {
+ BaseStream = baseStream;
+ //Create buffer
+ _buffer = InitializeBuffer(bufferSize);
+ LeaveOpen = leaveOpen;
+ }
+ /// <summary>
+ /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size.
+ /// </summary>
+ /// <param name="bufferSize">The requested size of the buffer to alloc</param>
+ /// <remarks>By default requests the buffer from the <see cref="ArrayPool{T}.Shared"/> instance</remarks>
+ protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize)
+ {
+ return new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize);
+ }
+
+ ///<inheritdoc/>
+ public override void Close()
+ {
+ try
+ {
+ //Make sure the buffer is empty
+ WriteBuffer();
+
+ if (!LeaveOpen)
+ {
+ //Dispose stream
+ BaseStream.Dispose();
+ }
+ }
+ finally
+ {
+ _buffer.Close();
+ }
+ }
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ try
+ {
+ if (_buffer.AccumulatedSize > 0)
+ {
+ await WriteBufferAsync(CancellationToken.None);
+ }
+
+ if (!LeaveOpen)
+ {
+ //Dispose stream
+ await BaseStream.DisposeAsync();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ finally
+ {
+ _buffer.Close();
+ }
+ }
+
+ ///<inheritdoc/>
+ public override void Flush() => WriteBuffer();
+ ///<inheritdoc/>
+ public override Task FlushAsync(CancellationToken cancellationToken) => WriteBufferAsync(cancellationToken).AsTask();
+
+ private void WriteBuffer()
+ {
+ //Only if data is available to write
+ if (_buffer.AccumulatedSize > 0)
+ {
+ //Write data to stream
+ BaseStream.Write(_buffer.Accumulated);
+ //Reset position
+ _buffer.Reset();
+ }
+ }
+
+ private async ValueTask WriteBufferAsync(CancellationToken token = default)
+ {
+ if(_buffer.AccumulatedSize > 0)
+ {
+ await BaseStream.WriteAsync(_buffer.AccumulatedBuffer, token);
+ _buffer.Reset();
+ }
+ }
+ ///<inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count));
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ ForwardOnlyReader<byte> reader = new(buffer);
+ //Attempt to buffer/flush data until all data is sent
+ do
+ {
+ //Try to buffer as much as possible
+ ERRNO buffered = _buffer.TryAccumulate(reader.Window);
+
+ if(buffered < reader.WindowSize)
+ {
+ //Buffer is full and needs to be flushed
+ WriteBuffer();
+ //Advance reader and continue to buffer
+ reader.Advance(buffered);
+ continue;
+ }
+
+ break;
+ }
+ while (true);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
+
+ public async override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ ForwardOnlyMemoryReader<byte> reader = new(buffer);
+ //Attempt to buffer/flush data until all data is sent
+ do
+ {
+ //Try to buffer as much as possible
+ ERRNO buffered = _buffer.TryAccumulate(reader.Window.Span);
+
+ if (buffered < reader.WindowSize)
+ {
+ //Buffer is full and needs to be flushed
+ await WriteBufferAsync(cancellationToken);
+ //Advance reader and continue to buffer
+ reader.Advance(buffered);
+ continue;
+ }
+
+ break;
+ }
+ while (true);
+ }
+
+
+ /// <summary>
+ /// Always false
+ /// </summary>
+ public override bool CanRead => false;
+ /// <summary>
+ /// Always returns false
+ /// </summary>
+ public override bool CanSeek => false;
+ /// <summary>
+ /// Always true
+ /// </summary>
+ public override bool CanWrite => true;
+ /// <summary>
+ /// Returns the size of the underlying buffer
+ /// </summary>
+ public override long Length => _buffer.AccumulatedSize;
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ /// <returns></returns>
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException("This stream is not readable");
+ }
+
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Always throws <see cref="NotSupportedException"/>
+ /// </summary>
+ /// <exception cref="NotSupportedException"></exception>
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Utils/src/IObjectStorage.cs b/Utils/src/IObjectStorage.cs
new file mode 100644
index 0000000..5c99cd8
--- /dev/null
+++ b/Utils/src/IObjectStorage.cs
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IObjectStorage.cs
+*
+* IObjectStorage.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// This object will provide methods for storing and retreiving objects by key-value pairing
+ /// </summary>
+ public interface IObjectStorage
+ {
+ /// <summary>
+ /// Attempts to retrieve the specified object from storage
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="key">Key for storage</param>
+ /// <returns>The object in storage, or T.default if object is not found</returns>
+ public T GetObject<T>(string key);
+
+ /// <summary>
+ /// Stores the specified object with the specified key
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="key">Key paired with object</param>
+ /// <param name="obj">Object to store</param>
+ public void SetObject<T>(string key, T obj);
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Logging/ILogProvider.cs b/Utils/src/Logging/ILogProvider.cs
new file mode 100644
index 0000000..55dbd6f
--- /dev/null
+++ b/Utils/src/Logging/ILogProvider.cs
@@ -0,0 +1,79 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ILogProvider.cs
+*
+* ILogProvider.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Logging
+{
+ /// <summary>
+ /// Self-contained logging interface that allows for applications events to be written to an
+ /// output source
+ /// </summary>
+ public interface ILogProvider
+ {
+ /// <summary>
+ /// Flushes any buffers to the output source
+ /// </summary>
+ abstract void Flush();
+
+ /// <summary>
+ /// Writes the string to the log with the specified priority log level
+ /// </summary>
+ /// <param name="level">The log priority level</param>
+ /// <param name="value">The message to print</param>
+ void Write(LogLevel level, string value);
+ /// <summary>
+ /// Writes the exception and optional string to the log with the specified priority log level
+ /// </summary>
+ /// <param name="level">The log priority level</param>
+ /// <param name="exception">An exception object to write</param>
+ /// <param name="value">The message to print</param>
+ void Write(LogLevel level, Exception exception, string value = "");
+ /// <summary>
+ /// Writes the template string and params arguments to the log with the specified priority log level
+ /// </summary>
+ /// <param name="level">The log priority level</param>
+ /// <param name="value">The log template string</param>
+ /// <param name="args">Variable length array of objects to log with the specified templatre</param>
+ void Write(LogLevel level, string value, params object?[] args);
+ /// <summary>
+ /// Writes the template string and params arguments to the log with the specified priority log level
+ /// </summary>
+ /// <param name="level">The log priority level</param>
+ /// <param name="value">The log template string</param>
+ /// <param name="args">Variable length array of objects to log with the specified templatre</param>
+ void Write(LogLevel level, string value, params ValueType[] args);
+
+ /// <summary>
+ /// Gets the underlying log source
+ /// </summary>
+ /// <returns>The underlying log source</returns>
+ object GetLogProvider();
+ /// <summary>
+ /// Gets the underlying log source
+ /// </summary>
+ /// <returns>The underlying log source</returns>
+ public virtual T GetLogProvider<T>() => (T)GetLogProvider();
+ }
+}
diff --git a/Utils/src/Logging/LogLevel.cs b/Utils/src/Logging/LogLevel.cs
new file mode 100644
index 0000000..1851c26
--- /dev/null
+++ b/Utils/src/Logging/LogLevel.cs
@@ -0,0 +1,33 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: LogLevel.cs
+*
+* LogLevel.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Logging
+{
+ public enum LogLevel
+ {
+ Verbose, Debug, Information, Warning, Error, Fatal
+ }
+}
diff --git a/Utils/src/Logging/LoggerExtensions.cs b/Utils/src/Logging/LoggerExtensions.cs
new file mode 100644
index 0000000..cd314ed
--- /dev/null
+++ b/Utils/src/Logging/LoggerExtensions.cs
@@ -0,0 +1,60 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: LoggerExtensions.cs
+*
+* LoggerExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+#pragma warning disable CA1062 // Validate arguments of public methods
+
+using System;
+
+namespace VNLib.Utils.Logging
+{
+ /// <summary>
+ /// Extension helper methods for writing logs to a <see cref="ILogProvider"/>
+ /// </summary>
+ public static class LoggerExtensions
+ {
+ public static void Debug(this ILogProvider log, Exception exp, string value = "") => log.Write(LogLevel.Debug, exp, value);
+ public static void Debug(this ILogProvider log, string value) => log.Write(LogLevel.Debug, value);
+ public static void Debug(this ILogProvider log, string format, params object?[] args) => log.Write(LogLevel.Debug, format, args);
+ public static void Debug(this ILogProvider log, string format, params ValueType[] args) => log.Write(LogLevel.Debug, format, args);
+ public static void Error(this ILogProvider log, Exception exp, string value = "") => log.Write(LogLevel.Error, exp, value);
+ public static void Error(this ILogProvider log, string value) => log.Write(LogLevel.Error, value);
+ public static void Error(this ILogProvider log, string format, params object?[] args) => log.Write(LogLevel.Error, format, args);
+ public static void Fatal(this ILogProvider log, Exception exp, string value = "") => log.Write(LogLevel.Fatal, exp, value);
+ public static void Fatal(this ILogProvider log, string value) => log.Write(LogLevel.Fatal, value);
+ public static void Fatal(this ILogProvider log, string format, params object?[] args) => log.Write(LogLevel.Fatal, format, args);
+ public static void Fatal(this ILogProvider log, string format, params ValueType[] args) => log.Write(LogLevel.Fatal, format, args);
+ public static void Information(this ILogProvider log, Exception exp, string value = "") => log.Write(LogLevel.Information, exp, value);
+ public static void Information(this ILogProvider log, string value) => log.Write(LogLevel.Information, value);
+ public static void Information(this ILogProvider log, string format, params object?[] args) => log.Write(LogLevel.Information, format, args);
+ public static void Information(this ILogProvider log, string format, params ValueType[] args) => log.Write(LogLevel.Information, format, args);
+ public static void Verbose(this ILogProvider log, Exception exp, string value = "") => log.Write(LogLevel.Verbose, exp, value);
+ public static void Verbose(this ILogProvider log, string value) => log.Write(LogLevel.Verbose, value);
+ public static void Verbose(this ILogProvider log, string format, params object?[] args) => log.Write(LogLevel.Verbose, format, args);
+ public static void Verbose(this ILogProvider log, string format, params ValueType[] args) => log.Write(LogLevel.Verbose, format, args);
+ public static void Warn(this ILogProvider log, Exception exp, string value = "") => log.Write(LogLevel.Warning, exp, value);
+ public static void Warn(this ILogProvider log, string value) => log.Write(LogLevel.Warning, value);
+ public static void Warn(this ILogProvider log, string format, params object?[] args) => log.Write(LogLevel.Warning, format, args);
+ public static void Warn(this ILogProvider log, string format, params ValueType[] args) => log.Write(LogLevel.Warning, format, args);
+ }
+}
diff --git a/Utils/src/Memory/Caching/ICacheHolder.cs b/Utils/src/Memory/Caching/ICacheHolder.cs
new file mode 100644
index 0000000..19eee64
--- /dev/null
+++ b/Utils/src/Memory/Caching/ICacheHolder.cs
@@ -0,0 +1,45 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ICacheHolder.cs
+*
+* ICacheHolder.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// Exposes basic control of classes that manage private caches
+ /// </summary>
+ public interface ICacheHolder
+ {
+ /// <summary>
+ /// Clears all held caches without causing application stopping effects.
+ /// </summary>
+ /// <remarks>This is a safe "light" cache clear</remarks>
+ void CacheClear();
+ /// <summary>
+ /// Performs all necessary actions to clear all held caches immediatly.
+ /// </summary>
+ /// <remarks>A "hard" cache clear/reset regardless of cost</remarks>
+ void CacheHardClear();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/Caching/ICacheable.cs b/Utils/src/Memory/Caching/ICacheable.cs
new file mode 100644
index 0000000..37575cc
--- /dev/null
+++ b/Utils/src/Memory/Caching/ICacheable.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ICacheable.cs
+*
+* ICacheable.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// Represents a cacheable entity with an expiration
+ /// </summary>
+ public interface ICacheable : IEquatable<ICacheable>
+ {
+ /// <summary>
+ /// A <see cref="DateTime"/> value that the entry is no longer valid
+ /// </summary>
+ DateTime Expires { get; set; }
+
+ /// <summary>
+ /// Invoked when a collection occurs
+ /// </summary>
+ void Evicted();
+ }
+}
diff --git a/Utils/src/Memory/Caching/IObjectRental.cs b/Utils/src/Memory/Caching/IObjectRental.cs
new file mode 100644
index 0000000..d9489f4
--- /dev/null
+++ b/Utils/src/Memory/Caching/IObjectRental.cs
@@ -0,0 +1,47 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IObjectRental.cs
+*
+* IObjectRental.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils.Memory.Caching
+{
+
+ /// <summary>
+ /// A thread safe store for reusing CLR managed objects
+ /// </summary>
+ /// <typeparam name="T">The reusable object class</typeparam>
+ public interface IObjectRental<T> where T: class
+ {
+ /// <summary>
+ /// Gets an object from the store, or creates a new one if none are available
+ /// </summary>
+ /// <returns>An instance of <typeparamref name="T"/> from the store if available or a new instance if none were available</returns>
+ T Rent();
+
+ /// <summary>
+ /// Returns a rented object back to the rental store for reuse
+ /// </summary>
+ /// <param name="item">The previously rented item</param>
+ void Return(T item);
+ }
+
+} \ No newline at end of file
diff --git a/Utils/src/Memory/Caching/IReusable.cs b/Utils/src/Memory/Caching/IReusable.cs
new file mode 100644
index 0000000..618878f
--- /dev/null
+++ b/Utils/src/Memory/Caching/IReusable.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IReusable.cs
+*
+* IReusable.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// Allows for use within a <see cref="ReusableStore{T}"/>, this object is intended to be reused heavily
+ /// </summary>
+ public interface IReusable
+ {
+ /// <summary>
+ /// The instance should prepare itself for use (or re-use)
+ /// </summary>
+ void Prepare();
+ /// <summary>
+ /// The intance is being returned and should determine if it's state is reusabled
+ /// </summary>
+ /// <returns>true if the instance can/should be reused, false if it should not be reused</returns>
+ bool Release();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/Caching/LRUCache.cs b/Utils/src/Memory/Caching/LRUCache.cs
new file mode 100644
index 0000000..7e96e0a
--- /dev/null
+++ b/Utils/src/Memory/Caching/LRUCache.cs
@@ -0,0 +1,127 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: LRUCache.cs
+*
+* LRUCache.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// A base class for a Least Recently Used cache
+ /// </summary>
+ /// <typeparam name="TKey">The key for O(1) lookups</typeparam>
+ /// <typeparam name="TValue">The value to store within cache</typeparam>
+ public abstract class LRUCache<TKey, TValue> : LRUDataStore<TKey, TValue> where TKey : notnull
+ {
+ ///<inheritdoc/>
+ protected LRUCache()
+ {}
+ ///<inheritdoc/>
+ protected LRUCache(int initialCapacity) : base(initialCapacity)
+ {}
+ ///<inheritdoc/>
+ protected LRUCache(IEqualityComparer<TKey> keyComparer) : base(keyComparer)
+ {}
+ ///<inheritdoc/>
+ protected LRUCache(int initialCapacity, IEqualityComparer<TKey> keyComparer) : base(initialCapacity, keyComparer)
+ {}
+
+ /// <summary>
+ /// The maximum number of items to store in LRU cache
+ /// </summary>
+ protected abstract int MaxCapacity { get; }
+
+ /// <summary>
+ /// Adds a new record to the LRU cache
+ /// </summary>
+ /// <param name="item">A <see cref="KeyValuePair{TKey, TValue}"/> to add to the cache store</param>
+ public override void Add(KeyValuePair<TKey, TValue> item)
+ {
+ //See if the store is at max capacity and an item needs to be evicted
+ if(Count == MaxCapacity)
+ {
+ //A record needs to be evicted before a new record can be added
+
+ //Get the oldest node from the list to reuse its instance and remove the old value
+ LinkedListNode<KeyValuePair<TKey, TValue>> oldNode = List.First!; //not null because count is at max capacity so an item must be at the end of the list
+ //Store old node value field
+ KeyValuePair<TKey, TValue> oldRecord = oldNode.Value;
+ //Remove from lookup
+ LookupTable.Remove(oldRecord.Key);
+ //Remove the node
+ List.RemoveFirst();
+ //Reuse the old ll node
+ oldNode.Value = item;
+ //add lookup with new key
+ LookupTable.Add(item.Key, oldNode);
+ //Add to end of list
+ List.AddLast(oldNode);
+ //Invoke evicted method
+ Evicted(oldRecord);
+ }
+ else
+ {
+ //Add new item to the list
+ base.Add(item);
+ }
+ }
+ /// <summary>
+ /// Attempts to get a value by the given key.
+ /// </summary>
+ /// <param name="key">The key identifying the value to store</param>
+ /// <param name="value">The value to store</param>
+ /// <returns>A value indicating if the value was found in the store</returns>
+ public override bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value)
+ {
+ //See if the cache contains the value
+ if(base.TryGetValue(key, out value))
+ {
+ //Cache hit
+ return true;
+ }
+ //Cache miss
+ if(CacheMiss(key, out value))
+ {
+ //Lookup hit
+ //Add the record to the store (eviction will happen as necessary
+ Add(key, value);
+ return true;
+ }
+ //Record does not exist
+ return false;
+ }
+ /// <summary>
+ /// Invoked when a record is evicted from the cache
+ /// </summary>
+ /// <param name="evicted">The record that is being evicted</param>
+ protected abstract void Evicted(KeyValuePair<TKey, TValue> evicted);
+ /// <summary>
+ /// Invoked when an entry was requested and was not found in cache.
+ /// </summary>
+ /// <param name="key">The key identifying the record to lookup</param>
+ /// <param name="value">The found value matching the key</param>
+ /// <returns>A value indicating if the record was found</returns>
+ protected abstract bool CacheMiss(TKey key, [NotNullWhen(true)] out TValue? value);
+ }
+}
diff --git a/Utils/src/Memory/Caching/LRUDataStore.cs b/Utils/src/Memory/Caching/LRUDataStore.cs
new file mode 100644
index 0000000..f564fcc
--- /dev/null
+++ b/Utils/src/Memory/Caching/LRUDataStore.cs
@@ -0,0 +1,232 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: LRUDataStore.cs
+*
+* LRUDataStore.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// A Least Recently Used store base class for E2E O(1) operations
+ /// </summary>
+ /// <typeparam name="TKey">A key used for O(1) lookups</typeparam>
+ /// <typeparam name="TValue">A value to store</typeparam>
+ public abstract class LRUDataStore<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<TValue>, IEnumerable<KeyValuePair<TKey, TValue>>
+ where TKey: notnull
+ {
+ /// <summary>
+ /// A lookup table that provides O(1) access times for key-value lookups
+ /// </summary>
+ protected Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> LookupTable { get; }
+ /// <summary>
+ /// A linked list that tracks the least recently used item.
+ /// New items (or recently access items) are moved to the end of the list.
+ /// The head contains the least recently used item
+ /// </summary>
+ protected LinkedList<KeyValuePair<TKey, TValue>> List { get; }
+
+ /// <summary>
+ /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/>
+ /// </summary>
+ protected LRUDataStore()
+ {
+ LookupTable = new();
+ List = new();
+ }
+ /// <summary>
+ /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and sets
+ /// the lookup table's inital capacity
+ /// </summary>
+ /// <param name="initialCapacity">LookupTable initial capacity</param>
+ protected LRUDataStore(int initialCapacity)
+ {
+ LookupTable = new(initialCapacity);
+ List = new();
+ }
+ /// <summary>
+ /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and uses the
+ /// specified keycomparison
+ /// </summary>
+ /// <param name="keyComparer">A <see cref="IEqualityComparer{T}"/> used by the Lookuptable to compare keys</param>
+ protected LRUDataStore(IEqualityComparer<TKey> keyComparer)
+ {
+ LookupTable = new(keyComparer);
+ List = new();
+ }
+ /// <summary>
+ /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and uses the
+ /// specified keycomparison, and sets the lookup table's initial capacity
+ /// </summary>
+ /// <param name="initialCapacity">LookupTable initial capacity</param>
+ /// <param name="keyComparer">A <see cref="IEqualityComparer{T}"/> used by the Lookuptable to compare keys</param>
+ protected LRUDataStore(int initialCapacity, IEqualityComparer<TKey> keyComparer)
+ {
+ LookupTable = new(initialCapacity, keyComparer);
+ List = new();
+ }
+
+ /// <summary>
+ /// Gets or sets a value within the LRU cache.
+ /// </summary>
+ /// <param name="key">The key identifying the value</param>
+ /// <returns>The value stored at the given key</returns>
+ /// <remarks>Items are promoted in the store when accessed</remarks>
+ public virtual TValue this[TKey key]
+ {
+ get
+ {
+ return TryGetValue(key, out TValue? value)
+ ? value
+ : throw new KeyNotFoundException("The item or its key were not found in the LRU data store");
+ }
+ set
+ {
+ //If a node by the same key in the store exists, just replace its value
+ if(LookupTable.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>>? oldNode))
+ {
+ //Remove the node before re-adding it
+ List.Remove(oldNode);
+ oldNode.Value = new KeyValuePair<TKey, TValue>(key, value);
+ //Move the item to the front of the list
+ List.AddLast(oldNode);
+ }
+ else
+ {
+ //Node does not exist yet so create new one
+ Add(key, value);
+ }
+ }
+ }
+ ///<inheritdoc/>
+ public ICollection<TKey> Keys => LookupTable.Keys;
+ ///<inheritdoc/>
+ ///<exception cref="NotImplementedException"></exception>
+ public ICollection<TValue> Values => throw new NotImplementedException();
+ IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => LookupTable.Keys;
+ IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => List.Select(static node => node.Value);
+ IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => List.Select(static node => node.Value).GetEnumerator();
+
+ /// <summary>
+ /// Gets the number of items within the LRU store
+ /// </summary>
+ public int Count => List.Count;
+ ///<inheritdoc/>
+ public abstract bool IsReadOnly { get; }
+
+ /// <summary>
+ /// Adds the specified record to the store and places it at the end of the LRU queue
+ /// </summary>
+ /// <param name="key">The key identifying the record</param>
+ /// <param name="value">The value to store at the key</param>
+ public void Add(TKey key, TValue value)
+ {
+ //Create new kvp lookup ref
+ KeyValuePair<TKey, TValue> lookupRef = new(key, value);
+ //Insert the lookup
+ Add(lookupRef);
+ }
+ ///<inheritdoc/>
+ public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
+ ///<inheritdoc/>
+ IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator();
+ ///<inheritdoc/>
+ public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => List.CopyTo(array, arrayIndex);
+ ///<inheritdoc/>
+ public virtual bool ContainsKey(TKey key) => LookupTable.ContainsKey(key);
+ ///<inheritdoc/>
+ public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => List.GetEnumerator();
+
+ /// <summary>
+ /// Adds the specified record to the store and places it at the end of the LRU queue
+ /// </summary>
+ /// <param name="item">The item to add</param>
+ public virtual void Add(KeyValuePair<TKey, TValue> item)
+ {
+ //Init new ll node
+ LinkedListNode<KeyValuePair<TKey, TValue>> newNode = new(item);
+ //Insert the new node
+ LookupTable.Add(item.Key, newNode);
+ //Add to the end of the linked list
+ List.AddLast(newNode);
+ }
+ /// <summary>
+ /// Removes all elements from the LRU store
+ /// </summary>
+ public virtual void Clear()
+ {
+ //Clear lists
+ LookupTable.Clear();
+ List.Clear();
+ }
+ /// <summary>
+ /// Determines if the <see cref="KeyValuePair{TKey, TValue}"/> exists in the store
+ /// </summary>
+ /// <param name="item">The record to search for</param>
+ /// <returns>True if the key was found in the store and the value equals the stored value, false otherwise</returns>
+ public virtual bool Contains(KeyValuePair<TKey, TValue> item)
+ {
+ if (LookupTable.TryGetValue(item.Key, out LinkedListNode<KeyValuePair<TKey, TValue>>? lookup))
+ {
+ return lookup.Value.Value?.Equals(item.Value) ?? false;
+ }
+ return false;
+ }
+ ///<inheritdoc/>
+ public virtual bool Remove(TKey key)
+ {
+ //Remove the item from the lookup table and if it exists, remove the node from the list
+ if(LookupTable.Remove(key, out LinkedListNode<KeyValuePair<TKey, TValue>>? node))
+ {
+ //Remove the new from the list
+ List.Remove(node);
+ return true;
+ }
+ return false;
+ }
+ /// <summary>
+ /// Tries to get a value from the store with its key. Found items are promoted
+ /// </summary>
+ /// <param name="key">The key identifying the value</param>
+ /// <param name="value">The found value</param>
+ /// <returns>A value indicating if the element was found in the store</returns>
+ public virtual bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value)
+ {
+ //Lookup the
+ if (LookupTable.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>>? val))
+ {
+ //Remove the value from the list and add it to the front of the list
+ List.Remove(val);
+ List.AddLast(val);
+ value = val.Value.Value!;
+ return true;
+ }
+ value = default;
+ return false;
+ }
+
+ }
+}
diff --git a/Utils/src/Memory/Caching/ObjectRental.cs b/Utils/src/Memory/Caching/ObjectRental.cs
new file mode 100644
index 0000000..22aca95
--- /dev/null
+++ b/Utils/src/Memory/Caching/ObjectRental.cs
@@ -0,0 +1,236 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ObjectRental.cs
+*
+* ObjectRental.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ //TODO: implement lock-free object tracking
+
+ /// <summary>
+ /// Provides concurrent storage for reusable objects to be rented and returned. This class
+ /// and its members is thread-safe
+ /// </summary>
+ /// <typeparam name="T">The data type to reuse</typeparam>
+ public class ObjectRental<T> : ObjectRental, IObjectRental<T>, ICacheHolder, IEnumerable<T> where T: class
+ {
+ /// <summary>
+ /// The initial data-structure capacity if quota is not defined
+ /// </summary>
+ public const int INITIAL_STRUCTURE_SIZE = 50;
+
+ protected readonly SemaphoreSlim StorageLock;
+ protected readonly Stack<T> Storage;
+ protected readonly HashSet<T> ContainsStore;
+
+ protected readonly Action<T>? ReturnAction;
+ protected readonly Action<T>? RentAction;
+ protected readonly Func<T> Constructor;
+ /// <summary>
+ /// Is the object type in the current store implement the Idisposable interface?
+ /// </summary>
+ protected readonly bool IsDisposableType;
+
+ /// <summary>
+ /// The maximum number of objects that will be cached.
+ /// Once this threshold has been reached, objects are
+ /// no longer stored
+ /// </summary>
+ protected readonly int QuotaLimit;
+
+#pragma warning disable CS8618 //Internal constructor does not set the constructor function
+ private ObjectRental(int quota)
+#pragma warning restore CS8618
+ {
+ //alloc new stack for rentals
+ Storage = new(Math.Max(quota, INITIAL_STRUCTURE_SIZE));
+ //Hashtable for quick lookups
+ ContainsStore = new(Math.Max(quota, INITIAL_STRUCTURE_SIZE));
+ //Semaphore slim to provide exclusive access
+ StorageLock = new SemaphoreSlim(1, 1);
+ //Store quota, if quota is -1, set to int-max to "disable quota"
+ QuotaLimit = quota == 0 ? int.MaxValue : quota;
+ //Determine if the type is disposeable and store a local value
+ IsDisposableType = typeof(IDisposable).IsAssignableFrom(typeof(T));
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="ObjectRental{T}"/> store with the rent/return callback methods
+ /// </summary>
+ /// <param name="constructor">The type initializer</param>
+ /// <param name="rentCb">The pre-retnal preperation action</param>
+ /// <param name="returnCb">The pre-return cleanup action</param>
+ /// <param name="quota">The maximum number of elements to cache in the store</param>
+ protected internal ObjectRental(Func<T> constructor, Action<T>? rentCb, Action<T>? returnCb, int quota) : this(quota)
+ {
+ this.RentAction = rentCb;
+ this.ReturnAction = returnCb;
+ this.Constructor = constructor;
+ }
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public virtual T Rent()
+ {
+ Check();
+ //See if we have an available object, if not return a new one by invoking the constructor function
+ T? rental = default;
+ //Get lock
+ using (SemSlimReleaser releader = StorageLock.GetReleaser())
+ {
+ //See if the store contains an item ready to use
+ if(Storage.TryPop(out T? item))
+ {
+ rental = item;
+ //Remove the item from the hash table
+ ContainsStore.Remove(item);
+ }
+ }
+ //If no object was removed from the store, create a new one
+ rental ??= Constructor();
+ //If rental cb is defined, invoke it
+ RentAction?.Invoke(rental);
+ return rental;
+ }
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public virtual void Return(T item)
+ {
+ Check();
+ //Invoke return callback if set
+ ReturnAction?.Invoke(item);
+ //Keeps track to know if the element was added or need to be cleaned up
+ bool wasAdded = false;
+ using (SemSlimReleaser releader = StorageLock.GetReleaser())
+ {
+ //Check quota limit
+ if (Storage.Count < QuotaLimit)
+ {
+ //Store item if it doesnt exist already
+ if (ContainsStore.Add(item))
+ {
+ //Store the object
+ Storage.Push(item);
+ }
+ //Set the was added flag
+ wasAdded = true;
+ }
+ }
+ if (!wasAdded && IsDisposableType)
+ {
+ //If the element was not added and is disposeable, we can dispose the element
+ (item as IDisposable)!.Dispose();
+ //Write debug message
+ Debug.WriteLine("Object rental disposed an object over quota");
+ }
+ }
+
+ /// <remarks>
+ /// NOTE: If <typeparamref name="T"/> implements <see cref="IDisposable"/>
+ /// interface, this method does nothing
+ /// </remarks>
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public virtual void CacheClear()
+ {
+ Check();
+ //If the type is disposeable, cleaning can be a long process, so defer to hard clear
+ if (IsDisposableType)
+ {
+ return;
+ }
+ //take the semaphore
+ using SemSlimReleaser releader = StorageLock.GetReleaser();
+ //Clear stores
+ ContainsStore.Clear();
+ Storage.Clear();
+ }
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public virtual void CacheHardClear()
+ {
+ Check();
+ //take the semaphore
+ using SemSlimReleaser releader = StorageLock.GetReleaser();
+ //If the type is disposable, dispose all elements before clearing storage
+ if (IsDisposableType)
+ {
+ //Dispose all elements
+ foreach (T element in Storage.ToArray())
+ {
+ (element as IDisposable)!.Dispose();
+ }
+ }
+ //Clear the storeage
+ Storage.Clear();
+ ContainsStore.Clear();
+ }
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ StorageLock.Dispose();
+ //If the element type is disposable, dispose all elements on a hard clear
+ if (IsDisposableType)
+ {
+ //Get all elements
+ foreach (T element in Storage.ToArray())
+ {
+ (element as IDisposable)!.Dispose();
+ }
+ }
+ }
+
+ ///<inheritdoc/>
+ public IEnumerator<T> GetEnumerator()
+ {
+ Check();
+ //Enter the semaphore
+ using SemSlimReleaser releader = StorageLock.GetReleaser();
+ foreach (T item in Storage)
+ {
+ yield return item;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ Check();
+ //Enter the semaphore
+ using SemSlimReleaser releader = StorageLock.GetReleaser();
+ foreach (T item in Storage)
+ {
+ yield return item;
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/Utils/src/Memory/Caching/ObjectRentalBase.cs b/Utils/src/Memory/Caching/ObjectRentalBase.cs
new file mode 100644
index 0000000..305d93f
--- /dev/null
+++ b/Utils/src/Memory/Caching/ObjectRentalBase.cs
@@ -0,0 +1,155 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ObjectRentalBase.cs
+*
+* ObjectRentalBase.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// Provides concurrent storage for reusable objects to be rented and returned. This class
+ /// and its members is thread-safe
+ /// </summary>
+ public abstract class ObjectRental : VnDisposeable
+ {
+ /// <summary>
+ /// Creates a new <see cref="ObjectRental{T}"/> store
+ /// </summary>
+ /// <param name="quota">The maximum number of elements that will be cached</param>
+ public static ObjectRental<TNew> Create<TNew>(int quota = 0) where TNew : class, new()
+ {
+ static TNew constructor() => new();
+ return new ObjectRental<TNew>(constructor, null, null, quota);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ObjectRental{T}"/> store with generic rental and return callback handlers
+ /// </summary>
+ /// <param name="rentCb">Function responsible for preparing an instance to be rented</param>
+ /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param>
+ /// <param name="quota">The maximum number of elements that will be cached</param>
+ public static ObjectRental<TNew> Create<TNew>(Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class, new()
+ {
+ static TNew constructor() => new();
+ return new ObjectRental<TNew>(constructor, rentCb, returnCb, quota);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ObjectRental{T}"/> store with a generic constructor function
+ /// </summary>
+ /// <param name="constructor">The function invoked to create a new instance when required</param>
+ /// <param name="quota">The maximum number of elements that will be cached</param>
+ /// <returns></returns>
+ public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, int quota = 0) where TNew: class
+ {
+ return new ObjectRental<TNew>(constructor, null, null, quota);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ObjectRental{T}"/> store with generic rental and return callback handlers
+ /// </summary>
+ /// <param name="constructor">The function invoked to create a new instance when required</param>
+ /// <param name="rentCb">Function responsible for preparing an instance to be rented</param>
+ /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param>
+ /// <param name="quota">The maximum number of elements that will be cached</param>
+ public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class
+ {
+ return new ObjectRental<TNew>(constructor, rentCb, returnCb, quota);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalObjectStorage{TNew}"/> store with generic rental and return callback handlers
+ /// </summary>
+ /// <typeparam name="TNew"></typeparam>
+ /// <param name="constructor">The function invoked to create a new instance when required</param>
+ /// <param name="rentCb">Function responsible for preparing an instance to be rented</param>
+ /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param>
+ /// <returns>The initialized store</returns>
+ public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class
+ {
+ return new ThreadLocalObjectStorage<TNew>(constructor, rentCb, returnCb);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store with generic rental and return callback handlers
+ /// </summary>
+ /// <param name="rentCb">Function responsible for preparing an instance to be rented</param>
+ /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param>
+ public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class, new()
+ {
+ static TNew constructor() => new();
+ return new ThreadLocalObjectStorage<TNew>(constructor, rentCb, returnCb);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store
+ /// </summary>
+ public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>() where TNew : class, new()
+ {
+ static TNew constructor() => new();
+ return new ThreadLocalObjectStorage<TNew>(constructor, null, null);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store with a generic constructor function
+ /// </summary>
+ /// <param name="constructor">The function invoked to create a new instance when required</param>
+ /// <returns></returns>
+ public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor) where TNew : class
+ {
+ return new ThreadLocalObjectStorage<TNew>(constructor, null, null);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="ReusableStore{T}"/> instance with a parameterless constructor
+ /// </summary>
+ /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam>
+ /// <param name="quota">The maximum number of elements that will be cached</param>
+ /// <returns></returns>
+ public static ReusableStore<T> CreateReusable<T>(int quota = 0) where T : class, IReusable, new()
+ {
+ static T constructor() => new();
+ return new(constructor, quota);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ReusableStore{T}"/> instance with the specified constructor
+ /// </summary>
+ /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam>
+ /// <param name="constructor">The constructor function invoked to create new instances of the <see cref="IReusable"/> type</param>
+ /// <param name="quota">The maximum number of elements that will be cached</param>
+ /// <returns></returns>
+ public static ReusableStore<T> CreateReusable<T>(Func<T> constructor, int quota = 0) where T : class, IReusable => new(constructor, quota);
+
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance with a parameterless constructor
+ /// </summary>
+ /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam>
+ /// <returns></returns>
+ public static ThreadLocalReusableStore<T> CreateThreadLocalReusable<T>() where T : class, IReusable, new()
+ {
+ static T constructor() => new();
+ return new(constructor);
+ }
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance with the specified constructor
+ /// </summary>
+ /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam>
+ /// <param name="constructor">The constructor function invoked to create new instances of the <see cref="IReusable"/> type</param>
+ /// <returns></returns>
+ public static ThreadLocalReusableStore<T> CreateThreadLocalReusable<T>(Func<T> constructor) where T : class, IReusable => new(constructor);
+ }
+}
diff --git a/Utils/src/Memory/Caching/ReusableStore.cs b/Utils/src/Memory/Caching/ReusableStore.cs
new file mode 100644
index 0000000..aacd012
--- /dev/null
+++ b/Utils/src/Memory/Caching/ReusableStore.cs
@@ -0,0 +1,61 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ReusableStore.cs
+*
+* ReusableStore.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory.Caching
+{
+
+ /// <summary>
+ /// A reusable object store that extends <see cref="ObjectRental{T}"/>, that allows for objects to be reused heavily
+ /// </summary>
+ /// <typeparam name="T">A reusable object</typeparam>
+ public class ReusableStore<T> : ObjectRental<T> where T : class, IReusable
+ {
+ internal ReusableStore(Func<T> constructor, int quota) :base(constructor, null, null, quota)
+ {}
+ ///<inheritdoc/>
+ public override T Rent()
+ {
+ //Rent the object (or create it)
+ T rental = base.Rent();
+ //Invoke prepare function
+ rental.Prepare();
+ //return object
+ return rental;
+ }
+ ///<inheritdoc/>
+ public override void Return(T item)
+ {
+ /*
+ * Clean up the item by invoking the cleanup function,
+ * and only return the item for reuse if the caller allows
+ */
+ if (item.Release())
+ {
+ base.Return(item);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs b/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs
new file mode 100644
index 0000000..511af24
--- /dev/null
+++ b/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs
@@ -0,0 +1,76 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ThreadLocalObjectStorage.cs
+*
+* ThreadLocalObjectStorage.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// Derrives from <see cref="ObjectRental{T}"/> to provide object rental syntax for <see cref="ThreadLocal{T}"/>
+ /// storage
+ /// </summary>
+ /// <typeparam name="T">The data type to store</typeparam>
+ public class ThreadLocalObjectStorage<T> : ObjectRental<T> where T: class
+ {
+ protected ThreadLocal<T> Store { get; }
+
+ internal ThreadLocalObjectStorage(Func<T> constructor, Action<T>? rentCb, Action<T>? returnCb)
+ :base(constructor, rentCb, returnCb, 0)
+ {
+ Store = new(Constructor);
+ }
+
+ /// <summary>
+ /// "Rents" or creates an object for the current thread
+ /// </summary>
+ /// <returns>The new or stored instanced</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public override T Rent()
+ {
+ Check();
+ //Get the tlocal value
+ T value = Store.Value!;
+ //Invoke the rent action if set
+ base.RentAction?.Invoke(value);
+ return value;
+ }
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public override void Return(T item)
+ {
+ Check();
+ //Invoke the rent action
+ base.ReturnAction?.Invoke(item);
+ }
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ Store.Dispose();
+ base.Free();
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs b/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs
new file mode 100644
index 0000000..83cd4d6
--- /dev/null
+++ b/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs
@@ -0,0 +1,64 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ThreadLocalReusableStore.cs
+*
+* ThreadLocalReusableStore.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory.Caching
+{
+ /// <summary>
+ /// A reusable object store that extends <see cref="ThreadLocalObjectStorage{T}"/>, that allows for objects to be reused heavily
+ /// in a thread-local cache
+ /// </summary>
+ /// <typeparam name="T">A reusable object</typeparam>
+ public class ThreadLocalReusableStore<T> : ThreadLocalObjectStorage<T> where T: class, IReusable
+ {
+ /// <summary>
+ /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance
+ /// </summary>
+ internal ThreadLocalReusableStore(Func<T> constructor):base(constructor, null, null)
+ { }
+ ///<inheritdoc/>
+ public override T Rent()
+ {
+ //Rent the object (or create it)
+ T rental = base.Rent();
+ //Invoke prepare function
+ rental.Prepare();
+ //return object
+ return rental;
+ }
+ ///<inheritdoc/>
+ public override void Return(T item)
+ {
+ /*
+ * Clean up the item by invoking the cleanup function,
+ * and only return the item for reuse if the caller allows
+ */
+ if (item.Release())
+ {
+ base.Return(item);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/ForwardOnlyBufferWriter.cs b/Utils/src/Memory/ForwardOnlyBufferWriter.cs
new file mode 100644
index 0000000..0ea507e
--- /dev/null
+++ b/Utils/src/Memory/ForwardOnlyBufferWriter.cs
@@ -0,0 +1,122 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ForwardOnlyBufferWriter.cs
+*
+* ForwardOnlyBufferWriter.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a stack based buffer writer
+ /// </summary>
+ public ref struct ForwardOnlyWriter<T>
+ {
+ /// <summary>
+ /// The buffer for writing output data to
+ /// </summary>
+ public readonly Span<T> Buffer { get; }
+ /// <summary>
+ /// The number of characters written to the buffer
+ /// </summary>
+ public int Written { readonly get; set; }
+ /// <summary>
+ /// The number of characters remaining in the buffer
+ /// </summary>
+ public readonly int RemainingSize => Buffer.Length - Written;
+
+ /// <summary>
+ /// The remaining buffer window
+ /// </summary>
+ public readonly Span<T> Remaining => Buffer[Written..];
+
+ /// <summary>
+ /// Creates a new <see cref="ForwardOnlyWriter{T}"/> assigning the specified buffer
+ /// </summary>
+ /// <param name="buffer">The buffer to write data to</param>
+ public ForwardOnlyWriter(in Span<T> buffer)
+ {
+ Buffer = buffer;
+ Written = 0;
+ }
+
+ /// <summary>
+ /// Returns a compiled string from the characters written to the buffer
+ /// </summary>
+ /// <returns>A string of the characters written to the buffer</returns>
+ public readonly override string ToString() => Buffer[..Written].ToString();
+
+ /// <summary>
+ /// Appends a sequence to the buffer
+ /// </summary>
+ /// <param name="data">The data to append to the buffer</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Append(ReadOnlySpan<T> data)
+ {
+ //Make sure the current window is large enough to buffer the new string
+ if (data.Length > RemainingSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Remaining) ,"The internal buffer does not have enough buffer space");
+ }
+ Span<T> window = Buffer[Written..];
+ //write data to window
+ data.CopyTo(window);
+ //update char position
+ Written += data.Length;
+ }
+ /// <summary>
+ /// Appends a single item to the buffer
+ /// </summary>
+ /// <param name="c">The item to append to the buffer</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Append(T c)
+ {
+ //Make sure the current window is large enough to buffer the new string
+ if (RemainingSize == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space");
+ }
+ //Write data to buffer and increment the buffer position
+ Buffer[Written++] = c;
+ }
+
+ /// <summary>
+ /// Advances the writer forward the specifed number of elements
+ /// </summary>
+ /// <param name="count">The number of elements to advance the writer by</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Advance(int count)
+ {
+ if (count > RemainingSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space");
+ }
+ Written += count;
+ }
+
+ /// <summary>
+ /// Resets the writer by setting the <see cref="Written"/>
+ /// property to 0.
+ /// </summary>
+ public void Reset() => Written = 0;
+ }
+}
diff --git a/Utils/src/Memory/ForwardOnlyMemoryReader.cs b/Utils/src/Memory/ForwardOnlyMemoryReader.cs
new file mode 100644
index 0000000..c850b14
--- /dev/null
+++ b/Utils/src/Memory/ForwardOnlyMemoryReader.cs
@@ -0,0 +1,74 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ForwardOnlyMemoryReader.cs
+*
+* ForwardOnlyMemoryReader.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// A mutable structure used to implement a simple foward only
+ /// reader for a memory segment
+ /// </summary>
+ /// <typeparam name="T">The element type</typeparam>
+ public struct ForwardOnlyMemoryReader<T>
+ {
+ private readonly ReadOnlyMemory<T> _segment;
+ private readonly int _size;
+
+ private int _position;
+
+ /// <summary>
+ /// Initializes a new <see cref="FordwardOnlyMemoryReader{T}"/>
+ /// of the specified type using the specified internal buffer
+ /// </summary>
+ /// <param name="buffer">The buffer to read from</param>
+ public ForwardOnlyMemoryReader(in ReadOnlyMemory<T> buffer)
+ {
+ _segment = buffer;
+ _size = buffer.Length;
+ _position = 0;
+ }
+
+ /// <summary>
+ /// The remaining data window
+ /// </summary>
+ public readonly ReadOnlyMemory<T> Window => _segment[_position..];
+ /// <summary>
+ /// The number of elements remaining in the window
+ /// </summary>
+ public readonly int WindowSize => _size - _position;
+
+
+ /// <summary>
+ /// Advances the window position the specified number of elements
+ /// </summary>
+ /// <param name="count">The number of elements to advance the widnow position</param>
+ public void Advance(int count) => _position += count;
+
+ /// <summary>
+ /// Resets the sliding window to the begining of the buffer
+ /// </summary>
+ public void Reset() => _position = 0;
+ }
+}
diff --git a/Utils/src/Memory/ForwardOnlyMemoryWriter.cs b/Utils/src/Memory/ForwardOnlyMemoryWriter.cs
new file mode 100644
index 0000000..4f5286d
--- /dev/null
+++ b/Utils/src/Memory/ForwardOnlyMemoryWriter.cs
@@ -0,0 +1,122 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ForwardOnlyMemoryWriter.cs
+*
+* ForwardOnlyMemoryWriter.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a mutable sliding buffer writer
+ /// </summary>
+ public struct ForwardOnlyMemoryWriter<T>
+ {
+ /// <summary>
+ /// The buffer for writing output data to
+ /// </summary>
+ public readonly Memory<T> Buffer { get; }
+ /// <summary>
+ /// The number of characters written to the buffer
+ /// </summary>
+ public int Written { readonly get; set; }
+ /// <summary>
+ /// The number of characters remaining in the buffer
+ /// </summary>
+ public readonly int RemainingSize => Buffer.Length - Written;
+
+ /// <summary>
+ /// The remaining buffer window
+ /// </summary>
+ public readonly Memory<T> Remaining => Buffer[Written..];
+
+ /// <summary>
+ /// Creates a new <see cref="ForwardOnlyWriter{T}"/> assigning the specified buffer
+ /// </summary>
+ /// <param name="buffer">The buffer to write data to</param>
+ public ForwardOnlyMemoryWriter(in Memory<T> buffer)
+ {
+ Buffer = buffer;
+ Written = 0;
+ }
+
+ /// <summary>
+ /// Returns a compiled string from the characters written to the buffer
+ /// </summary>
+ /// <returns>A string of the characters written to the buffer</returns>
+ public readonly override string ToString() => Buffer[..Written].ToString();
+
+ /// <summary>
+ /// Appends a sequence to the buffer
+ /// </summary>
+ /// <param name="data">The data to append to the buffer</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Append(ReadOnlyMemory<T> data)
+ {
+ //Make sure the current window is large enough to buffer the new string
+ if (data.Length > RemainingSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space");
+ }
+ Memory<T> window = Buffer[Written..];
+ //write data to window
+ data.CopyTo(window);
+ //update char position
+ Written += data.Length;
+ }
+ /// <summary>
+ /// Appends a single item to the buffer
+ /// </summary>
+ /// <param name="c">The item to append to the buffer</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Append(T c)
+ {
+ //Make sure the current window is large enough to buffer the new string
+ if (RemainingSize == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space");
+ }
+ //Write data to buffer and increment the buffer position
+ Buffer.Span[Written++] = c;
+ }
+
+ /// <summary>
+ /// Advances the writer forward the specifed number of elements
+ /// </summary>
+ /// <param name="count">The number of elements to advance the writer by</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Advance(int count)
+ {
+ if (count > RemainingSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot advance past the end of the buffer");
+ }
+ Written += count;
+ }
+
+ /// <summary>
+ /// Resets the writer by setting the <see cref="Written"/>
+ /// property to 0.
+ /// </summary>
+ public void Reset() => Written = 0;
+ }
+}
diff --git a/Utils/src/Memory/ForwardOnlyReader.cs b/Utils/src/Memory/ForwardOnlyReader.cs
new file mode 100644
index 0000000..aa268c4
--- /dev/null
+++ b/Utils/src/Memory/ForwardOnlyReader.cs
@@ -0,0 +1,74 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ForwardOnlyReader.cs
+*
+* ForwardOnlyReader.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// A mutable structure used to implement a simple foward only
+ /// reader for a memory segment
+ /// </summary>
+ /// <typeparam name="T">The element type</typeparam>
+ public ref struct ForwardOnlyReader<T>
+ {
+ private readonly ReadOnlySpan<T> _segment;
+ private readonly int _size;
+
+ private int _position;
+
+ /// <summary>
+ /// Initializes a new <see cref="FordwardOnlyReader{T}"/>
+ /// of the specified type using the specified internal buffer
+ /// </summary>
+ /// <param name="buffer">The buffer to read from</param>
+ public ForwardOnlyReader(in ReadOnlySpan<T> buffer)
+ {
+ _segment = buffer;
+ _size = buffer.Length;
+ _position = 0;
+ }
+
+ /// <summary>
+ /// The remaining data window
+ /// </summary>
+ public readonly ReadOnlySpan <T> Window => _segment[_position..];
+
+ /// <summary>
+ /// The number of elements remaining in the window
+ /// </summary>
+ public readonly int WindowSize => _size - _position;
+
+ /// <summary>
+ /// Advances the window position the specified number of elements
+ /// </summary>
+ /// <param name="count">The number of elements to advance the widnow position</param>
+ public void Advance(int count) => _position += count;
+
+ /// <summary>
+ /// Resets the sliding window to the begining of the buffer
+ /// </summary>
+ public void Reset() => _position = 0;
+ }
+}
diff --git a/Utils/src/Memory/IMemoryHandle.cs b/Utils/src/Memory/IMemoryHandle.cs
new file mode 100644
index 0000000..75d1cce
--- /dev/null
+++ b/Utils/src/Memory/IMemoryHandle.cs
@@ -0,0 +1,53 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IMemoryHandle.cs
+*
+* IMemoryHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Represents a handle for safe access to memory managed/unamanged memory
+ /// </summary>
+ /// <typeparam name="T">The type this handle represents</typeparam>
+ public interface IMemoryHandle<T> : IDisposable, IPinnable
+ {
+ /// <summary>
+ /// The size of the block as an integer
+ /// </summary>
+ /// <exception cref="OverflowException"></exception>
+ int IntLength { get; }
+
+ /// <summary>
+ /// The number of elements in the block
+ /// </summary>
+ ulong Length { get; }
+
+ /// <summary>
+ /// Gets the internal block as a span
+ /// </summary>
+ Span<T> Span { get; }
+ }
+
+}
diff --git a/Utils/src/Memory/IStringSerializeable.cs b/Utils/src/Memory/IStringSerializeable.cs
new file mode 100644
index 0000000..12cfe52
--- /dev/null
+++ b/Utils/src/Memory/IStringSerializeable.cs
@@ -0,0 +1,55 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IStringSerializeable.cs
+*
+* IStringSerializeable.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// A interface that provides indempodent abstractions for compiling an instance
+ /// to its representitive string.
+ /// </summary>
+ public interface IStringSerializeable
+ {
+ /// <summary>
+ /// Compiles the current instance into its safe string representation
+ /// </summary>
+ /// <returns>A string of the desired representation of the current instance</returns>
+ string Compile();
+ /// <summary>
+ /// Compiles the current instance into its safe string representation, and writes its
+ /// contents to the specified buffer writer
+ /// </summary>
+ /// <param name="writer">The ouput writer to write the serialized representation to</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ void Compile(ref ForwardOnlyWriter<char> writer);
+ /// <summary>
+ /// Compiles the current instance into its safe string representation, and writes its
+ /// contents to the specified buffer writer
+ /// </summary>
+ /// <param name="buffer">The buffer to write the serialized representation to</param>
+ /// <returns>The number of characters written to the buffer</returns>
+ ERRNO Compile(in Span<char> buffer);
+ }
+}
diff --git a/Utils/src/Memory/IUnmangedHeap.cs b/Utils/src/Memory/IUnmangedHeap.cs
new file mode 100644
index 0000000..5d8f4bf
--- /dev/null
+++ b/Utils/src/Memory/IUnmangedHeap.cs
@@ -0,0 +1,59 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IUnmangedHeap.cs
+*
+* IUnmangedHeap.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Abstraction for handling (allocating, resizing, and freeing) blocks of unmanaged memory from an unmanged heap
+ /// </summary>
+ public interface IUnmangedHeap : IDisposable
+ {
+ /// <summary>
+ /// Allocates a block of memory from the heap and returns a pointer to the new memory block
+ /// </summary>
+ /// <param name="size">The size (in bytes) of the element</param>
+ /// <param name="elements">The number of elements to allocate</param>
+ /// <param name="zero">An optional parameter to zero the block of memory</param>
+ /// <returns></returns>
+ IntPtr Alloc(UInt64 elements, UInt64 size, bool zero);
+
+ /// <summary>
+ /// Resizes the allocated block of memory to the new size
+ /// </summary>
+ /// <param name="block">The block to resize</param>
+ /// <param name="elements">The new number of elements</param>
+ /// <param name="size">The size (in bytes) of the type</param>
+ /// <param name="zero">An optional parameter to zero the block of memory</param>
+ void Resize(ref IntPtr block, UInt64 elements, UInt64 size, bool zero);
+
+ /// <summary>
+ /// Free's a previously allocated block of memory
+ /// </summary>
+ /// <param name="block">The memory to be freed</param>
+ /// <returns>A value indicating if the free operation succeeded</returns>
+ bool Free(ref IntPtr block);
+ }
+}
diff --git a/Utils/src/Memory/Memory.cs b/Utils/src/Memory/Memory.cs
new file mode 100644
index 0000000..822d98c
--- /dev/null
+++ b/Utils/src/Memory/Memory.cs
@@ -0,0 +1,456 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: Memory.cs
+*
+* Memory.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Security;
+using System.Threading;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides optimized cross-platform maanged/umanaged safe/unsafe memory operations
+ /// </summary>
+ [SecurityCritical]
+ [ComVisible(false)]
+ public unsafe static class Memory
+ {
+ public const string SHARED_HEAP_TYPE_ENV= "VNLIB_SHARED_HEAP_TYPE";
+ public const string SHARED_HEAP_INTIAL_SIZE_ENV = "VNLIB_SHARED_HEAP_SIZE";
+
+ /// <summary>
+ /// Initial shared heap size (bytes)
+ /// </summary>
+ public const ulong SHARED_HEAP_INIT_SIZE = 20971520;
+
+ public const int MAX_BUF_SIZE = 2097152;
+ public const int MIN_BUF_SIZE = 16000;
+
+ /// <summary>
+ /// The maximum buffer size requested by <see cref="UnsafeAlloc{T}(int, bool)"/>
+ /// that will use the array pool before falling back to the <see cref="Shared"/>.
+ /// heap.
+ /// </summary>
+ public const int MAX_UNSAFE_POOL_SIZE = 500 * 1024;
+
+ /// <summary>
+ /// Provides a shared heap instance for the process to allocate memory from.
+ /// </summary>
+ /// <remarks>
+ /// The backing heap
+ /// is determined by the OS type and process environment varibles.
+ /// </remarks>
+ public static IUnmangedHeap Shared => _sharedHeap.Value;
+
+ private static readonly Lazy<IUnmangedHeap> _sharedHeap;
+
+ static Memory()
+ {
+ _sharedHeap = new Lazy<IUnmangedHeap>(() => InitHeapInternal(true), LazyThreadSafetyMode.PublicationOnly);
+ //Cleanup the heap on process exit
+ AppDomain.CurrentDomain.DomainUnload += DomainUnloaded;
+ }
+
+ private static void DomainUnloaded(object sender, EventArgs e)
+ {
+ //Dispose the heap if allocated
+ if (_sharedHeap.IsValueCreated)
+ {
+ _sharedHeap.Value.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="IUnmangedHeap"/> determined by compilation/runtime flags
+ /// and operating system type for the current proccess.
+ /// </summary>
+ /// <returns>An <see cref="IUnmangedHeap"/> for the current process</returns>
+ /// <exception cref="SystemException"></exception>
+ /// <exception cref="DllNotFoundException"></exception>
+ public static IUnmangedHeap InitializeNewHeapForProcess() => InitHeapInternal(false);
+
+ private static IUnmangedHeap InitHeapInternal(bool isShared)
+ {
+ bool IsWindows = OperatingSystem.IsWindows();
+ //Get environment varable
+ string heapType = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV);
+ //Get inital size
+ string sharedSize = Environment.GetEnvironmentVariable(SHARED_HEAP_INTIAL_SIZE_ENV);
+ //Try to parse the shared size from the env
+ if (!ulong.TryParse(sharedSize, out ulong defaultSize))
+ {
+ defaultSize = SHARED_HEAP_INIT_SIZE;
+ }
+ //Gen the private heap from its type or default
+ switch (heapType)
+ {
+ case "win32":
+ if (!IsWindows)
+ {
+ throw new PlatformNotSupportedException("Win32 private heaps are not supported on non-windows platforms");
+ }
+ return PrivateHeap.Create(defaultSize);
+ case "rpmalloc":
+ //If the shared heap is being allocated, then return a lock free global heap
+ return isShared ? RpMallocPrivateHeap.GlobalHeap : new RpMallocPrivateHeap(false);
+ default:
+ return IsWindows ? PrivateHeap.Create(defaultSize) : new ProcessHeap();
+ }
+ }
+
+ /// <summary>
+ /// Gets a value that indicates if the Rpmalloc native library is loaded
+ /// </summary>
+ public static bool IsRpMallocLoaded { get; } = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV) == "rpmalloc";
+
+ #region Zero
+ /// <summary>
+ /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function
+ /// </summary>
+ /// <typeparam name="T">Unmanged datatype</typeparam>
+ /// <param name="block">Block of memory to be cleared</param>
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static void UnsafeZeroMemory<T>(ReadOnlySpan<T> block) where T : unmanaged
+ {
+ if (!block.IsEmpty)
+ {
+ checked
+ {
+ fixed (void* ptr = &MemoryMarshal.GetReference(block))
+ {
+ //Calls memset
+ Unsafe.InitBlock(ptr, 0, (uint)(block.Length * sizeof(T)));
+ }
+ }
+ }
+ }
+ /// <summary>
+ /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function
+ /// </summary>
+ /// <typeparam name="T">Unmanged datatype</typeparam>
+ /// <param name="block">Block of memory to be cleared</param>
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static void UnsafeZeroMemory<T>(ReadOnlyMemory<T> block) where T : unmanaged
+ {
+ if (!block.IsEmpty)
+ {
+ checked
+ {
+ //Pin memory and get pointer
+ using MemoryHandle handle = block.Pin();
+ //Calls memset
+ Unsafe.InitBlock(handle.Pointer, 0, (uint)(block.Length * sizeof(T)));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Initializes a block of memory with zeros
+ /// </summary>
+ /// <typeparam name="T">The unmanaged</typeparam>
+ /// <param name="block">The block of memory to initialize</param>
+ public static void InitializeBlock<T>(Span<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block);
+ /// <summary>
+ /// Initializes a block of memory with zeros
+ /// </summary>
+ /// <typeparam name="T">The unmanaged</typeparam>
+ /// <param name="block">The block of memory to initialize</param>
+ public static void InitializeBlock<T>(Memory<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block);
+
+ /// <summary>
+ /// Zeroes a block of memory pointing to the structure
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="block">The pointer to the allocated structure</param>
+ public static void ZeroStruct<T>(IntPtr block)
+ {
+ //get thes size of the structure
+ int size = Unsafe.SizeOf<T>();
+ //Zero block
+ Unsafe.InitBlock(block.ToPointer(), 0, (uint)size);
+ }
+ /// <summary>
+ /// Zeroes a block of memory pointing to the structure
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="structPtr">The pointer to the allocated structure</param>
+ public static void ZeroStruct<T>(void* structPtr) where T: unmanaged
+ {
+ //get thes size of the structure
+ int size = Unsafe.SizeOf<T>();
+ //Zero block
+ Unsafe.InitBlock(structPtr, 0, (uint)size);
+ }
+ /// <summary>
+ /// Zeroes a block of memory pointing to the structure
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="structPtr">The pointer to the allocated structure</param>
+ public static void ZeroStruct<T>(T* structPtr) where T : unmanaged
+ {
+ //get thes size of the structure
+ int size = Unsafe.SizeOf<T>();
+ //Zero block
+ Unsafe.InitBlock(structPtr, 0, (uint)size);
+ }
+
+ #endregion
+
+ #region Copy
+ /// <summary>
+ /// Copies data from source memory to destination memory of an umanged data type
+ /// </summary>
+ /// <typeparam name="T">Unmanged type</typeparam>
+ /// <param name="source">Source data <see cref="ReadOnlySpan{T}"/></param>
+ /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param>
+ /// <param name="destOffset">Dest offset</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Copy<T>(ReadOnlySpan<T> source, MemoryHandle<T> dest, Int64 destOffset) where T : unmanaged
+ {
+ if (source.IsEmpty)
+ {
+ return;
+ }
+ if (dest.Length < (ulong)(destOffset + source.Length))
+ {
+ throw new ArgumentException("Source data is larger than the dest data block", nameof(source));
+ }
+ //Get long offset from the destination handle
+ T* offset = dest.GetOffset(destOffset);
+ fixed(void* src = &MemoryMarshal.GetReference(source))
+ {
+ int byteCount = checked(source.Length * sizeof(T));
+ Unsafe.CopyBlock(offset, src, (uint)byteCount);
+ }
+ }
+ /// <summary>
+ /// Copies data from source memory to destination memory of an umanged data type
+ /// </summary>
+ /// <typeparam name="T">Unmanged type</typeparam>
+ /// <param name="source">Source data <see cref="ReadOnlyMemory{T}"/></param>
+ /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param>
+ /// <param name="destOffset">Dest offset</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Copy<T>(ReadOnlyMemory<T> source, MemoryHandle<T> dest, Int64 destOffset) where T : unmanaged
+ {
+ if (source.IsEmpty)
+ {
+ return;
+ }
+ if (dest.Length < (ulong)(destOffset + source.Length))
+ {
+ throw new ArgumentException("Dest constraints are larger than the dest data block", nameof(source));
+ }
+ //Get long offset from the destination handle
+ T* offset = dest.GetOffset(destOffset);
+ //Pin the source memory
+ using MemoryHandle srcHandle = source.Pin();
+ int byteCount = checked(source.Length * sizeof(T));
+ //Copy block using unsafe class
+ Unsafe.CopyBlock(offset, srcHandle.Pointer, (uint)byteCount);
+ }
+ /// <summary>
+ /// Copies data from source memory to destination memory of an umanged data type
+ /// </summary>
+ /// <typeparam name="T">Unmanged type</typeparam>
+ /// <param name="source">Source data <see cref="MemoryHandle{T}"/></param>
+ /// <param name="sourceOffset">Number of elements to offset source data</param>
+ /// <param name="dest">Destination <see cref="Span{T}"/></param>
+ /// <param name="destOffset">Dest offset</param>
+ /// <param name="count">Number of elements to copy</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Copy<T>(MemoryHandle<T> source, Int64 sourceOffset, Span<T> dest, int destOffset, int count) where T : unmanaged
+ {
+ if (count <= 0)
+ {
+ return;
+ }
+ if (source.Length < (ulong)(sourceOffset + count))
+ {
+ throw new ArgumentException("Source constraints are larger than the source data block", nameof(count));
+ }
+ if (dest.Length < destOffset + count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(destOffset), "Destination offset range cannot exceed the size of the destination buffer");
+ }
+ //Get offset to allow large blocks of memory
+ T* src = source.GetOffset(sourceOffset);
+ fixed(T* dst = &MemoryMarshal.GetReference(dest))
+ {
+ //Cacl offset
+ T* dstoffset = dst + destOffset;
+ int byteCount = checked(count * sizeof(T));
+ //Aligned copy
+ Unsafe.CopyBlock(dstoffset, src, (uint)byteCount);
+ }
+ }
+ /// <summary>
+ /// Copies data from source memory to destination memory of an umanged data type
+ /// </summary>
+ /// <typeparam name="T">Unmanged type</typeparam>
+ /// <param name="source">Source data <see cref="MemoryHandle{T}"/></param>
+ /// <param name="sourceOffset">Number of elements to offset source data</param>
+ /// <param name="dest">Destination <see cref="Memory{T}"/></param>
+ /// <param name="destOffset">Dest offset</param>
+ /// <param name="count">Number of elements to copy</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Copy<T>(MemoryHandle<T> source, Int64 sourceOffset, Memory<T> dest, int destOffset, int count) where T : unmanaged
+ {
+ if (count == 0)
+ {
+ return;
+ }
+ if (source.Length < (ulong)(sourceOffset + count))
+ {
+ throw new ArgumentException("Source constraints are larger than the source data block", nameof(count));
+ }
+ if(dest.Length < destOffset + count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(destOffset), "Destination offset range cannot exceed the size of the destination buffer");
+ }
+ //Get offset to allow large blocks of memory
+ T* src = source.GetOffset(sourceOffset);
+ //Pin the memory handle
+ using MemoryHandle handle = dest.Pin();
+ //Byte count
+ int byteCount = checked(count * sizeof(T));
+ //Dest offset
+ T* dst = ((T*)handle.Pointer) + destOffset;
+ //Aligned copy
+ Unsafe.CopyBlock(dst, src, (uint)byteCount);
+ }
+ #endregion
+
+ #region Streams
+ /// <summary>
+ /// Copies data from one stream to another in specified blocks
+ /// </summary>
+ /// <param name="source">Source memory</param>
+ /// <param name="srcOffset">Source offset</param>
+ /// <param name="dest">Destination memory</param>
+ /// <param name="destOffst">Destination offset</param>
+ /// <param name="count">Number of elements to copy</param>
+ public static void Copy(Stream source, Int64 srcOffset, Stream dest, Int64 destOffst, Int64 count)
+ {
+ if (count == 0)
+ {
+ return;
+ }
+ if (count < 0)
+ {
+ throw new ArgumentException("Count must be a positive integer", nameof(count));
+ }
+ //Seek streams
+ _ = source.Seek(srcOffset, SeekOrigin.Begin);
+ _ = dest.Seek(destOffst, SeekOrigin.Begin);
+ //Create new buffer
+ using IMemoryHandle<byte> buffer = Shared.Alloc<byte>(count);
+ Span<byte> buf = buffer.Span;
+ int total = 0;
+ do
+ {
+ //read from source
+ int read = source.Read(buf);
+ //guard
+ if (read == 0)
+ {
+ break;
+ }
+ //write read slice to dest
+ dest.Write(buf[..read]);
+ //update total read
+ total += read;
+ } while (total < count);
+ }
+ #endregion
+
+ #region alloc
+
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
+ {
+ if (elements < 0)
+ {
+ throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
+ }
+ if(elements > MAX_UNSAFE_POOL_SIZE || IsRpMallocLoaded)
+ {
+ // Alloc from heap
+ IntPtr block = Shared.Alloc((uint)elements, (uint)sizeof(T), zero);
+ //Init new handle
+ return new(Shared, block, elements);
+ }
+ else
+ {
+ return new(ArrayPool<T>.Shared, elements, zero);
+ }
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T: unmanaged
+ {
+ if (elements < 0)
+ {
+ throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
+ }
+
+ //If the element count is larger than max pool size, alloc from shared heap
+ if (elements > MAX_UNSAFE_POOL_SIZE)
+ {
+ //Alloc from shared heap
+ return Shared.Alloc<T>(elements, zero);
+ }
+ else
+ {
+ //Get temp buffer from shared buffer pool
+ return new VnTempBuffer<T>(elements, zero);
+ }
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/MemoryHandle.cs b/Utils/src/Memory/MemoryHandle.cs
new file mode 100644
index 0000000..a09edea
--- /dev/null
+++ b/Utils/src/Memory/MemoryHandle.cs
@@ -0,0 +1,237 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: MemoryHandle.cs
+*
+* MemoryHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using Microsoft.Win32.SafeHandles;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a wrapper for using umanged memory handles from an assigned <see cref="PrivateHeap"/> for <see cref="UnmanagedType"/> types
+ /// </summary>
+ /// <remarks>
+ /// Handles are configured to address blocks larger than 2GB,
+ /// so some properties may raise exceptions if large blocks are used.
+ /// </remarks>
+ public sealed class MemoryHandle<T> : SafeHandleZeroOrMinusOneIsInvalid, IMemoryHandle<T>, IEquatable<MemoryHandle<T>> where T : unmanaged
+ {
+ /// <summary>
+ /// New <typeparamref name="T"/>* pointing to the base of the allocated block
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public unsafe T* Base
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => GetOffset(0);
+ }
+ /// <summary>
+ /// New <see cref="IntPtr"/> pointing to the base of the allocated block
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public unsafe IntPtr BasePtr
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (IntPtr)GetOffset(0);
+ }
+ /// <summary>
+ /// Gets a span over the entire allocated block
+ /// </summary>
+ /// <returns>A <see cref="Span{T}"/> over the internal data</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ public unsafe Span<T> Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ this.ThrowIfClosed();
+ return _length == 0 ? Span<T>.Empty : new Span<T>(Base, IntLength);
+ }
+ }
+
+ private readonly bool ZeroMemory;
+ private readonly IUnmangedHeap Heap;
+ private ulong _length;
+
+ /// <summary>
+ /// Number of elements allocated to the current instance
+ /// </summary>
+ public ulong Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+ /// <summary>
+ /// Number of elements in the memory block casted to an integer
+ /// </summary>
+ /// <exception cref="OverflowException"></exception>
+ public int IntLength
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => checked((int)_length);
+ }
+
+ /// <summary>
+ /// Number of bytes allocated to the current instance
+ /// </summary>
+ /// <exception cref="OverflowException"></exception>
+ public unsafe ulong ByteLength
+ {
+ //Check for overflows when converting to bytes (should run out of memory before this is an issue, but just incase)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => checked(_length * (UInt64)sizeof(T));
+ }
+
+ /// <summary>
+ /// Creates a new memory handle, for which is holds ownership, and allocates the number of elements specified on the heap.
+ /// </summary>
+ /// <param name="heap">The heap to allocate/deallocate memory from</param>
+ /// <param name="elements">Number of elements to allocate</param>
+ /// <param name="zero">Zero all memory during allocations from heap</param>
+ /// <param name="initial">The initial block of allocated memory to wrap</param>
+ internal MemoryHandle(IUnmangedHeap heap, IntPtr initial, ulong elements, bool zero) : base(true)
+ {
+ //Set element size (always allocate at least 1 object)
+ _length = elements;
+ ZeroMemory = zero;
+ //assign heap ref
+ Heap = heap;
+ handle = initial;
+ }
+
+ /// <summary>
+ /// Resizes the current handle on the heap
+ /// </summary>
+ /// <param name="elements">Positive number of elemnts the current handle should referrence</param>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public unsafe void Resize(ulong elements)
+ {
+ this.ThrowIfClosed();
+ //Update size (should never be less than inital size)
+ _length = elements;
+ //Re-alloc (Zero if required)
+ try
+ {
+ Heap.Resize(ref handle, Length, (ulong)sizeof(T), ZeroMemory);
+ }
+ //Catch the disposed exception so we can invalidate the current ptr
+ catch (ObjectDisposedException)
+ {
+ base.handle = IntPtr.Zero;
+ //Set as invalid so release does not get called
+ base.SetHandleAsInvalid();
+ //Propagate the exception
+ throw;
+ }
+ }
+ /// <summary>
+ /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks
+ /// </summary>
+ /// <param name="elements">Number of elements of type to offset</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <returns><typeparamref name="T"/> pointer to the memory offset specified</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe T* GetOffset(ulong elements)
+ {
+ if (elements >= _length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elements), "Element offset cannot be larger than allocated size");
+ }
+ this.ThrowIfClosed();
+ //Get ptr and offset it
+ T* bs = ((T*)handle) + elements;
+ return bs;
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ ///<exception cref="ArgumentOutOfRangeException"></exception>
+ public unsafe MemoryHandle Pin(int elementIndex)
+ {
+ //Get ptr and guard checks before adding the referrence
+ T* ptr = GetOffset((ulong)elementIndex);
+
+ bool addRef = false;
+ //use the pinned field as success val
+ DangerousAddRef(ref addRef);
+ //Create a new system.buffers memory handle from the offset ptr address
+ return !addRef
+ ? throw new ObjectDisposedException("Failed to increase referrence count on the memory handle because it was released")
+ : new MemoryHandle(ptr, pinnable: this);
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public void Unpin()
+ {
+ //Dec count on release
+ DangerousRelease();
+ }
+
+
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle()
+ {
+ //Return result of free
+ return Heap.Free(ref handle);
+ }
+
+
+
+ /// <summary>
+ /// Determines if the memory blocks are equal by comparing their base addresses.
+ /// </summary>
+ /// <param name="other"><see cref="MemoryHandle{T}"/> to compare</param>
+ /// <returns>true if the block of memory is the same, false if the handle's size does not
+ /// match or the base addresses do not match even if they point to an overlapping address space</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public bool Equals(MemoryHandle<T> other)
+ {
+ this.ThrowIfClosed();
+ other.ThrowIfClosed();
+ return _length == other._length && handle == other.handle;
+ }
+ ///<inheritdoc/>
+ public override bool Equals(object obj) => obj is MemoryHandle<T> oHandle && Equals(oHandle);
+ ///<inheritdoc/>
+ public override int GetHashCode() => base.GetHashCode();
+
+
+ ///<inheritdoc/>
+ public static implicit operator Span<T>(MemoryHandle<T> handle)
+ {
+ //If the handle is invalid or closed return an empty span
+ return handle.IsClosed || handle.IsInvalid || handle._length == 0 ? Span<T>.Empty : handle.Span;
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/Utils/src/Memory/PrivateBuffersMemoryPool.cs
new file mode 100644
index 0000000..1e85207
--- /dev/null
+++ b/Utils/src/Memory/PrivateBuffersMemoryPool.cs
@@ -0,0 +1,67 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: PrivateBuffersMemoryPool.cs
+*
+* PrivateBuffersMemoryPool.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a <see cref="MemoryPool{T}"/> wrapper for using unmanged <see cref="PrivateHeap"/>s
+ /// </summary>
+ /// <typeparam name="T">Unamanged memory type to provide data memory instances from</typeparam>
+ public sealed class PrivateBuffersMemoryPool<T> : MemoryPool<T> where T : unmanaged
+ {
+ private readonly IUnmangedHeap Heap;
+
+ internal PrivateBuffersMemoryPool(IUnmangedHeap heap):base()
+ {
+ this.Heap = heap;
+ }
+ ///<inheritdoc/>
+ public override int MaxBufferSize => int.MaxValue;
+ ///<inheritdoc/>
+ ///<exception cref="OutOfMemoryException"></exception>
+ ///<exception cref="ObjectDisposedException"></exception>
+ ///<exception cref="ArgumentOutOfRangeException"></exception>
+ public override IMemoryOwner<T> Rent(int minBufferSize = 0) => new SysBufferMemoryManager<T>(Heap, (uint)minBufferSize, false);
+
+ /// <summary>
+ /// Allocates a new <see cref="MemoryManager{T}"/> of a different data type from the pool
+ /// </summary>
+ /// <typeparam name="TDifType">The unmanaged data type to allocate for</typeparam>
+ /// <param name="minBufferSize">Minumum size of the buffer</param>
+ /// <returns>The memory owner of a different data type</returns>
+ public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged
+ {
+ return new SysBufferMemoryManager<TDifType>(Heap, (uint)minBufferSize, false);
+ }
+ ///<inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ //Dispose the heap
+ Heap.Dispose();
+ }
+ }
+}
diff --git a/Utils/src/Memory/PrivateHeap.cs b/Utils/src/Memory/PrivateHeap.cs
new file mode 100644
index 0000000..5d97506
--- /dev/null
+++ b/Utils/src/Memory/PrivateHeap.cs
@@ -0,0 +1,184 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: PrivateHeap.cs
+*
+* PrivateHeap.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics;
+using System.Runtime.Versioning;
+using System.Runtime.InteropServices;
+
+using DWORD = System.Int64;
+using SIZE_T = System.UInt64;
+using LPVOID = System.IntPtr;
+
+namespace VNLib.Utils.Memory
+{
+ ///<summary>
+ ///<para>
+ /// Provides a win32 private heap managed wrapper class
+ ///</para>
+ ///</summary>
+ ///<remarks>
+ /// <see cref="PrivateHeap"/> implements <see cref="SafeHandle"/> and tracks allocated blocks by its
+ /// referrence counter. Allocations increment the count, and free's decrement the count, so the heap may
+ /// be disposed safely
+ /// </remarks>
+ [ComVisible(false)]
+ [SupportedOSPlatform("Windows")]
+ public sealed class PrivateHeap : UnmanagedHeapBase
+ {
+ private const string KERNEL_DLL = "Kernel32";
+
+ #region Extern
+ //Heap flags
+ public const DWORD HEAP_NO_FLAGS = 0x00;
+ public const DWORD HEAP_GENERATE_EXCEPTIONS = 0x04;
+ public const DWORD HEAP_NO_SERIALIZE = 0x01;
+ public const DWORD HEAP_REALLOC_IN_PLACE_ONLY = 0x10;
+ public const DWORD HEAP_ZERO_MEMORY = 0x08;
+
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ private static extern LPVOID HeapAlloc(IntPtr hHeap, DWORD flags, SIZE_T dwBytes);
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ private static extern LPVOID HeapReAlloc(IntPtr hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes);
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool HeapFree(IntPtr hHeap, DWORD dwFlags, LPVOID lpMem);
+
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ private static extern LPVOID HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize);
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool HeapDestroy(IntPtr hHeap);
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ private static extern bool HeapValidate(IntPtr hHeap, DWORD dwFlags, LPVOID lpMem);
+ [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.U8)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ private static extern SIZE_T HeapSize(IntPtr hHeap, DWORD flags, LPVOID lpMem);
+
+ #endregion
+
+ /// <summary>
+ /// Create a new <see cref="PrivateHeap"/> with the specified sizes and flags
+ /// </summary>
+ /// <param name="initialSize">Intial size of the heap</param>
+ /// <param name="maxHeapSize">Maximum size allowed for the heap (disabled = 0, default)</param>
+ /// <param name="flags">Defalt heap flags to set globally for all blocks allocated by the heap (default = 0)</param>
+ public static PrivateHeap Create(SIZE_T initialSize, SIZE_T maxHeapSize = 0, DWORD flags = HEAP_NO_FLAGS)
+ {
+ //Call create, throw exception if the heap falled to allocate
+ IntPtr heapHandle = HeapCreate(flags, initialSize, maxHeapSize);
+ if (heapHandle == IntPtr.Zero)
+ {
+ throw new NativeMemoryException("Heap could not be created");
+ }
+#if TRACE
+ Trace.WriteLine($"Win32 private heap {heapHandle:x} created");
+#endif
+ //Heap has been created so we can wrap it
+ return new(heapHandle);
+ }
+ /// <summary>
+ /// LIFETIME WARNING. Consumes a valid win32 handle and will manage it's lifetime once constructed.
+ /// Locking and memory blocks will attempt to be allocated from this heap handle.
+ /// </summary>
+ /// <param name="win32HeapHandle">An open and valid handle to a win32 private heap</param>
+ /// <returns>A wrapper around the specified heap</returns>
+ public static PrivateHeap ConsumeExisting(IntPtr win32HeapHandle) => new (win32HeapHandle);
+
+ private PrivateHeap(IntPtr heapPtr) : base(false, true) => handle = heapPtr;
+
+ /// <summary>
+ /// Retrieves the size of a memory block allocated from the current heap.
+ /// </summary>
+ /// <param name="block">The pointer to a block of memory to get the size of</param>
+ /// <returns>The size of the block of memory, (SIZE_T)-1 if the operation fails</returns>
+ public SIZE_T HeapSize(ref LPVOID block) => HeapSize(handle, HEAP_NO_FLAGS, block);
+
+ /// <summary>
+ /// Validates the specified block of memory within the current heap instance. This function will block hte
+ /// </summary>
+ /// <param name="block">Pointer to the block of memory to validate</param>
+ /// <returns>True if the block is valid, false otherwise</returns>
+ public bool Validate(ref LPVOID block)
+ {
+ bool result;
+ //Lock the heap before validating
+ HeapLock.Wait();
+ //validate the block on the current heap
+ result = HeapValidate(handle, HEAP_NO_FLAGS, block);
+ //Unlock the heap
+ HeapLock.Release();
+ return result;
+
+ }
+ /// <summary>
+ /// Validates the current heap instance. The function scans all the memory blocks in the heap and verifies that the heap control structures maintained by
+ /// the heap manager are in a consistent state.
+ /// </summary>
+ /// <returns>If the specified heap or memory block is valid, the return value is nonzero.</returns>
+ /// <remarks>This can be a consuming operation which will block all allocations</remarks>
+ public bool Validate()
+ {
+ bool result;
+ //Lock the heap before validating
+ HeapLock.Wait();
+ //validate the entire heap
+ result = HeapValidate(handle, HEAP_NO_FLAGS, IntPtr.Zero);
+ //Unlock the heap
+ HeapLock.Release();
+ return result;
+ }
+
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle()
+ {
+#if TRACE
+ Trace.WriteLine($"Win32 private heap {handle:x} destroyed");
+#endif
+ return HeapDestroy(handle) && base.ReleaseHandle();
+ }
+ ///<inheritdoc/>
+ protected override sealed LPVOID AllocBlock(ulong elements, ulong size, bool zero)
+ {
+ ulong bytes = checked(elements * size);
+ return HeapAlloc(handle, zero ? HEAP_ZERO_MEMORY : HEAP_NO_FLAGS, bytes);
+ }
+ ///<inheritdoc/>
+ protected override sealed bool FreeBlock(LPVOID block) => HeapFree(handle, HEAP_NO_FLAGS, block);
+ ///<inheritdoc/>
+ protected override sealed LPVOID ReAllocBlock(LPVOID block, ulong elements, ulong size, bool zero)
+ {
+ ulong bytes = checked(elements * size);
+ return HeapReAlloc(handle, zero ? HEAP_ZERO_MEMORY : HEAP_NO_FLAGS, block, bytes);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/PrivateString.cs b/Utils/src/Memory/PrivateString.cs
new file mode 100644
index 0000000..cd3b1f6
--- /dev/null
+++ b/Utils/src/Memory/PrivateString.cs
@@ -0,0 +1,185 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: PrivateString.cs
+*
+* PrivateString.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a wrapper class that will have unsafe access to the memory of
+ /// the specified <see cref="string"/> provided during object creation.
+ /// </summary>
+ /// <remarks>The value of the memory the protected string points to is undefined when the instance is disposed</remarks>
+ public class PrivateString : PrivateStringManager, IEquatable<PrivateString>, IEquatable<string>, ICloneable
+ {
+ protected string StrRef => base[0]!;
+ private readonly bool OwnsReferrence;
+
+ /// <summary>
+ /// Creates a new <see cref="PrivateString"/> over the specified string and the memory it points to.
+ /// </summary>
+ /// <param name="data">The <see cref="string"/> instance pointing to the memory to protect</param>
+ /// <param name="ownsReferrence">Does the current instance "own" the memory the data parameter points to</param>
+ /// <remarks>You should no longer reference the input string directly</remarks>
+ public PrivateString(string data, bool ownsReferrence = true) : base(1)
+ {
+ //Create a private string manager to store referrence to string
+ base[0] = data ?? throw new ArgumentNullException(nameof(data));
+ OwnsReferrence = ownsReferrence;
+ }
+
+ //Create private string from a string
+ public static explicit operator PrivateString?(string? data)
+ {
+ //Allow passing null strings during implicit casting
+ return data == null ? null : new(data);
+ }
+
+ public static PrivateString? ToPrivateString(string? value)
+ {
+ return value == null ? null : new PrivateString(value, true);
+ }
+
+ //Cast to string
+ public static explicit operator string (PrivateString str)
+ {
+ //Check if disposed, or return the string
+ str.Check();
+ return str.StrRef;
+ }
+
+ public static implicit operator ReadOnlySpan<char>(PrivateString str)
+ {
+ return str.Disposed ? Span<char>.Empty : str.StrRef.AsSpan();
+ }
+
+ /// <summary>
+ /// Gets the value of the internal string as a <see cref="ReadOnlySpan{T}"/>
+ /// </summary>
+ /// <returns>The <see cref="ReadOnlySpan{T}"/> referrence to the internal string</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public ReadOnlySpan<char> ToReadOnlySpan()
+ {
+ Check();
+ return StrRef.AsSpan();
+ }
+
+ ///<inheritdoc/>
+ public bool Equals(string? other)
+ {
+ Check();
+ return StrRef.Equals(other);
+ }
+ ///<inheritdoc/>
+ public bool Equals(PrivateString? other)
+ {
+ Check();
+ return other != null && StrRef.Equals(other.StrRef);
+ }
+ ///<inheritdoc/>
+ public override bool Equals(object? other)
+ {
+ Check();
+ return other is PrivateString otherRef && StrRef.Equals(otherRef);
+ }
+ ///<inheritdoc/>
+ public bool Equals(ReadOnlySpan<char> other)
+ {
+ Check();
+ return StrRef.AsSpan().SequenceEqual(other);
+ }
+ /// <summary>
+ /// Creates a deep copy of the internal string and returns that copy
+ /// </summary>
+ /// <returns>A deep copy of the internal string</returns>
+ public override string ToString()
+ {
+ Check();
+ return new(StrRef.AsSpan());
+ }
+ /// <summary>
+ /// String length
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public int Length
+ {
+ get
+ {
+ Check();
+ return StrRef.Length;
+ }
+ }
+ /// <summary>
+ /// Indicates whether the underlying string is null or an empty string ("")
+ /// </summary>
+ /// <param name="ps"></param>
+ /// <returns>True if the parameter is null, or an empty string (""). False otherwise</returns>
+ public static bool IsNullOrEmpty([NotNullWhen(false)] PrivateString? ps) => ps == null|| ps.Length == 0;
+
+ /// <summary>
+ /// The hashcode of the underlying string
+ /// </summary>
+ /// <returns></returns>
+ public override int GetHashCode()
+ {
+ Check();
+ return StrRef.GetHashCode();
+ }
+
+ /// <summary>
+ /// Creates a new deep copy of the current instance that is an independent <see cref="PrivateString"/>
+ /// </summary>
+ /// <returns>The new <see cref="PrivateString"/> instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public override object Clone()
+ {
+ Check();
+ //Copy all contents of string to another reference
+ string clone = new (StrRef.AsSpan());
+ //return a new private string
+ return new PrivateString(clone, true);
+ }
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ Erase();
+ }
+
+ /// <summary>
+ /// Erases the contents of the internal CLR string
+ /// </summary>
+ public void Erase()
+ {
+ //Only dispose the instance if we own the memory
+ if (OwnsReferrence && !Disposed)
+ {
+ base.Free();
+ }
+ }
+
+
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/PrivateStringManager.cs b/Utils/src/Memory/PrivateStringManager.cs
new file mode 100644
index 0000000..9ed8f5f
--- /dev/null
+++ b/Utils/src/Memory/PrivateStringManager.cs
@@ -0,0 +1,117 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: PrivateStringManager.cs
+*
+* PrivateStringManager.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// When inherited by a class, provides a safe string storage that zeros a CLR string memory on disposal
+ /// </summary>
+ public class PrivateStringManager : VnDisposeable, ICloneable
+ {
+ /// <summary>
+ /// Strings to be cleared when exiting
+ /// </summary>
+ private readonly string?[] ProtectedElements;
+ /// <summary>
+ /// Gets or sets a string referrence into the protected elements store
+ /// </summary>
+ /// <param name="index"></param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <returns>Referrence to string associated with the index</returns>
+ protected string? this[int index]
+ {
+ get
+ {
+ Check();
+ return ProtectedElements[index];
+ }
+ set
+ {
+ Check();
+ //Check to see if the string has been interned
+ if (!string.IsNullOrEmpty(value) && string.IsInterned(value) != null)
+ {
+ throw new ArgumentException($"The specified string has been CLR interned and cannot be stored in {nameof(PrivateStringManager)}");
+ }
+ //Clear the old value before setting the new one
+ if (!string.IsNullOrEmpty(ProtectedElements[index]))
+ {
+ Memory.UnsafeZeroMemory<char>(ProtectedElements[index]);
+ }
+ //set new value
+ ProtectedElements[index] = value;
+ }
+ }
+ /// <summary>
+ /// Create a new instance with fixed array size
+ /// </summary>
+ /// <param name="elements">Number of elements to protect</param>
+ public PrivateStringManager(int elements)
+ {
+ //Allocate the string array
+ ProtectedElements = new string[elements];
+ }
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ //Zero all strings specified
+ for (int i = 0; i < ProtectedElements.Length; i++)
+ {
+ if (!string.IsNullOrEmpty(ProtectedElements[i]))
+ {
+ //Zero the string memory
+ Memory.UnsafeZeroMemory<char>(ProtectedElements[i]);
+ //Set to null
+ ProtectedElements[i] = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a deep copy for a new independent <see cref="PrivateStringManager"/>
+ /// </summary>
+ /// <returns>A new independent <see cref="PrivateStringManager"/> instance</returns>
+ /// <remarks>Be careful duplicating large instances, and make sure clones are properly disposed if necessary</remarks>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public virtual object Clone()
+ {
+ Check();
+ PrivateStringManager other = new (ProtectedElements.Length);
+ //Copy all strings to the other instance
+ for(int i = 0; i < ProtectedElements.Length; i++)
+ {
+ //Copy all strings and store their copies in the new array
+ other.ProtectedElements[i] = this.ProtectedElements[i].AsSpan().ToString();
+ }
+ //return the new copy
+ return other;
+ }
+ }
+}
diff --git a/Utils/src/Memory/ProcessHeap.cs b/Utils/src/Memory/ProcessHeap.cs
new file mode 100644
index 0000000..4f06d52
--- /dev/null
+++ b/Utils/src/Memory/ProcessHeap.cs
@@ -0,0 +1,82 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ProcessHeap.cs
+*
+* ProcessHeap.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a <see cref="IUnmangedHeap"/> wrapper for the <see cref="Marshal"/> virtualalloc
+ /// global heap methods
+ /// </summary>
+ [ComVisible(false)]
+ public unsafe class ProcessHeap : VnDisposeable, IUnmangedHeap
+ {
+ /// <summary>
+ /// Initalizes a new global (cross platform) process heap
+ /// </summary>
+ public ProcessHeap()
+ {
+#if TRACE
+ Trace.WriteLine($"Default heap instnace created {GetHashCode():x}");
+#endif
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="OverflowException"></exception>
+ ///<exception cref="OutOfMemoryException"></exception>
+ public IntPtr Alloc(ulong elements, ulong size, bool zero)
+ {
+ return zero
+ ? (IntPtr)NativeMemory.AllocZeroed((nuint)elements, (nuint)size)
+ : (IntPtr)NativeMemory.Alloc((nuint)elements, (nuint)size);
+ }
+ ///<inheritdoc/>
+ public bool Free(ref IntPtr block)
+ {
+ //Free native mem from ptr
+ NativeMemory.Free(block.ToPointer());
+ block = IntPtr.Zero;
+ return true;
+ }
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+#if TRACE
+ Trace.WriteLine($"Default heap instnace disposed {GetHashCode():x}");
+#endif
+ }
+ ///<inheritdoc/>
+ ///<exception cref="OverflowException"></exception>
+ ///<exception cref="OutOfMemoryException"></exception>
+ public void Resize(ref IntPtr block, ulong elements, ulong size, bool zero)
+ {
+ nuint bytes = checked((nuint)(elements * size));
+ IntPtr old = block;
+ block = (IntPtr)NativeMemory.Realloc(old.ToPointer(), bytes);
+ }
+ }
+}
diff --git a/Utils/src/Memory/RpMallocPrivateHeap.cs b/Utils/src/Memory/RpMallocPrivateHeap.cs
new file mode 100644
index 0000000..70c8a7f
--- /dev/null
+++ b/Utils/src/Memory/RpMallocPrivateHeap.cs
@@ -0,0 +1,279 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: RpMallocPrivateHeap.cs
+*
+* RpMallocPrivateHeap.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using size_t = System.UInt64;
+using LPVOID = System.IntPtr;
+using LPHEAPHANDLE = System.IntPtr;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// A wrapper class for cross platform RpMalloc implementation.
+ /// </summary>
+ [ComVisible(false)]
+ public sealed class RpMallocPrivateHeap : UnmanagedHeapBase
+ {
+ const string DLL_NAME = "rpmalloc";
+
+ #region statics
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern int rpmalloc_initialize();
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_finalize();
+
+ //Heap api
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPHEAPHANDLE rpmalloc_heap_acquire();
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_heap_release(LPHEAPHANDLE heap);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc_heap_alloc(LPHEAPHANDLE heap, size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc_heap_aligned_alloc(LPHEAPHANDLE heap, size_t alignment, size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc_heap_calloc(LPHEAPHANDLE heap, size_t num, size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc_heap_aligned_calloc(LPHEAPHANDLE heap, size_t alignment, size_t num, size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc_heap_realloc(LPHEAPHANDLE heap, LPVOID ptr, size_t size, nuint flags);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc_heap_aligned_realloc(LPHEAPHANDLE heap, LPVOID ptr, size_t alignment, size_t size, nuint flags);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_heap_free(LPHEAPHANDLE heap, LPVOID ptr);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_heap_free_all(LPHEAPHANDLE heap);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_heap_thread_set_current(LPHEAPHANDLE heap);
+
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_thread_initialize();
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern int rpmalloc_is_thread_initialized();
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpmalloc_thread_finalize(int release_caches);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpmalloc(size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rpcalloc(size_t num, size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern LPVOID rprealloc(LPVOID ptr, size_t size);
+ [DllImport(DLL_NAME, ExactSpelling = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ static extern void rpfree(LPVOID ptr);
+
+ #endregion
+
+ private class RpMallocGlobalHeap : IUnmangedHeap
+ {
+ IntPtr IUnmangedHeap.Alloc(ulong elements, ulong size, bool zero)
+ {
+ return RpMalloc(elements, (nuint)size, zero);
+ }
+
+ //Global heap does not need to be disposed
+ void IDisposable.Dispose()
+ { }
+
+ bool IUnmangedHeap.Free(ref IntPtr block)
+ {
+ //Free the block
+ RpFree(ref block);
+ return true;
+ }
+
+ void IUnmangedHeap.Resize(ref IntPtr block, ulong elements, ulong size, bool zero)
+ {
+ //Try to resize the block
+ IntPtr resize = RpRealloc(block, elements, (nuint)size);
+
+ if (resize == IntPtr.Zero)
+ {
+ throw new NativeMemoryOutOfMemoryException("Failed to resize the block");
+ }
+ //assign ptr
+ block = resize;
+ }
+ }
+
+ /// <summary>
+ /// <para>
+ /// A <see cref="IUnmangedHeap"/> API for the RPMalloc library if loaded.
+ /// </para>
+ /// <para>
+ /// This heap is thread safe and may be converted to a <see cref="MemoryManager{T}"/>
+ /// infinitley and disposed safely.
+ /// </para>
+ /// <para>
+ /// If the native library is not loaded, calls to this API will throw a <see cref="DllNotFoundException"/>.
+ /// </para>
+ /// </summary>
+ public static IUnmangedHeap GlobalHeap { get; } = new RpMallocGlobalHeap();
+
+ /// <summary>
+ /// <para>
+ /// Initializes RpMalloc for the current thread and alloctes a block of memory
+ /// </para>
+ /// </summary>
+ /// <param name="elements">The number of elements to allocate</param>
+ /// <param name="size">The number of bytes per element type (aligment)</param>
+ /// <param name="zero">Zero the block of memory before returning</param>
+ /// <returns>A pointer to the block, (zero if failed)</returns>
+ public static LPVOID RpMalloc(size_t elements, nuint size, bool zero)
+ {
+ //See if the current thread has been initialized
+ if (rpmalloc_is_thread_initialized() == 0)
+ {
+ //Initialize the current thread
+ rpmalloc_thread_initialize();
+ }
+ //Alloc block
+ LPVOID block;
+ if (zero)
+ {
+ block = rpcalloc(elements, size);
+ }
+ else
+ {
+ //Calculate the block size
+ ulong blockSize = checked(elements * size);
+ block = rpmalloc(blockSize);
+ }
+ return block;
+ }
+
+ /// <summary>
+ /// Frees a block of memory allocated by RpMalloc
+ /// </summary>
+ /// <param name="block">A ref to the pointer of the block to free</param>
+ public static void RpFree(ref LPVOID block)
+ {
+ if (block != IntPtr.Zero)
+ {
+ rpfree(block);
+ block = IntPtr.Zero;
+ }
+ }
+
+ /// <summary>
+ /// Attempts to re-allocate the specified block on the global heap
+ /// </summary>
+ /// <param name="block">A pointer to a previously allocated block of memory</param>
+ /// <param name="elements">The number of elements in the block</param>
+ /// <param name="size">The number of bytes in the element</param>
+ /// <returns>A pointer to the new block if the reallocation succeeded, null if the resize failed</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ public static LPVOID RpRealloc(LPVOID block, size_t elements, nuint size)
+ {
+ if(block == IntPtr.Zero)
+ {
+ throw new ArgumentException("The supplied block is not valid", nameof(block));
+ }
+ //Calc new block size
+ size_t blockSize = checked(elements * size);
+ return rprealloc(block, blockSize);
+ }
+
+ #region instance
+
+ /// <summary>
+ /// Initializes a new RpMalloc first class heap to allocate memory blocks from
+ /// </summary>
+ /// <param name="zeroAll">A global flag to zero all blocks of memory allocated</param>
+ /// <exception cref="NativeMemoryOutOfMemoryException"></exception>
+ public RpMallocPrivateHeap(bool zeroAll):base(zeroAll, true)
+ {
+ //Alloc the heap
+ handle = rpmalloc_heap_acquire();
+ if(IsInvalid)
+ {
+ throw new NativeMemoryException("Failed to aquire a new heap");
+ }
+#if TRACE
+ Trace.WriteLine($"RPMalloc heap {handle:x} created");
+#endif
+ }
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle()
+ {
+#if TRACE
+ Trace.WriteLine($"RPMalloc heap {handle:x} destroyed");
+#endif
+ //Release all heap memory
+ rpmalloc_heap_free_all(handle);
+ //Destroy the heap
+ rpmalloc_heap_release(handle);
+ //Release base
+ return base.ReleaseHandle();
+ }
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected sealed override LPVOID AllocBlock(ulong elements, ulong size, bool zero)
+ {
+ //Alloc or calloc and initalize
+ return zero ? rpmalloc_heap_calloc(handle, elements, size) : rpmalloc_heap_alloc(handle, checked(size * elements));
+ }
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected sealed override bool FreeBlock(LPVOID block)
+ {
+ //Free block
+ rpmalloc_heap_free(handle, block);
+ return true;
+ }
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected sealed override LPVOID ReAllocBlock(LPVOID block, ulong elements, ulong size, bool zero)
+ {
+ //Realloc
+ return rpmalloc_heap_realloc(handle, block, checked(elements * size), 0);
+ }
+ #endregion
+ }
+}
diff --git a/Utils/src/Memory/SubSequence.cs b/Utils/src/Memory/SubSequence.cs
new file mode 100644
index 0000000..3800fb5
--- /dev/null
+++ b/Utils/src/Memory/SubSequence.cs
@@ -0,0 +1,113 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SubSequence.cs
+*
+* SubSequence.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Represents a subset (or window) of data within a <see cref="MemoryHandle{T}"/>
+ /// </summary>
+ /// <typeparam name="T">The unmanaged type to wrap</typeparam>
+ public readonly struct SubSequence<T> : IEquatable<SubSequence<T>> where T: unmanaged
+ {
+ private readonly MemoryHandle<T> _handle;
+ /// <summary>
+ /// The number of elements in the current window
+ /// </summary>
+ public readonly int Size { get; }
+
+ /// <summary>
+ /// Creates a new <see cref="SubSequence{T}"/> to the handle to get a window of the block
+ /// </summary>
+ /// <param name="block"></param>
+ /// <param name="offset"></param>
+ /// <param name="size"></param>
+#if TARGET_64_BIT
+ public SubSequence(MemoryHandle<T> block, ulong offset, int size)
+#else
+ public SubSequence(MemoryHandle<T> block, int offset, int size)
+#endif
+ {
+ _offset = offset;
+ Size = size >= 0 ? size : throw new ArgumentOutOfRangeException(nameof(size));
+ _handle = block ?? throw new ArgumentNullException(nameof(block));
+ }
+
+
+#if TARGET_64_BIT
+ private readonly ulong _offset;
+#else
+ private readonly int _offset;
+#endif
+ /// <summary>
+ /// Gets a <see cref="Span{T}"/> that is offset from the base of the handle
+ /// </summary>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+
+#if TARGET_64_BIT
+ public readonly Span<T> Span => Size > 0 ? _handle.GetOffsetSpan(_offset, Size) : Span<T>.Empty;
+#else
+ public readonly Span<T> Span => Size > 0 ? _handle.Span.Slice(_offset, Size) : Span<T>.Empty;
+#endif
+
+ /// <summary>
+ /// Slices the current sequence into a smaller <see cref="SubSequence{T}"/>
+ /// </summary>
+ /// <param name="offset">The relative offset from the current window offset</param>
+ /// <param name="size">The size of the block</param>
+ /// <returns>A <see cref="SubSequence{T}"/> of the current sequence</returns>
+ public readonly SubSequence<T> Slice(uint offset, int size) => new (_handle, _offset + checked((int)offset), size);
+
+ /// <summary>
+ /// Returns the signed 32-bit hashcode
+ /// </summary>
+ /// <returns>A signed 32-bit integer that represents the hashcode for the current instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public readonly override int GetHashCode() => _handle.GetHashCode() + _offset.GetHashCode();
+
+ ///<inheritdoc/>
+ public readonly bool Equals(SubSequence<T> other) => Span.SequenceEqual(other.Span);
+
+ ///<inheritdoc/>
+ public readonly override bool Equals(object? obj) => obj is SubSequence<T> other && Equals(other);
+
+ /// <summary>
+ /// Determines if two <see cref="SubSequence{T}"/> are equal
+ /// </summary>
+ /// <param name="left"></param>
+ /// <param name="right"></param>
+ /// <returns>True if the sequences are equal, false otherwise</returns>
+ public static bool operator ==(SubSequence<T> left, SubSequence<T> right) => left.Equals(right);
+ /// <summary>
+ /// Determines if two <see cref="SubSequence{T}"/> are not equal
+ /// </summary>
+ /// <param name="left"></param>
+ /// <param name="right"></param>
+ /// <returns>True if the sequences are not equal, false otherwise</returns>
+ public static bool operator !=(SubSequence<T> left, SubSequence<T> right) => !left.Equals(right);
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/SysBufferMemoryManager.cs b/Utils/src/Memory/SysBufferMemoryManager.cs
new file mode 100644
index 0000000..040467f
--- /dev/null
+++ b/Utils/src/Memory/SysBufferMemoryManager.cs
@@ -0,0 +1,102 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SysBufferMemoryManager.cs
+*
+* SysBufferMemoryManager.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides an unmanaged System.Buffers integration with zero-cost pinning. Uses <see cref="MemoryHandle{T}"/>
+ /// as a memory provider which implements a <see cref="System.Runtime.InteropServices.SafeHandle"/>
+ /// </summary>
+ /// <typeparam name="T">Unmanaged memory type</typeparam>
+ public sealed class SysBufferMemoryManager<T> : MemoryManager<T> where T :unmanaged
+ {
+ private readonly IMemoryHandle<T> BackingMemory;
+ private readonly bool _ownsHandle;
+
+ /// <summary>
+ /// Consumes an exisitng <see cref="MemoryHandle{T}"/> to provide <see cref="Memory"/> wrappers.
+ /// The handle should no longer be referrenced directly
+ /// </summary>
+ /// <param name="existingHandle">The existing handle to consume</param>
+ /// <param name="ownsHandle">A value that indicates if the memory manager owns the handle reference</param>
+ internal SysBufferMemoryManager(IMemoryHandle<T> existingHandle, bool ownsHandle)
+ {
+ BackingMemory = existingHandle;
+ _ownsHandle = ownsHandle;
+ }
+
+ /// <summary>
+ /// Allocates a fized size buffer from the specified unmanaged <see cref="PrivateHeap"/>
+ /// </summary>
+ /// <param name="heap">The heap to perform allocations from</param>
+ /// <param name="elements">The number of elements to allocate</param>
+ /// <param name="zero">Zero allocations</param>
+ public SysBufferMemoryManager(IUnmangedHeap heap, ulong elements, bool zero)
+ {
+ BackingMemory = heap.Alloc<T>(elements, zero);
+ _ownsHandle = true;
+ }
+
+ ///<inheritdoc/>
+ protected override bool TryGetArray(out ArraySegment<T> segment)
+ {
+ //Always false since no array is available
+ segment = default;
+ return false;
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="OverflowException"></exception>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public override Span<T> GetSpan() => BackingMemory.Span;
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public unsafe override MemoryHandle Pin(int elementIndex = 0)
+ {
+ return BackingMemory.Pin(elementIndex);
+ }
+
+ ///<inheritdoc/>
+ public override void Unpin()
+ {}
+
+ ///<inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (_ownsHandle)
+ {
+ BackingMemory.Dispose();
+ }
+ }
+ }
+}
diff --git a/Utils/src/Memory/UnmanagedHeapBase.cs b/Utils/src/Memory/UnmanagedHeapBase.cs
new file mode 100644
index 0000000..5c92aff
--- /dev/null
+++ b/Utils/src/Memory/UnmanagedHeapBase.cs
@@ -0,0 +1,185 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: UnmanagedHeapBase.cs
+*
+* UnmanagedHeapBase.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Runtime.InteropServices;
+
+using Microsoft.Win32.SafeHandles;
+
+using size_t = System.UInt64;
+using LPVOID = System.IntPtr;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a synchronized base methods for accessing unmanaged memory. Implements <see cref="SafeHandle"/>
+ /// for safe disposal of heaps
+ /// </summary>
+ public abstract class UnmanagedHeapBase : SafeHandleZeroOrMinusOneIsInvalid, IUnmangedHeap
+ {
+ /// <summary>
+ /// The heap synchronization handle
+ /// </summary>
+ protected readonly SemaphoreSlim HeapLock;
+ /// <summary>
+ /// The global heap zero flag
+ /// </summary>
+ protected readonly bool GlobalZero;
+
+ /// <summary>
+ /// Initalizes the unmanaged heap base class (init synchronization handle)
+ /// </summary>
+ /// <param name="globalZero">A global flag to zero all blocks of memory during allocation</param>
+ /// <param name="ownsHandle">A flag that indicates if the handle is owned by the instance</param>
+ protected UnmanagedHeapBase(bool globalZero, bool ownsHandle) : base(ownsHandle)
+ {
+ HeapLock = new(1, 1);
+ GlobalZero = globalZero;
+ }
+
+ ///<inheritdoc/>
+ ///<remarks>Increments the handle count</remarks>
+ ///<exception cref="OutOfMemoryException"></exception>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public LPVOID Alloc(size_t elements, size_t size, bool zero)
+ {
+ //Force zero if global flag is set
+ zero |= GlobalZero;
+ bool handleCountIncremented = false;
+ //Increment handle count to prevent premature release
+ DangerousAddRef(ref handleCountIncremented);
+ //Failed to increment ref count, class has been disposed
+ if (!handleCountIncremented)
+ {
+ throw new ObjectDisposedException("The handle has been released");
+ }
+ try
+ {
+ //wait for lock
+ HeapLock.Wait();
+ //Alloc block
+ LPVOID block = AllocBlock(elements, size, zero);
+ //release lock
+ HeapLock.Release();
+ //Check if block was allocated
+ return block != IntPtr.Zero ? block : throw new NativeMemoryOutOfMemoryException("Failed to allocate the requested block");
+ }
+ catch
+ {
+ //Decrement handle count since allocation failed
+ DangerousRelease();
+ throw;
+ }
+ }
+ ///<inheritdoc/>
+ ///<remarks>Decrements the handle count</remarks>
+ public bool Free(ref LPVOID block)
+ {
+ bool result;
+ //If disposed, set the block handle to zero and exit to avoid raising exceptions during finalization
+ if (IsClosed || IsInvalid)
+ {
+ block = IntPtr.Zero;
+ return true;
+ }
+ //wait for lock
+ HeapLock.Wait();
+ //Free block
+ result = FreeBlock(block);
+ //Release lock before releasing handle
+ HeapLock.Release();
+ //Decrement handle count
+ DangerousRelease();
+ //set block to invalid
+ block = IntPtr.Zero;
+ return result;
+ }
+ ///<inheritdoc/>
+ ///<exception cref="OutOfMemoryException"></exception>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public void Resize(ref LPVOID block, size_t elements, size_t size, bool zero)
+ {
+ //wait for lock
+ HeapLock.Wait();
+ /*
+ * Realloc may return a null pointer if allocation fails
+ * so check the results and only assign the block pointer
+ * if the result is valid. Otherwise pointer block should
+ * be left untouched
+ */
+ LPVOID newBlock = ReAllocBlock(block, elements, size, zero);
+ //release lock
+ HeapLock.Release();
+ //Check block
+ if (newBlock == IntPtr.Zero)
+ {
+ throw new NativeMemoryOutOfMemoryException("The memory block could not be resized");
+ }
+ //Set the new block
+ block = newBlock;
+ }
+
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle()
+ {
+ HeapLock.Dispose();
+ return true;
+ }
+
+ /// <summary>
+ /// Allocates a block of memory from the heap
+ /// </summary>
+ /// <param name="elements">The number of elements within the block</param>
+ /// <param name="size">The size of the element type (in bytes)</param>
+ /// <param name="zero">A flag to zero the allocated block</param>
+ /// <returns>A pointer to the allocated block</returns>
+ protected abstract LPVOID AllocBlock(size_t elements, size_t size, bool zero);
+ /// <summary>
+ /// Frees a previously allocated block of memory
+ /// </summary>
+ /// <param name="block">The block to free</param>
+ protected abstract bool FreeBlock(LPVOID block);
+ /// <summary>
+ /// Resizes the previously allocated block of memory on the current heap
+ /// </summary>
+ /// <param name="block">The prevously allocated block</param>
+ /// <param name="elements">The new number of elements within the block</param>
+ /// <param name="size">The size of the element type (in bytes)</param>
+ /// <param name="zero">A flag to indicate if the new region of the block should be zeroed</param>
+ /// <returns>A pointer to the same block, but resized, null if the allocation falied</returns>
+ /// <remarks>
+ /// Heap base relies on the block pointer to remain unchanged if the resize fails so the
+ /// block is still valid, and the return value is used to determine if the resize was successful
+ /// </remarks>
+ protected abstract LPVOID ReAllocBlock(LPVOID block, size_t elements, size_t size, bool zero);
+ ///<inheritdoc/>
+ public override int GetHashCode() => handle.GetHashCode();
+ ///<inheritdoc/>
+ public override bool Equals(object? obj)
+ {
+ return obj is UnmanagedHeapBase heap && !heap.IsInvalid && !heap.IsClosed && handle == heap.handle;
+ }
+ }
+}
diff --git a/Utils/src/Memory/UnsafeMemoryHandle.cs b/Utils/src/Memory/UnsafeMemoryHandle.cs
new file mode 100644
index 0000000..b05ad40
--- /dev/null
+++ b/Utils/src/Memory/UnsafeMemoryHandle.cs
@@ -0,0 +1,231 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: UnsafeMemoryHandle.cs
+*
+* UnsafeMemoryHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Represents an unsafe handle to managed/unmanaged memory that should be used cautiously.
+ /// A referrence counter is not maintained.
+ /// </summary>
+ /// <typeparam name="T">Unmanaged memory type</typeparam>
+ [StructLayout(LayoutKind.Sequential)]
+ public readonly struct UnsafeMemoryHandle<T> : IMemoryHandle<T>, IEquatable<UnsafeMemoryHandle<T>> where T : unmanaged
+ {
+ private enum HandleType
+ {
+ None,
+ Pool,
+ PrivateHeap
+ }
+
+ private readonly T[]? _poolArr;
+ private readonly IntPtr _memoryPtr;
+ private readonly ArrayPool<T>? _pool;
+ private readonly IUnmangedHeap? _heap;
+ private readonly HandleType _handleType;
+ private readonly int _length;
+
+ ///<inheritdoc/>
+ public readonly unsafe Span<T> Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _handleType == HandleType.Pool ? _poolArr.AsSpan(0, IntLength) : new (_memoryPtr.ToPointer(), IntLength);
+ }
+ ///<inheritdoc/>
+ public readonly int IntLength => _length;
+ ///<inheritdoc/>
+ public readonly ulong Length => (ulong)_length;
+
+ /// <summary>
+ /// Creates an empty <see cref="UnsafeMemoryHandle{T}"/>
+ /// </summary>
+ public UnsafeMemoryHandle()
+ {
+ _pool = null;
+ _heap = null;
+ _poolArr = null;
+ _memoryPtr = IntPtr.Zero;
+ _handleType = HandleType.None;
+ _length = 0;
+ }
+
+ /// <summary>
+ /// Inializes a new <see cref="UnsafeMemoryHandle{T}"/> using the specified
+ /// <see cref="ArrayPool{T}"/>
+ /// </summary>
+ /// <param name="elements">The number of elements to store</param>
+ /// <param name="zero">Zero initial contents?</param>
+ /// <param name="pool">The explicit pool to alloc buffers from</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public unsafe UnsafeMemoryHandle(ArrayPool<T> pool, int elements, bool zero)
+ {
+ if (elements < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elements));
+ }
+ //Pool is required
+ _pool = pool ?? throw new ArgumentNullException(nameof(pool));
+ //Rent the array from the pool and hold referrence to it
+ _poolArr = pool.Rent(elements, zero);
+ //Cant store ref to array becase GC can move it
+ _memoryPtr = IntPtr.Zero;
+ //Set pool handle type
+ _handleType = HandleType.Pool;
+ //No heap being loaded
+ _heap = null;
+ _length = elements;
+ }
+
+ /// <summary>
+ /// Intializes a new <see cref="UnsafeMemoryHandle{T}"/> for block of memory allocated from
+ /// an <see cref="IUnmangedHeap"/>
+ /// </summary>
+ /// <param name="heap">The heap the initial memory block belongs to</param>
+ /// <param name="initial">A pointer to the unmanaged memory block</param>
+ /// <param name="elements">The number of elements this block points to</param>
+ internal UnsafeMemoryHandle(IUnmangedHeap heap, IntPtr initial, int elements)
+ {
+ _pool = null;
+ _poolArr = null;
+ _heap = heap;
+ _length = elements;
+ _memoryPtr = initial;
+ _handleType = HandleType.PrivateHeap;
+ }
+
+ /// <summary>
+ /// Releases memory back to the pool or heap from which is was allocated.
+ /// </summary>
+ /// <remarks>After this method is called, this handle points to invalid memory</remarks>
+ public readonly void Dispose()
+ {
+ switch (_handleType)
+ {
+ case HandleType.Pool:
+ {
+ //Return array to pool
+ _pool!.Return(_poolArr!);
+ }
+ break;
+ case HandleType.PrivateHeap:
+ {
+ IntPtr unalloc = _memoryPtr;
+ //Free the unmanaged handle
+ _heap!.Free(ref unalloc);
+ }
+ break;
+ }
+ }
+
+ ///<inheritdoc/>
+ public readonly override int GetHashCode() => _handleType == HandleType.Pool ? _poolArr!.GetHashCode() : _memoryPtr.GetHashCode();
+ ///<inheritdoc/>
+ public readonly unsafe MemoryHandle Pin(int elementIndex)
+ {
+ //Guard
+ if (elementIndex < 0 || elementIndex >= IntLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elementIndex));
+ }
+
+ if (_handleType == HandleType.Pool)
+ {
+ //Pin the array
+ GCHandle arrHandle = GCHandle.Alloc(_poolArr, GCHandleType.Pinned);
+ //Get array base address
+ void* basePtr = (void*)arrHandle.AddrOfPinnedObject();
+ //Get element offset
+ void* indexOffet = Unsafe.Add<T>(basePtr, elementIndex);
+ return new (indexOffet, arrHandle);
+ }
+ else
+ {
+ //Get offset pointer and pass self as pinnable argument, (nothing happens but support it)
+ void* basePtr = Unsafe.Add<T>(_memoryPtr.ToPointer(), elementIndex);
+ //Unmanaged memory is always pinned, so no need to pass this as IPinnable, since it will cause a box
+ return new (basePtr);
+ }
+ }
+ ///<inheritdoc/>
+ public readonly void Unpin()
+ {
+ //Nothing to do since gc handle takes care of array, and unmanaged pointers are not pinned
+ }
+
+ /// <summary>
+ /// Determines if the other handle represents the same memory block as the
+ /// current handle.
+ /// </summary>
+ /// <param name="other">The other handle to test</param>
+ /// <returns>True if the other handle points to the same block of memory as the current handle</returns>
+ public readonly bool Equals(UnsafeMemoryHandle<T> other)
+ {
+ return _handleType == other._handleType && Length == other.Length && GetHashCode() == other.GetHashCode();
+ }
+
+ /// <summary>
+ /// Override for object equality operator, will cause boxing
+ /// for structures
+ /// </summary>
+ /// <param name="obj">The other object to compare</param>
+ /// <returns>
+ /// True if the passed object is of type <see cref="UnsafeMemoryHandle{T}"/>
+ /// and uses the structure equality operator
+ /// false otherwise.
+ /// </returns>
+ public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is UnsafeMemoryHandle<T> other && Equals(other);
+
+ /// <summary>
+ /// Casts the handle to it's <see cref="Span{T}"/> representation
+ /// </summary>
+ /// <param name="handle">the handle to cast</param>
+ public static implicit operator Span<T>(in UnsafeMemoryHandle<T> handle) => handle.Span;
+
+ /// <summary>
+ /// Equality overload
+ /// </summary>
+ /// <param name="left"></param>
+ /// <param name="right"></param>
+ /// <returns>True if handles are equal, flase otherwise</returns>
+ public static bool operator ==(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => left.Equals(right);
+ /// <summary>
+ /// Equality overload
+ /// </summary>
+ /// <param name="left"></param>
+ /// <param name="right"></param>
+ /// <returns>True if handles are equal, flase otherwise</returns>
+ public static bool operator !=(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => !left.Equals(right);
+
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/VnString.cs b/Utils/src/Memory/VnString.cs
new file mode 100644
index 0000000..7fa0c5a
--- /dev/null
+++ b/Utils/src/Memory/VnString.cs
@@ -0,0 +1,497 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnString.cs
+*
+* VnString.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Buffers;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+
+ /// <summary>
+ /// Provides an immutable character buffer stored on an unmanged heap. Contains handles to unmanged memory, and should be disposed
+ /// </summary>
+ [ComVisible(false)]
+ [ImmutableObject(true)]
+ public sealed class VnString : VnDisposeable, IEquatable<VnString>, IEquatable<string>, IEquatable<char[]>, IComparable<VnString>, IComparable<string>
+ {
+ private readonly MemoryHandle<char>? Handle;
+
+ private readonly SubSequence<char> _stringSequence;
+
+ /// <summary>
+ /// The number of unicode characters the current instance can reference
+ /// </summary>
+ public int Length => _stringSequence.Size;
+ /// <summary>
+ /// Gets a value indicating if the current instance is empty
+ /// </summary>
+ public bool IsEmpty => Length == 0;
+
+ private VnString(SubSequence<char> sequence)
+ {
+ _stringSequence = sequence;
+ }
+
+ private VnString(
+ MemoryHandle<char> handle,
+#if TARGET_64_BIT
+ ulong start,
+#else
+ int start,
+#endif
+ int length)
+ {
+ Handle = handle ?? throw new ArgumentNullException(nameof(handle));
+ //get sequence
+ _stringSequence = handle.GetSubSequence(start, length);
+ }
+
+ /// <summary>
+ /// Creates and empty <see cref="VnString"/>, not particularly usefull, just and empty instance.
+ /// </summary>
+ public VnString()
+ {
+ //Default string sequence is empty and does not hold any memory
+ }
+ /// <summary>
+ /// Creates a new <see cref="VnString"/> around a <see cref="ReadOnlySpan{T}"/> or a <see cref="string"/> of data
+ /// </summary>
+ /// <param name="data"><see cref="ReadOnlySpan{T}"/> of data to replicate</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public VnString(ReadOnlySpan<char> data)
+ {
+ //Create new handle with enough size (heap)
+ Handle = Memory.Shared.Alloc<char>(data.Length);
+ //Copy
+ Memory.Copy(data, Handle, 0);
+ //Get subsequence over the whole copy of data
+ _stringSequence = Handle.GetSubSequence(0, data.Length);
+ }
+ /// <summary>
+ /// Allocates a temporary buffer to read data from the stream until the end of the stream is reached.
+ /// Decodes data from the user-specified encoding
+ /// </summary>
+ /// <param name="stream">Active stream of data to decode to a string</param>
+ /// <param name="encoding"><see cref="Encoding"/> to use for decoding</param>
+ /// <param name="bufferSize">The size of the buffer to allocate during copying</param>
+ /// <returns>The new <see cref="VnString"/> instance</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static VnString FromStream(Stream stream, Encoding encoding, uint bufferSize)
+ {
+ //Make sure the stream is readable
+ if (!stream.CanRead)
+ {
+ throw new InvalidOperationException();
+ }
+ //See if the stream is a vn memory stream
+ if (stream is VnMemoryStream vnms)
+ {
+ //Get the number of characters
+ int numChars = encoding.GetCharCount(vnms.AsSpan());
+ //New handle
+ MemoryHandle<char> charBuffer = Memory.Shared.Alloc<char>(numChars);
+ try
+ {
+ //Write characters to character buffer
+ _ = encoding.GetChars(vnms.AsSpan(), charBuffer);
+ //Consume the new handle
+ return ConsumeHandle(charBuffer, 0, numChars);
+ }
+ catch
+ {
+ //If an error occured, dispose the buffer
+ charBuffer.Dispose();
+ throw;
+ }
+ }
+ //Need to read from the stream old school with buffers
+ else
+ {
+ //Create a new char bufer that will expand dyanmically
+ MemoryHandle<char> charBuffer = Memory.Shared.Alloc<char>(bufferSize);
+ //Allocate a binary buffer
+ MemoryHandle<byte> binBuffer = Memory.Shared.Alloc<byte>(bufferSize);
+ try
+ {
+ int length = 0;
+ //span ref to bin buffer
+ Span<byte> buffer = binBuffer;
+ //Run in checked context for overflows
+ checked
+ {
+ do
+ {
+ //read
+ int read = stream.Read(buffer);
+ //guard
+ if (read <= 0)
+ {
+ break;
+ }
+ //Slice into only the read data
+ ReadOnlySpan<byte> readbytes = buffer[..read];
+ //get num chars
+ int numChars = encoding.GetCharCount(readbytes);
+ //Guard for overflow
+ if (((ulong)(numChars + length)) >= int.MaxValue)
+ {
+ throw new OverflowException();
+ }
+ //Re-alloc buffer
+ charBuffer.ResizeIfSmaller(length + numChars);
+ //Decode and update position
+ _= encoding.GetChars(readbytes, charBuffer.Span.Slice(length, numChars));
+ //Update char count
+ length += numChars;
+ } while (true);
+ }
+ return ConsumeHandle(charBuffer, 0, length);
+ }
+ catch
+ {
+ //Free the memory allocated
+ charBuffer.Dispose();
+ //We still want the exception to be propagated!
+ throw;
+ }
+ finally
+ {
+ //Dispose the binary buffer
+ binBuffer.Dispose();
+ }
+ }
+ }
+ /// <summary>
+ /// Creates a new Vnstring from the <see cref="MemoryHandle{T}"/> buffer provided. This function "consumes"
+ /// a handle, meaning it now takes ownsership of the the memory it points to.
+ /// </summary>
+ /// <param name="handle">The <see cref="MemoryHandle{T}"/> to consume</param>
+ /// <param name="start">The offset from the begining of the buffer marking the begining of the string</param>
+ /// <param name="length">The number of characters this string points to</param>
+ /// <returns>The new <see cref="VnString"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ConsumeHandle(
+ MemoryHandle<char> handle,
+
+#if TARGET_64_BIT
+ ulong start,
+#else
+ int start,
+#endif
+
+ int length)
+ {
+ if(length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+ if((uint)length > handle.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+ return new VnString(handle, start, length);
+ }
+ /// <summary>
+ /// Asynchronously reads data from the specified stream and uses the specified encoding
+ /// to decode the binary data to a new <see cref="VnString"/> heap character buffer.
+ /// </summary>
+ /// <param name="stream">The stream to read data from</param>
+ /// <param name="encoding">The encoding to use while decoding data</param>
+ /// <param name="heap">The <see cref="IUnmangedHeap"/> to allocate buffers from</param>
+ /// <param name="bufferSize">The size of the buffer to allocate</param>
+ /// <returns>The new <see cref="VnString"/> containing the data</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static async ValueTask<VnString> FromStreamAsync(Stream stream, Encoding encoding, IUnmangedHeap heap, int bufferSize)
+ {
+ _ = stream ?? throw new ArgumentNullException(nameof(stream));
+ _ = encoding ?? throw new ArgumentNullException(nameof(encoding));
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+
+ //Make sure the stream is readable
+ if (!stream.CanRead)
+ {
+ throw new IOException("The input stream is not readable");
+ }
+ //See if the stream is a vn memory stream
+ if (stream is VnMemoryStream vnms)
+ {
+ //Get the number of characters
+ int numChars = encoding.GetCharCount(vnms.AsSpan());
+ //New handle
+ MemoryHandle<char> charBuffer = heap.Alloc<char>(numChars);
+ try
+ {
+ //Write characters to character buffer
+ _ = encoding.GetChars(vnms.AsSpan(), charBuffer);
+ //Consume the new handle
+ return ConsumeHandle(charBuffer, 0, numChars);
+ }
+ catch
+ {
+ //If an error occured, dispose the buffer
+ charBuffer.Dispose();
+ throw;
+ }
+ }
+ else
+ {
+ //Create a new char bufer starting with the buffer size
+ MemoryHandle<char> charBuffer = heap.Alloc<char>(bufferSize);
+ //Rent a temp binary buffer
+ IMemoryOwner<byte> binBuffer = heap.DirectAlloc<byte>(bufferSize);
+ try
+ {
+ int length = 0;
+ do
+ {
+ //read async
+ int read = await stream.ReadAsync(binBuffer.Memory);
+ //guard
+ if (read <= 0)
+ {
+ break;
+ }
+ //calculate the number of characters
+ int numChars = encoding.GetCharCount(binBuffer.Memory.Span[..read]);
+ //Guard for overflow
+ if (((ulong)(numChars + length)) >= int.MaxValue)
+ {
+ throw new OverflowException();
+ }
+ //Re-alloc buffer
+ charBuffer.ResizeIfSmaller(length + numChars);
+ //Decode and update position
+ _ = encoding.GetChars(binBuffer.Memory.Span[..read], charBuffer.Span.Slice(length, numChars));
+ //Update char count
+ length += numChars;
+ } while (true);
+ return ConsumeHandle(charBuffer, 0, length);
+ }
+ catch
+ {
+ //Free the memory allocated
+ charBuffer.Dispose();
+ //We still want the exception to be propagated!
+ throw;
+ }
+ finally
+ {
+ //Dispose the binary buffer
+ binBuffer.Dispose();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the value of the character at the specified index
+ /// </summary>
+ /// <param name="index">The index of the character to get</param>
+ /// <returns>The <see cref="char"/> at the specified index within the buffer</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public char CharAt(int index)
+ {
+ //Check
+ Check();
+ //Check bounds
+ return _stringSequence.Span[index];
+ }
+
+#pragma warning disable IDE0057 // Use range operator
+ /// <summary>
+ /// Creates a <see cref="VnString"/> that is a window within the current string,
+ /// the referrence points to the same memory as the first instnace.
+ /// </summary>
+ /// <param name="start">The index within the current string to begin the child string</param>
+ /// <param name="count">The number of characters (or length) of the child string</param>
+ /// <returns>The child <see cref="VnString"/></returns>
+ /// <remarks>
+ /// Making substrings will reference the parents's underlying <see cref="MemoryHandle{T}"/>
+ /// and all children will be set in a disposed state when the parent instance is disposed
+ /// </remarks>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public VnString Substring(int start, int count)
+ {
+ //Check
+ Check();
+ //Check bounds
+ if (start < 0 || (start + count) >= Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ //get sub-sequence slice for the current string
+ SubSequence<char> sub = _stringSequence.Slice((uint)start, count);
+ //Create new string with offsets pointing to same internal referrence
+ return new VnString(sub);
+ }
+ /// <summary>
+ /// Creates a <see cref="VnString"/> that is a window within the current string,
+ /// the referrence points to the same memory as the first instnace.
+ /// </summary>
+ /// <param name="start">The index within the current string to begin the child string</param>
+ /// <returns>The child <see cref="VnString"/></returns>
+ /// <remarks>
+ /// Making substrings will reference the parents's underlying <see cref="MemoryHandle{T}"/>
+ /// and all children will be set in a disposed state when the parent instance is disposed
+ /// </remarks>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public VnString Substring(int start) => Substring(start, (Length - start));
+ public VnString this[Range range]
+ {
+ get
+ {
+ //get start
+ int start = range.Start.IsFromEnd ? Length - range.Start.Value : range.Start.Value;
+ //Get end
+ int end = range.End.IsFromEnd ? Length - range.End.Value : range.End.Value;
+ //Handle strings with no ending range
+ return (end >= start) ? Substring(start, (end - start)) : Substring(start);
+ }
+ }
+#pragma warning restore IDE0057 // Use range operator
+
+ /// <summary>
+ /// Gets a <see cref="ReadOnlySpan{T}"/> over the internal character buffer
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan<char> AsSpan()
+ {
+ //Check
+ Check();
+ return _stringSequence.Span;
+ }
+
+ /// <summary>
+ /// Gets a <see cref="string"/> copy of the internal buffer
+ /// </summary>
+ /// <returns><see cref="string"/> representation of internal data</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public override unsafe string ToString()
+ {
+ //Check
+ Check();
+ //Create a new
+ return AsSpan().ToString();
+ }
+ /// <summary>
+ /// Gets the value of the character at the specified index
+ /// </summary>
+ /// <param name="index">The index of the character to get</param>
+ /// <returns>The <see cref="char"/> at the specified index within the buffer</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public char this[int index] => CharAt(index);
+
+ //Casting to a vnstring should be explicit so the caller doesnt misuse memory managment
+ public static explicit operator ReadOnlySpan<char>(VnString? value) => Unsafe.IsNullRef(ref value) || value!.Disposed ? ReadOnlySpan<char>.Empty : value.AsSpan();
+ public static explicit operator VnString(string value) => new (value);
+ public static explicit operator VnString(ReadOnlySpan<char> value) => new (value);
+ public static explicit operator VnString(char[] value) => new (value);
+ ///<inheritdoc/>
+ public override bool Equals(object? obj)
+ {
+ if(obj == null)
+ {
+ return false;
+ }
+ return obj switch
+ {
+ VnString => Equals(obj as VnString), //Use operator overload
+ string => Equals(obj as string), //Use operator overload
+ char[] => Equals(obj as char[]), //Use operator overload
+ _ => false,
+ };
+ }
+ ///<inheritdoc/>
+ public bool Equals(VnString? other) => !ReferenceEquals(other, null) && Equals(other.AsSpan());
+ ///<inheritdoc/>
+ public bool Equals(VnString? other, StringComparison stringComparison) => !ReferenceEquals(other, null) && Equals(other.AsSpan(), stringComparison);
+ ///<inheritdoc/>
+ public bool Equals(string? other) => Equals(other.AsSpan());
+ ///<inheritdoc/>
+ public bool Equals(string? other, StringComparison stringComparison) => Equals(other.AsSpan(), stringComparison);
+ ///<inheritdoc/>
+ public bool Equals(char[]? other) => Equals(other.AsSpan());
+ ///<inheritdoc/>
+ public bool Equals(char[]? other, StringComparison stringComparison) => Equals(other.AsSpan(), stringComparison);
+ ///<inheritdoc/>
+ public bool Equals(ReadOnlySpan<char> other, StringComparison stringComparison = StringComparison.Ordinal) => Length == other.Length && AsSpan().Equals(other, stringComparison);
+ ///<inheritdoc/>
+ public bool Equals(SubSequence<char> other) => Length == other.Size && AsSpan().SequenceEqual(other.Span);
+ ///<inheritdoc/>
+ public int CompareTo(string? other) => AsSpan().CompareTo(other, StringComparison.Ordinal);
+ ///<inheritdoc/>
+ ///<exception cref="ArgumentNullException"></exception>
+ public int CompareTo(VnString? other)
+ {
+ _ = other ?? throw new ArgumentNullException(nameof(other));
+ return AsSpan().CompareTo(other.AsSpan(), StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Gets a hashcode for the underyling string by using the .NET <see cref="string.GetHashCode()"/>
+ /// method on the character representation of the data
+ /// </summary>
+ /// <returns></returns>
+ /// <remarks>
+ /// It is safe to compare hashcodes of <see cref="VnString"/> to the <see cref="string"/> class or
+ /// a character span etc
+ /// </remarks>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public override int GetHashCode() => string.GetHashCode(AsSpan());
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ //Dispose the handle if we own it (null if we do not have the parent handle)
+ Handle?.Dispose();
+ }
+
+ public static bool operator ==(VnString left, VnString right) => ReferenceEquals(left, null) ? ReferenceEquals(right, null) : left.Equals(right);
+
+ public static bool operator !=(VnString left, VnString right) => !(left == right);
+
+ public static bool operator <(VnString left, VnString right) => ReferenceEquals(left, null) ? !ReferenceEquals(right, null) : left.CompareTo(right) < 0;
+
+ public static bool operator <=(VnString left, VnString right) => ReferenceEquals(left, null) || left.CompareTo(right) <= 0;
+
+ public static bool operator >(VnString left, VnString right) => !ReferenceEquals(left, null) && left.CompareTo(right) > 0;
+
+ public static bool operator >=(VnString left, VnString right) => ReferenceEquals(left, null) ? ReferenceEquals(right, null) : left.CompareTo(right) >= 0;
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/VnTable.cs b/Utils/src/Memory/VnTable.cs
new file mode 100644
index 0000000..1d5c0a6
--- /dev/null
+++ b/Utils/src/Memory/VnTable.cs
@@ -0,0 +1,213 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnTable.cs
+*
+* VnTable.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// Provides a Row-Major ordered table for use of storing value-types in umnaged heap memory
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public sealed class VnTable<T> : VnDisposeable, IIndexable<uint, T> where T : unmanaged
+ {
+ private readonly MemoryHandle<T>? BufferHandle;
+ /// <summary>
+ /// A value that indicates if the table does not contain any values
+ /// </summary>
+ public bool Empty { get; }
+ /// <summary>
+ /// The number of rows in the table
+ /// </summary>
+ public int Rows { get; }
+ /// <summary>
+ /// The nuber of columns in the table
+ /// </summary>
+ public int Cols { get; }
+ /// <summary>
+ /// Creates a new 2 dimensional table in unmanaged heap memory, using the <see cref="Memory.Shared"/> heap.
+ /// User should dispose of the table when no longer in use
+ /// </summary>
+ /// <param name="rows">Number of rows in the table</param>
+ /// <param name="cols">Number of columns in the table</param>
+ public VnTable(int rows, int cols) : this(Memory.Shared, rows, cols) { }
+ /// <summary>
+ /// Creates a new 2 dimensional table in unmanaged heap memory, using the specified heap.
+ /// User should dispose of the table when no longer in use
+ /// </summary>
+ /// <param name="heap"><see cref="PrivateHeap"/> to allocate table memory from</param>
+ /// <param name="rows">Number of rows in the table</param>
+ /// <param name="cols">Number of columns in the table</param>
+ public VnTable(IUnmangedHeap heap, int rows, int cols)
+ {
+ if (rows < 0 || cols < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(rows), "Row and coulmn number must be 0 or larger");
+ }
+ //empty table
+ if (rows == 0 && cols == 0)
+ {
+ Empty = true;
+ return;
+ }
+
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+
+ this.Rows = rows;
+ this.Cols = cols;
+
+ long tableSize = Math.BigMul(rows, cols);
+
+ //Alloc a buffer with zero memory enabled, with Rows * Cols number of elements
+ BufferHandle = heap.Alloc<T>(tableSize, true);
+ }
+ /// <summary>
+ /// Gets the value of an item in the table at the given indexes
+ /// </summary>
+ /// <param name="row">Row address of the item</param>
+ /// <param name="col">Column address of item</param>
+ /// <returns>The value of the item</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public T Get(int row, int col)
+ {
+ Check();
+ if (this.Empty)
+ {
+ throw new InvalidOperationException("Table is empty");
+ }
+ if (row < 0 || col < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), "Row or column address less than 0");
+ }
+ if (row > this.Rows)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), "Row out of range of current table");
+ }
+ if (col > this.Cols)
+ {
+ throw new ArgumentOutOfRangeException(nameof(col), "Column address out of range of current table");
+ }
+ //Calculate the address in memory for the item
+ //Calc row offset
+ long address = Cols * row;
+ //Calc column offset
+ address += col;
+ unsafe
+ {
+ //Get the value item
+ return *(BufferHandle!.GetOffset(address));
+ }
+ }
+ /// <summary>
+ /// Sets the value of an item in the table at the given address
+ /// </summary>
+ /// <param name="item">Value of item to store</param>
+ /// <param name="row">Row address of the item</param>
+ /// <param name="col">Column address of item</param>
+ /// <returns>The value of the item</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public void Set(int row, int col, T item)
+ {
+ Check();
+ if (this.Empty)
+ {
+ throw new InvalidOperationException("Table is empty");
+ }
+ if (row < 0 || col < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), "Row or column address less than 0");
+ }
+ if (row > this.Rows)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), "Row out of range of current table");
+ }
+ if (col > this.Cols)
+ {
+ throw new ArgumentOutOfRangeException(nameof(col), "Column address out of range of current table");
+ }
+ //Calculate the address in memory for the item
+ //Calc row offset
+ long address = Cols * row;
+ //Calc column offset
+ address += col;
+ //Set the value item
+ unsafe
+ {
+ *BufferHandle!.GetOffset(address) = item;
+ }
+ }
+ /// <summary>
+ /// Equivalent to <see cref="VnTable{T}.Get(int, int)"/> and <see cref="VnTable{T}.Set(int, int, T)"/>
+ /// </summary>
+ /// <param name="row">Row address of item</param>
+ /// <param name="col">Column address of item</param>
+ /// <returns>The value of the item</returns>
+ public T this[int row, int col]
+ {
+ get => Get(row, col);
+ set => Set(row, col, value);
+ }
+ /// <summary>
+ /// Allows for direct addressing in the table.
+ /// </summary>
+ /// <param name="index"></param>
+ /// <returns></returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public unsafe T this[uint index]
+ {
+ get
+ {
+ Check();
+ return !Empty ? *(BufferHandle!.GetOffset(index)) : throw new InvalidOperationException("Cannot index an empty table");
+ }
+
+ set
+ {
+ Check();
+ if (Empty)
+ {
+ throw new InvalidOperationException("Cannot index an empty table");
+ }
+ *(BufferHandle!.GetOffset(index)) = value;
+ }
+ }
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ if (!this.Empty)
+ {
+ //Dispose the buffer
+ BufferHandle!.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Memory/VnTempBuffer.cs b/Utils/src/Memory/VnTempBuffer.cs
new file mode 100644
index 0000000..7726fe1
--- /dev/null
+++ b/Utils/src/Memory/VnTempBuffer.cs
@@ -0,0 +1,207 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnTempBuffer.cs
+*
+* VnTempBuffer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Extensions;
+using System.Security.Cryptography;
+
+namespace VNLib.Utils.Memory
+{
+ /// <summary>
+ /// A disposable temporary buffer from shared ArrayPool
+ /// </summary>
+ /// <typeparam name="T">Type of buffer to create</typeparam>
+ public sealed class VnTempBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>
+ {
+ private readonly ArrayPool<T> Pool;
+
+ /// <summary>
+ /// Referrence to internal buffer
+ /// </summary>
+ public T[] Buffer { get; private set; }
+ /// <summary>
+ /// Inital/desired size of internal buffer
+ /// </summary>
+ public int InitSize { get; }
+
+ /// <summary>
+ /// Actual length of internal buffer
+ /// </summary>
+ public ulong Length => (ulong)Buffer.LongLength;
+
+ /// <summary>
+ /// Actual length of internal buffer
+ /// </summary>
+ public int IntLength => Buffer.Length;
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public Span<T> Span
+ {
+ get
+ {
+ Check();
+ return new Span<T>(Buffer, 0, InitSize);
+ }
+ }
+
+ /// <summary>
+ /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from shared array-pool
+ /// </summary>
+ /// <param name="minSize">Minimum size of the buffer</param>
+ /// <param name="zero">Set the zero memory flag on close</param>
+ public VnTempBuffer(int minSize, bool zero = false) :this(ArrayPool<T>.Shared, minSize, zero)
+ {}
+ /// <summary>
+ /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from specified array-pool
+ /// </summary>
+ /// <param name="pool">The <see cref="ArrayPool{T}"/> to allocate from and return to</param>
+ /// <param name="minSize">Minimum size of the buffer</param>
+ /// <param name="zero">Set the zero memory flag on close</param>
+ public VnTempBuffer(ArrayPool<T> pool, int minSize, bool zero = false)
+ {
+ Pool = pool;
+ Buffer = pool.Rent(minSize, zero);
+ InitSize = minSize;
+ }
+ /// <summary>
+ /// Gets an offset wrapper around the current buffer
+ /// </summary>
+ /// <param name="offset">Offset from begining of current buffer</param>
+ /// <param name="count">Number of <typeparamref name="T"/> from offset</param>
+ /// <returns>An <see cref="ArraySegment{BufType}"/> wrapper around the current buffer containing the offset</returns>
+ public ArraySegment<T> GetOffsetWrapper(int offset, int count)
+ {
+ Check();
+ //Let arraysegment throw exceptions for checks
+ return new ArraySegment<T>(Buffer, offset, count);
+ }
+ ///<inheritdoc/>
+ public T this[int index]
+ {
+ get
+ {
+ Check();
+ return Buffer[index];
+ }
+ set
+ {
+ Check();
+ Buffer[index] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets a memory structure around the internal buffer
+ /// </summary>
+ /// <returns>A memory structure over the buffer</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public Memory<T> AsMemory()
+ {
+ Check();
+ return new Memory<T>(Buffer, 0, InitSize);
+ }
+ /// <summary>
+ /// Gets a memory structure around the internal buffer
+ /// </summary>
+ /// <param name="count">The number of elements included in the result</param>
+ /// <param name="start">A value specifying the begining index of the buffer to include</param>
+ /// <returns>A memory structure over the buffer</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public Memory<T> AsMemory(int start, int count)
+ {
+ Check();
+ return new Memory<T>(Buffer, start, count);
+ }
+ /// <summary>
+ /// Gets a memory structure around the internal buffer
+ /// </summary>
+ /// <param name="count">The number of elements included in the result</param>
+ /// <returns>A memory structure over the buffer</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public Memory<T> AsMemory(int count)
+ {
+ Check();
+ return new Memory<T>(Buffer, 0, count);
+ }
+
+ /*
+ * Allow implict casts to span/arrayseg/memory
+ */
+ public static implicit operator Memory<T>(VnTempBuffer<T> buf) => buf == null ? Memory<T>.Empty : buf.ToMemory();
+ public static implicit operator Span<T>(VnTempBuffer<T> buf) => buf == null ? Span<T>.Empty : buf.ToSpan();
+ public static implicit operator ArraySegment<T>(VnTempBuffer<T> buf) => buf == null ? ArraySegment<T>.Empty : buf.ToArraySegment();
+
+ public Memory<T> ToMemory() => Disposed ? Memory<T>.Empty : Buffer.AsMemory(0, InitSize);
+ public Span<T> ToSpan() => Disposed ? Span<T>.Empty : Buffer.AsSpan(0, InitSize);
+ public ArraySegment<T> ToArraySegment() => Disposed ? ArraySegment<T>.Empty : new(Buffer, 0, InitSize);
+
+ /// <summary>
+ /// Returns buffer to shared array-pool
+ /// </summary>
+ protected override void Free()
+ {
+ //Return the buffer to the array pool
+ Pool.Return(Buffer);
+
+ //Set buffer to null,
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+ Buffer = null;
+#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+ }
+
+ unsafe MemoryHandle IPinnable.Pin(int elementIndex)
+ {
+ //Guard
+ if (elementIndex < 0 || elementIndex >= IntLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elementIndex));
+ }
+
+ //Pin the array
+ GCHandle arrHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned);
+ //Get array base address
+ void* basePtr = (void*)arrHandle.AddrOfPinnedObject();
+ //Get element offset
+ void* indexOffet = Unsafe.Add<T>(basePtr, elementIndex);
+ return new(indexOffet, arrHandle, this);
+ }
+
+ void IPinnable.Unpin()
+ {
+ //Gchandle will manage the unpin
+ }
+
+ ~VnTempBuffer() => Free();
+
+
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Native/SafeLibraryHandle.cs b/Utils/src/Native/SafeLibraryHandle.cs
new file mode 100644
index 0000000..4772bd4
--- /dev/null
+++ b/Utils/src/Native/SafeLibraryHandle.cs
@@ -0,0 +1,220 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SafeLibraryHandle.cs
+*
+* SafeLibraryHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Native
+{
+ /// <summary>
+ /// Represents a safe handle to a native library loaded to the current process
+ /// </summary>
+ public sealed class SafeLibraryHandle : SafeHandle
+ {
+ ///<inheritdoc/>
+ public override bool IsInvalid => handle == IntPtr.Zero;
+
+ private SafeLibraryHandle(IntPtr libHandle) : base(IntPtr.Zero, true)
+ {
+ //Init handle
+ SetHandle(libHandle);
+ }
+
+ /// <summary>
+ /// Finds and loads the specified native libary into the current process by its name at runtime
+ /// </summary>
+ /// <param name="libPath">The path (or name of libary) to search for</param>
+ /// <param name="searchPath">
+ /// The <see cref="DllImportSearchPath"/> used to search for libaries
+ /// within the current filesystem
+ /// </param>
+ /// <returns>The loaded <see cref="SafeLibraryHandle"/></returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="DllNotFoundException"></exception>
+ public static SafeLibraryHandle LoadLibrary(string libPath, DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory)
+ {
+ _ = libPath ?? throw new ArgumentNullException(nameof(libPath));
+ //See if the path includes a file extension
+ return TryLoadLibrary(libPath, searchPath, out SafeLibraryHandle? lib)
+ ? lib
+ : throw new DllNotFoundException($"The library {libPath} or one of its dependencies could not be found");
+ }
+
+ /// <summary>
+ /// Attempts to load the specified native libary into the current process by its name at runtime
+ /// </summary>
+ ///<param name="libPath">The path (or name of libary) to search for</param>
+ /// <param name="searchPath">
+ /// The <see cref="DllImportSearchPath"/> used to search for libaries
+ /// within the current filesystem
+ /// </param>
+ /// <param name="lib">The handle to the libary if successfully loaded</param>
+ /// <returns>True if the libary was found and loaded into the current process</returns>
+ public static bool TryLoadLibrary(string libPath, DllImportSearchPath searchPath, [NotNullWhen(true)] out SafeLibraryHandle? lib)
+ {
+ lib = null;
+ //Allow full rooted paths
+ if (Path.IsPathRooted(libPath))
+ {
+ //Attempt a native load
+ if (NativeLibrary.TryLoad(libPath, out IntPtr libHandle))
+ {
+ lib = new(libHandle);
+ return true;
+ }
+ return false;
+ }
+ //Check application directory first (including subdirectories)
+ if ((searchPath & DllImportSearchPath.ApplicationDirectory) > 0)
+ {
+ //get the current directory
+ string libDir = Directory.GetCurrentDirectory();
+ if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib))
+ {
+ return true;
+ }
+ }
+ //See if search in the calling assembly directory
+ if ((searchPath & DllImportSearchPath.AssemblyDirectory) > 0)
+ {
+ //Get the calling assmblies directory
+ string libDir = Assembly.GetCallingAssembly().Location;
+ Debug.WriteLine("Native library searching for calling assembly location:{0} ", libDir);
+ if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib))
+ {
+ return true;
+ }
+ }
+ //Search system32 dir
+ if ((searchPath & DllImportSearchPath.System32) > 0)
+ {
+ //Get the system directory
+ string libDir = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86);
+ if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib))
+ {
+ return true;
+ }
+ }
+ //Attempt a native load
+ {
+ if (NativeLibrary.TryLoad(libPath, out IntPtr libHandle))
+ {
+ lib = new(libHandle);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private static bool TryLoadLibraryInternal(string libDir, string libPath, SearchOption dirSearchOptions, [NotNullWhen(true)] out SafeLibraryHandle? libary)
+ {
+ //Try to find the libary file
+ string? libFile = GetLibraryFile(libDir, libPath, dirSearchOptions);
+ //Load libary
+ if (libFile != null && NativeLibrary.TryLoad(libFile, out IntPtr libHandle))
+ {
+ libary = new SafeLibraryHandle(libHandle);
+ return true;
+ }
+ libary = null;
+ return false;
+ }
+ private static string? GetLibraryFile(string dirPath, string libPath, SearchOption search)
+ {
+ //slice the lib to its file name
+ libPath = Path.GetFileName(libPath);
+ libPath = Path.ChangeExtension(libPath, OperatingSystem.IsWindows() ? ".dll" : ".so");
+ //Select the first file that matches the name
+ return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault();
+ }
+
+ /// <summary>
+ /// Loads a native method from the library of the specified name and managed delegate
+ /// </summary>
+ /// <typeparam name="T">The native method delegate type</typeparam>
+ /// <param name="methodName">The name of the native method</param>
+ /// <returns>A wapper handle around the native method delegate</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException">If the handle is closed or invalid</exception>
+ /// <exception cref="EntryPointNotFoundException">When the specified entrypoint could not be found</exception>
+ public SafeMethodHandle<T> GetMethod<T>(string methodName) where T : Delegate
+ {
+ //Increment handle count before obtaining a method
+ bool success = false;
+ DangerousAddRef(ref success);
+ if (!success)
+ {
+ throw new ObjectDisposedException("The libary has been released!");
+ }
+ try
+ {
+ //Get the method pointer
+ IntPtr nativeMethod = NativeLibrary.GetExport(handle, methodName);
+ //Get the delegate for the function pointer
+ T method = Marshal.GetDelegateForFunctionPointer<T>(nativeMethod);
+ return new(this, method);
+ }
+ catch
+ {
+ DangerousRelease();
+ throw;
+ }
+ }
+ /// <summary>
+ /// Gets an delegate wrapper for the specified method without tracking its referrence.
+ /// The caller must manage the <see cref="SafeLibraryHandle"/> referrence count in order
+ /// to not leak resources or cause process corruption
+ /// </summary>
+ /// <typeparam name="T">The native method delegate type</typeparam>
+ /// <param name="methodName">The name of the native method</param>
+ /// <returns>A the delegate wrapper on the native method</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException">If the handle is closed or invalid</exception>
+ /// <exception cref="EntryPointNotFoundException">When the specified entrypoint could not be found</exception>
+ public T DangerousGetMethod<T>(string methodName) where T : Delegate
+ {
+ this.ThrowIfClosed();
+ //Get the method pointer
+ IntPtr nativeMethod = NativeLibrary.GetExport(handle, methodName);
+ //Get the delegate for the function pointer
+ return Marshal.GetDelegateForFunctionPointer<T>(nativeMethod);
+ }
+
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle()
+ {
+ //Free the library and set the handle as invalid
+ NativeLibrary.Free(handle);
+ SetHandleAsInvalid();
+ return true;
+ }
+ }
+}
diff --git a/Utils/src/Native/SafeMethodHandle.cs b/Utils/src/Native/SafeMethodHandle.cs
new file mode 100644
index 0000000..3ba0879
--- /dev/null
+++ b/Utils/src/Native/SafeMethodHandle.cs
@@ -0,0 +1,61 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: SafeMethodHandle.cs
+*
+* SafeMethodHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Resources;
+
+namespace VNLib.Utils.Native
+{
+ /// <summary>
+ /// Represents a handle to a <see cref="SafeLibraryHandle"/>'s
+ /// native method
+ /// </summary>
+ /// <typeparam name="T">The native method deelgate type</typeparam>
+ public class SafeMethodHandle<T> : OpenHandle where T : Delegate
+ {
+ private T? _method;
+ private readonly SafeLibraryHandle Library;
+
+ internal SafeMethodHandle(SafeLibraryHandle lib, T method)
+ {
+ Library = lib;
+ _method = method;
+ }
+
+ /// <summary>
+ /// A delegate to the native method
+ /// </summary>
+ public T? Method => _method;
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ //Release the method
+ _method = default;
+ //Decrement lib handle count
+ Library.DangerousRelease();
+ }
+ }
+}
diff --git a/Utils/src/NativeLibraryException.cs b/Utils/src/NativeLibraryException.cs
new file mode 100644
index 0000000..5c66852
--- /dev/null
+++ b/Utils/src/NativeLibraryException.cs
@@ -0,0 +1,89 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: NativeLibraryException.cs
+*
+* NativeLibraryException.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Raised when an internal buffer was not propery sized for the opreation
+ /// </summary>
+ public class InternalBufferTooSmallException : OutOfMemoryException
+ {
+ public InternalBufferTooSmallException(string message) : base(message)
+ {}
+
+ public InternalBufferTooSmallException(string message, Exception innerException) : base(message, innerException)
+ {}
+
+ public InternalBufferTooSmallException()
+ {}
+ }
+
+ /// <summary>
+ /// A base class for all native library related exceptions
+ /// </summary>
+ public class NativeLibraryException : SystemException
+ {
+ public NativeLibraryException(string message) : base(message)
+ {}
+
+ public NativeLibraryException(string message, Exception innerException) : base(message, innerException)
+ {}
+
+ public NativeLibraryException()
+ {}
+ }
+
+ /// <summary>
+ /// Base exception class for native memory related exceptions
+ /// </summary>
+ public class NativeMemoryException : NativeLibraryException
+ {
+ public NativeMemoryException(string message) : base(message)
+ {}
+
+ public NativeMemoryException(string message, Exception innerException) : base(message, innerException)
+ {}
+
+ public NativeMemoryException()
+ {}
+ }
+
+ /// <summary>
+ /// Raised when a memory allocation or resize failed because there is
+ /// no more memory available
+ /// </summary>
+ public class NativeMemoryOutOfMemoryException : OutOfMemoryException
+ {
+ public NativeMemoryOutOfMemoryException(string message) : base(message)
+ {}
+
+ public NativeMemoryOutOfMemoryException(string message, Exception innerException) : base(message, innerException)
+ {}
+
+ public NativeMemoryOutOfMemoryException()
+ {}
+ }
+}
diff --git a/Utils/src/Resources/BackedResourceBase.cs b/Utils/src/Resources/BackedResourceBase.cs
new file mode 100644
index 0000000..0a2e1e3
--- /dev/null
+++ b/Utils/src/Resources/BackedResourceBase.cs
@@ -0,0 +1,79 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: BackedResourceBase.cs
+*
+* BackedResourceBase.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Text.Json;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// A base class for a resource that is backed by an external data store.
+ /// Implements the <see cref="IResource"/> interfaceS
+ /// </summary>
+ public abstract class BackedResourceBase : IResource
+ {
+ ///<inheritdoc/>
+ public bool IsReleased { get; protected set; }
+
+ /// <summary>
+ /// Optional <see cref="JsonSerializerOptions"/> to be used when serializing
+ /// the resource
+ /// </summary>
+ internal protected virtual JsonSerializerOptions? JSO { get; }
+
+ /// <summary>
+ /// A value indicating whether the instance should be deleted when released
+ /// </summary>
+ protected bool Deleted { get; set; }
+ /// <summary>
+ /// A value indicating whether the instance should be updated when released
+ /// </summary>
+ protected bool Modified { get; set; }
+
+ /// <summary>
+ /// Checks if the resouce has been disposed and raises an exception if it is
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void Check()
+ {
+ if (IsReleased)
+ {
+ throw new ObjectDisposedException("The resource has been disposed");
+ }
+ }
+
+ /// <summary>
+ /// Returns the JSON serializable resource to be updated during an update
+ /// </summary>
+ /// <returns>The resource to update</returns>
+ protected abstract object GetResource();
+
+ /// <summary>
+ /// Marks the resource for deletion from backing store during closing events
+ /// </summary>
+ public virtual void Delete() => Deleted = true;
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/CallbackOpenHandle.cs b/Utils/src/Resources/CallbackOpenHandle.cs
new file mode 100644
index 0000000..625bd45
--- /dev/null
+++ b/Utils/src/Resources/CallbackOpenHandle.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: CallbackOpenHandle.cs
+*
+* CallbackOpenHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// A concrete <see cref="OpenHandle"/> for a defered operation or a resource that should be released or unwound
+ /// when the instance lifetime has ended.
+ /// </summary>
+ public sealed class CallbackOpenHandle : OpenHandle
+ {
+ private readonly Action ReleaseFunc;
+ /// <summary>
+ /// Creates a new generic <see cref="OpenHandle"/> with the specified release callback method
+ /// </summary>
+ /// <param name="release">The callback function to invoke when the <see cref="OpenHandle"/> is disposed</param>
+ public CallbackOpenHandle(Action release) => ReleaseFunc = release;
+ ///<inheritdoc/>
+ protected override void Free() => ReleaseFunc();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/ExclusiveResourceHandle.cs b/Utils/src/Resources/ExclusiveResourceHandle.cs
new file mode 100644
index 0000000..173bdd1
--- /dev/null
+++ b/Utils/src/Resources/ExclusiveResourceHandle.cs
@@ -0,0 +1,81 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ExclusiveResourceHandle.cs
+*
+* ExclusiveResourceHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// While in scope, holds an exclusive lock on the specified object that implements the <see cref="IExclusiveResource"/> interface
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public class ExclusiveResourceHandle<T> : OpenResourceHandle<T> where T : IExclusiveResource
+ {
+ private readonly Action Release;
+ private readonly Lazy<T> LazyVal;
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// <br></br>
+ /// <br></br>
+ /// This value is lazy inialized and will invoke the factory function on first access.
+ /// Accessing this variable is thread safe while the handle is in scope
+ /// <br></br>
+ /// <br></br>
+ /// Exceptions will be propagated during initialziation
+ /// </summary>
+ public override T Resource => LazyVal.Value;
+
+ /// <summary>
+ /// Creates a new <see cref="ExclusiveResourceHandle{TResource}"/> wrapping the
+ /// <see cref="IExclusiveResource"/> object to manage its lifecycle and reuse
+ /// </summary>
+ /// <param name="factory">Factory function that will generate the value when used</param>
+ /// <param name="release">Callback function that will be invoked after object gets disposed</param>
+ internal ExclusiveResourceHandle(Func<T> factory, Action release)
+ {
+ //Store the release action
+ Release = release;
+ //Store the new lazy val from the factory function (enabled thread safey)
+ LazyVal = new(factory, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
+ }
+
+ protected override void Free()
+ {
+ try
+ {
+ //Dispose the value if it was created, otherwise do not create it
+ if (LazyVal.IsValueCreated)
+ {
+ Resource?.Release();
+ }
+ }
+ finally
+ {
+ //Always invoke the release callback
+ Release();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/IExclusiveResource.cs b/Utils/src/Resources/IExclusiveResource.cs
new file mode 100644
index 0000000..43ec607
--- /dev/null
+++ b/Utils/src/Resources/IExclusiveResource.cs
@@ -0,0 +1,39 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IExclusiveResource.cs
+*
+* IExclusiveResource.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils.Resources
+{
+
+ /// <summary>
+ /// An object, that when used in a mulithreading context, guaruntees that the caller has exclusive
+ /// access to the instance and relinquishes exclusive access when Release() is called;
+ /// </summary>
+ public interface IExclusiveResource : IResource
+ {
+ /// <summary>
+ /// Releases the resource from use. Called when a <see cref="ExclusiveResourceHandle{T}"/> is disposed
+ /// </summary>
+ void Release();
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/IResource.cs b/Utils/src/Resources/IResource.cs
new file mode 100644
index 0000000..345e284
--- /dev/null
+++ b/Utils/src/Resources/IResource.cs
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IResource.cs
+*
+* IResource.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// The base interface for any resource that may be backed by an external
+ /// data store, and has a finite lifetime, usually accessed within a handle
+ /// </summary>
+ public interface IResource
+ {
+ /// <summary>
+ /// Gets a value indicating if the resource has been released
+ /// </summary>
+ bool IsReleased { get; }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/OpenHandle.cs b/Utils/src/Resources/OpenHandle.cs
new file mode 100644
index 0000000..6133a65
--- /dev/null
+++ b/Utils/src/Resources/OpenHandle.cs
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: OpenHandle.cs
+*
+* OpenHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// Represents a base class for an open resource or operation that is valid while being held,
+ /// and is released or unwound when disposed.
+ /// </summary>
+ /// <remarks>
+ /// The <see cref="OpenHandle"/> pattern, may throw exceptions when disposed as deferred
+ /// release actions are completed
+ /// </remarks>
+ public abstract class OpenHandle : VnDisposeable
+ {
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/OpenResourceHandle.cs b/Utils/src/Resources/OpenResourceHandle.cs
new file mode 100644
index 0000000..d9f9fd2
--- /dev/null
+++ b/Utils/src/Resources/OpenResourceHandle.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: OpenResourceHandle.cs
+*
+* OpenResourceHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// <para>
+ /// An abstract base class for an <see cref="OpenHandle"/> that holds a specific resource and manages its lifetime.
+ /// </para>
+ /// </summary>
+ /// <inheritdoc/>
+ /// <typeparam name="TResource">The resource type</typeparam>
+ public abstract class OpenResourceHandle<TResource> : OpenHandle
+ {
+ /// <summary>
+ /// The resource held by the open handle
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public abstract TResource Resource { get; }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/ResourceDeleteFailedException.cs b/Utils/src/Resources/ResourceDeleteFailedException.cs
new file mode 100644
index 0000000..8e796b5
--- /dev/null
+++ b/Utils/src/Resources/ResourceDeleteFailedException.cs
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ResourceDeleteFailedException.cs
+*
+* ResourceDeleteFailedException.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.Serialization;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// Raised when a resource delete has failed
+ /// </summary>
+ public class ResourceDeleteFailedException : Exception
+ {
+ public ResourceDeleteFailedException() { }
+ public ResourceDeleteFailedException(string message) : base(message) { }
+ public ResourceDeleteFailedException(string message, Exception innerException) : base(message, innerException) { }
+ protected ResourceDeleteFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/ResourceUpdateFailedException.cs b/Utils/src/Resources/ResourceUpdateFailedException.cs
new file mode 100644
index 0000000..b4b2b3a
--- /dev/null
+++ b/Utils/src/Resources/ResourceUpdateFailedException.cs
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ResourceUpdateFailedException.cs
+*
+* ResourceUpdateFailedException.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.Serialization;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// Raised when a resource update has failed
+ /// </summary>
+ public class ResourceUpdateFailedException : Exception
+ {
+ public ResourceUpdateFailedException() { }
+ public ResourceUpdateFailedException(string message) : base(message) { }
+ public ResourceUpdateFailedException(string message, Exception innerException) : base(message, innerException) { }
+ protected ResourceUpdateFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/Resources/UpdatableResource.cs b/Utils/src/Resources/UpdatableResource.cs
new file mode 100644
index 0000000..16f26f2
--- /dev/null
+++ b/Utils/src/Resources/UpdatableResource.cs
@@ -0,0 +1,113 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: UpdatableResource.cs
+*
+* UpdatableResource.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+
+using VNLib.Utils.IO;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// A callback delegate used for updating a <see cref="UpdatableResource"/>
+ /// </summary>
+ /// <param name="source">The <see cref="UpdatableResource"/> to be updated</param>
+ /// <param name="data">The serialized data to be stored/updated</param>
+ /// <exception cref="ResourceUpdateFailedException"></exception>
+ public delegate void UpdateCallback(object source, Stream data);
+ /// <summary>
+ /// A callback delegate invoked when a <see cref="UpdatableResource"/> delete is requested
+ /// </summary>
+ /// <param name="source">The <see cref="UpdatableResource"/> to be deleted</param>
+ /// <exception cref="ResourceDeleteFailedException"></exception>
+ public delegate void DeleteCallback(object source);
+
+ /// <summary>
+ /// Implemented by a resource that is backed by an external data store, that when modified or deleted will
+ /// be reflected to the backing store.
+ /// </summary>
+ public abstract class UpdatableResource : BackedResourceBase, IExclusiveResource
+ {
+ /// <summary>
+ /// The update callback method to invoke during a release operation
+ /// when the resource is updated.
+ /// </summary>
+ protected abstract UpdateCallback UpdateCb { get; }
+ /// <summary>
+ /// The callback method to invoke during a realease operation
+ /// when the resource should be deleted
+ /// </summary>
+ protected abstract DeleteCallback DeleteCb { get; }
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// </summary>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="ResourceDeleteFailedException"></exception>
+ /// <exception cref="ResourceUpdateFailedException"></exception>
+ public virtual void Release()
+ {
+ //If resource has already been realeased, return
+ if (IsReleased)
+ {
+ return;
+ }
+ //If deleted flag is set, invoke the delete callback
+ if (Deleted)
+ {
+ DeleteCb(this);
+ }
+ //If the state has been modifed, flush changes to the store
+ else if (Modified)
+ {
+ FlushPendingChanges();
+ }
+ //Set the released value
+ IsReleased = true;
+ }
+
+ /// <summary>
+ /// Writes the current state of the the resource to the backing store
+ /// immediatly by invoking the specified callback.
+ /// <br></br>
+ /// <br></br>
+ /// Only call this method if your store supports multiple state updates
+ /// </summary>
+ protected virtual void FlushPendingChanges()
+ {
+ //Get the resource
+ object resource = GetResource();
+ //Open a memory stream to store data in
+ using VnMemoryStream data = new();
+ //Serialize and write to stream
+ VnEncoding.JSONSerializeToBinary(resource, data, resource.GetType(), JSO);
+ //Reset stream to begining
+ _ = data.Seek(0, SeekOrigin.Begin);
+ //Invoke update callback
+ UpdateCb(this, data);
+ //Clear modified flag
+ Modified = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/VNLib.Utils.csproj b/Utils/src/VNLib.Utils.csproj
new file mode 100644
index 0000000..b14ab27
--- /dev/null
+++ b/Utils/src/VNLib.Utils.csproj
@@ -0,0 +1,47 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <RootNamespace>VNLib.Utils</RootNamespace>
+ <Authors>Vaughn Nugent</Authors>
+ <Product>VNLib Utilities Library</Product>
+ <Copyright>Copyright © 2022 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
+ <AssemblyName>VNLib.Utils</AssemblyName>
+ <Version>1.0.1.10</Version>
+ <Description>Base utilities library, structs, classes</Description>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <Nullable>enable</Nullable>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <SignAssembly>True</SignAssembly>
+ <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Include="..\.editorconfig" Link=".editorconfig" />
+ </ItemGroup>
+
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+ <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+</Project>
diff --git a/Utils/src/VnDisposeable.cs b/Utils/src/VnDisposeable.cs
new file mode 100644
index 0000000..4230a13
--- /dev/null
+++ b/Utils/src/VnDisposeable.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnDisposeable.cs
+*
+* VnDisposeable.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Provides a base class with abstract methods for for disposable objects, with disposed check method
+ /// </summary>
+ public abstract class VnDisposeable : IDisposable
+ {
+ ///<inheritdoc/>
+ protected bool Disposed { get; private set; }
+
+ /// <summary>
+ /// When overriden in a child class, is responsible for freeing resources
+ /// </summary>
+ protected abstract void Free();
+
+ /// <summary>
+ /// Checks if the current object has been disposed. Method will be inlined where possible
+ /// </summary>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual void Check()
+ {
+ if (Disposed)
+ {
+ throw new ObjectDisposedException("Object has been disposed");
+ }
+ }
+
+ /// <summary>
+ /// Sets the internal state to diposed without calling <see cref="Free"/> operation.
+ /// Usefull if another code-path performs the free operation independant of a dispose opreation.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void SetDisposedState() => Disposed = true;
+ ///<inheritdoc/>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!Disposed)
+ {
+ if (disposing)
+ {
+ //Call free method
+ Free();
+ }
+ Disposed = true;
+ }
+ }
+ //Finalizer is not needed here
+
+ ///<inheritdoc/>
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Utils/src/VnEncoding.cs b/Utils/src/VnEncoding.cs
new file mode 100644
index 0000000..94d8a1a
--- /dev/null
+++ b/Utils/src/VnEncoding.cs
@@ -0,0 +1,914 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnEncoding.cs
+*
+* VnEncoding.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Buffers;
+using System.Text.Json;
+using System.Threading;
+using System.Buffers.Text;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Contains static methods for encoding data
+ /// </summary>
+ public static class VnEncoding
+ {
+ /// <summary>
+ /// Encodes a <see cref="ReadOnlySpan{T}"/> with the specified <see cref="Encoding"/> to a <see cref="VnMemoryStream"/> that must be disposed by the user
+ /// </summary>
+ /// <param name="data">Data to be encoded</param>
+ /// <param name="encoding"><see cref="Encoding"/> to encode data with</param>
+ /// <returns>A <see cref="Stream"/> contating the encoded data</returns>
+ public static VnMemoryStream GetMemoryStream(in ReadOnlySpan<char> data, Encoding encoding)
+ {
+ _ = encoding ?? throw new ArgumentNullException(nameof(encoding));
+ //Create new memory handle to copy data to
+ MemoryHandle<byte>? handle = null;
+ try
+ {
+ //get number of bytes
+ int byteCount = encoding.GetByteCount(data);
+ //resize the handle to fit the data
+ handle = Memory.Memory.Shared.Alloc<byte>(byteCount);
+ //encode
+ int size = encoding.GetBytes(data, handle);
+ //Consume the handle into a new vnmemstream and return it
+ return VnMemoryStream.ConsumeHandle(handle, size, true);
+ }
+ catch
+ {
+ //Dispose the handle if there is an excpetion
+ handle?.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Attempts to deserialze a json object from a stream of UTF8 data
+ /// </summary>
+ /// <typeparam name="T">The type of the object to deserialize</typeparam>
+ /// <param name="data">Binary data to read from</param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to deserializer</param>
+ /// <returns>The object decoded from the stream</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static T? JSONDeserializeFromBinary<T>(Stream? data, JsonSerializerOptions? options = null)
+ {
+ //Return default if null
+ if (data == null)
+ {
+ return default;
+ }
+ //Create a memory stream as a buffer
+ using VnMemoryStream ms = new();
+ //Copy stream data to memory
+ data.CopyTo(ms, null);
+ if (ms.Length > 0)
+ {
+ //Rewind
+ ms.Position = 0;
+ //Recover data from stream
+ return ms.AsSpan().AsJsonObject<T>(options);
+ }
+ //Stream is empty
+ return default;
+ }
+ /// <summary>
+ /// Attempts to deserialze a json object from a stream of UTF8 data
+ /// </summary>
+ /// <param name="data">Binary data to read from</param>
+ /// <param name="type"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to deserializer</param>
+ /// <returns>The object decoded from the stream</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static object? JSONDeserializeFromBinary(Stream? data, Type type, JsonSerializerOptions? options = null)
+ {
+ //Return default if null
+ if (data == null)
+ {
+ return default;
+ }
+ //Create a memory stream as a buffer
+ using VnMemoryStream ms = new();
+ //Copy stream data to memory
+ data.CopyTo(ms, null);
+ if (ms.Length > 0)
+ {
+ //Rewind
+ ms.Position = 0;
+ //Recover data from stream
+ return JsonSerializer.Deserialize(ms.AsSpan(), type, options);
+ }
+ //Stream is empty
+ return default;
+ }
+ /// <summary>
+ /// Attempts to deserialze a json object from a stream of UTF8 data
+ /// </summary>
+ /// <typeparam name="T">The type of the object to deserialize</typeparam>
+ /// <param name="data">Binary data to read from</param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to deserializer</param>
+ /// <param name="cancellationToken"></param>
+ /// <returns>The object decoded from the stream</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static ValueTask<T?> JSONDeserializeFromBinaryAsync<T>(Stream? data, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ //Return default if null
+ return data == null || data.Length == 0 ? ValueTask.FromResult<T?>(default) : JsonSerializer.DeserializeAsync<T>(data, options, cancellationToken);
+ }
+ /// <summary>
+ /// Attempts to deserialze a json object from a stream of UTF8 data
+ /// </summary>
+ /// <param name="data">Binary data to read from</param>
+ /// <param name="type"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to deserializer</param>
+ /// <param name="cancellationToken"></param>
+ /// <returns>The object decoded from the stream</returns>
+ /// <exception cref="JsonException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ public static ValueTask<object?> JSONDeserializeFromBinaryAsync(Stream? data, Type type, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ //Return default if null
+ return data == null || data.Length == 0 ? ValueTask.FromResult<object?>(default) : JsonSerializer.DeserializeAsync(data, type, options, cancellationToken);
+ }
+ /// <summary>
+ /// Attempts to serialize the object to json and write the encoded data to the stream
+ /// </summary>
+ /// <typeparam name="T">The object type to serialize</typeparam>
+ /// <param name="data">The object to serialize</param>
+ /// <param name="output">The <see cref="Stream"/> to write output data to</param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to serializer</param>
+ /// <exception cref="JsonException"></exception>
+ public static void JSONSerializeToBinary<T>(T data, Stream output, JsonSerializerOptions? options = null)
+ {
+ //return if null
+ if(data == null)
+ {
+ return;
+ }
+ //Serialize
+ JsonSerializer.Serialize(output, data, options);
+ }
+ /// <summary>
+ /// Attempts to serialize the object to json and write the encoded data to the stream
+ /// </summary>
+ /// <param name="data">The object to serialize</param>
+ /// <param name="output">The <see cref="Stream"/> to write output data to</param>
+ /// <param name="type"></param>
+ /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to serializer</param>
+ /// <exception cref="JsonException"></exception>
+ public static void JSONSerializeToBinary(object data, Stream output, Type type, JsonSerializerOptions? options = null)
+ {
+ //return if null
+ if (data == null)
+ {
+ return;
+ }
+ //Serialize
+ JsonSerializer.Serialize(output, data, type, options);
+ }
+
+ #region Base32
+
+ private const string RFC_4648_BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+ /// <summary>
+ /// Attempts to convert the specified byte sequence in Base32 encoding
+ /// and writing the encoded data to the output buffer.
+ /// </summary>
+ /// <param name="input">The input buffer to convert</param>
+ /// <param name="output">The ouput buffer to write encoded data to</param>
+ /// <returns>The number of characters written, false if no data was written or output buffer was too small</returns>
+ public static ERRNO TryToBase32Chars(ReadOnlySpan<byte> input, Span<char> output)
+ {
+ ForwardOnlyWriter<char> writer = new(output);
+ return TryToBase32Chars(input, ref writer);
+ }
+ /// <summary>
+ /// Attempts to convert the specified byte sequence in Base32 encoding
+ /// and writing the encoded data to the output buffer.
+ /// </summary>
+ /// <param name="input">The input buffer to convert</param>
+ /// <param name="writer">A <see cref="ForwardOnlyWriter{T}"/> to write encoded chars to</param>
+ /// <returns>The number of characters written, false if no data was written or output buffer was too small</returns>
+ public static ERRNO TryToBase32Chars(ReadOnlySpan<byte> input, ref ForwardOnlyWriter<char> writer)
+ {
+ //calculate char size
+ int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
+ //Make sure there is enough room
+ if(charCount > writer.RemainingSize)
+ {
+ return false;
+ }
+ //sliding window over input buffer
+ ForwardOnlyReader<byte> reader = new(input);
+
+ while (reader.WindowSize > 0)
+ {
+ //Convert the current window
+ WriteChars(reader.Window, ref writer);
+ //shift the window
+ reader.Advance(Math.Min(5, reader.WindowSize));
+ }
+ return writer.Written;
+ }
+ private unsafe static void WriteChars(ReadOnlySpan<byte> input, ref ForwardOnlyWriter<char> writer)
+ {
+ //Get the input buffer as long
+ ulong inputAsLong = 0;
+ //Get a byte pointer over the ulong to index it as a byte buffer
+ byte* buffer = (byte*)&inputAsLong;
+ //Check proc endianness
+ if (BitConverter.IsLittleEndian)
+ {
+ //store each byte consecutivley and allow for padding
+ for (int i = 0; (i < 5 && i < input.Length); i++)
+ {
+ //Write bytes from upper to lower byte order for little endian systems
+ buffer[7 - i] = input[i];
+ }
+ }
+ else
+ {
+ //store each byte consecutivley and allow for padding
+ for (int i = 0; (i < 5 && i < input.Length); i++)
+ {
+ //Write bytes from lower to upper byte order for Big Endian systems
+ buffer[i] = input[i];
+ }
+ }
+ int rounds = (input.Length) switch
+ {
+ 1 => 2,
+ 2 => 4,
+ 3 => 5,
+ 4 => 7,
+ _ => 8
+ };
+ //Convert each byte segment up to the number of bytes encoded
+ for (int i = 0; i < rounds; i++)
+ {
+ //store the leading byte
+ byte val = buffer[7];
+ //right shift the value to lower 5 bits
+ val >>= 3;
+ //Lookup charcode
+ char base32Char = RFC_4648_BASE32_CHARS[val];
+ //append the character to the writer
+ writer.Append(base32Char);
+ //Shift input left by 5 bits so the next 5 bits can be read
+ inputAsLong <<= 5;
+ }
+ //Fill remaining bytes with padding chars
+ for(; rounds < 8; rounds++)
+ {
+ //Append trailing '='
+ writer.Append('=');
+ }
+ }
+
+ /// <summary>
+ /// Attempts to decode the Base32 encoded string
+ /// </summary>
+ /// <param name="input">The Base32 encoded data to decode</param>
+ /// <param name="output">The output buffer to write decoded data to</param>
+ /// <returns>The number of bytes written to the output</returns>
+ /// <exception cref="FormatException"></exception>
+ public static ERRNO TryFromBase32Chars(ReadOnlySpan<char> input, Span<byte> output)
+ {
+ ForwardOnlyWriter<byte> writer = new(output);
+ return TryFromBase32Chars(input, ref writer);
+ }
+ /// <summary>
+ /// Attempts to decode the Base32 encoded string
+ /// </summary>
+ /// <param name="input">The Base32 encoded data to decode</param>
+ /// <param name="writer">A <see cref="ForwardOnlyWriter{T}"/> to write decoded bytes to</param>
+ /// <returns>The number of bytes written to the output</returns>
+ /// <exception cref="FormatException"></exception>
+ public unsafe static ERRNO TryFromBase32Chars(ReadOnlySpan<char> input, ref ForwardOnlyWriter<byte> writer)
+ {
+ //TODO support Big-Endian byte order
+
+ //trim padding characters
+ input = input.Trim('=');
+ //Calc the number of bytes to write
+ int outputSize = (input.Length * 5) / 8;
+ //make sure the output buffer is large enough
+ if(writer.RemainingSize < outputSize)
+ {
+ return false;
+ }
+
+ //buffer used to shift data while decoding
+ ulong bufferLong = 0;
+
+ //re-cast to byte* to index it as a byte buffer
+ byte* buffer = (byte*)&bufferLong;
+
+ int count = 0, len = input.Length;
+ while(count < len)
+ {
+ //Convert the character to its char code
+ byte charCode = GetCharCode(input[count]);
+ //write byte to buffer
+ buffer[0] |= charCode;
+ count++;
+ //If 8 characters have been decoded, reset the buffer
+ if((count % 8) == 0)
+ {
+ //Write the 5 upper bytes in reverse order to the output buffer
+ for(int j = 0; j < 5; j++)
+ {
+ writer.Append(buffer[4 - j]);
+ }
+ //reset
+ bufferLong = 0;
+ }
+ //left shift the buffer up by 5 bits
+ bufferLong <<= 5;
+ }
+ //If remaining data has not be written, but has been buffed, finalize it
+ if (writer.Written < outputSize)
+ {
+ //calculate how many bits the buffer still needs to be shifted by (will be 5 bits off because of the previous loop)
+ int remainingShift = (7 - (count % 8)) * 5;
+ //right shift the buffer by the remaining bit count
+ bufferLong <<= remainingShift;
+ //calc remaining bytes
+ int remaining = (outputSize - writer.Written);
+ //Write remaining bytes to the output
+ for(int i = 0; i < remaining; i++)
+ {
+ writer.Append(buffer[4 - i]);
+ }
+ }
+ return writer.Written;
+ }
+ private static byte GetCharCode(char c)
+ {
+ //cast to byte to get its base 10 value
+ return c switch
+ {
+ //Upper case
+ 'A' => 0,
+ 'B' => 1,
+ 'C' => 2,
+ 'D' => 3,
+ 'E' => 4,
+ 'F' => 5,
+ 'G' => 6,
+ 'H' => 7,
+ 'I' => 8,
+ 'J' => 9,
+ 'K' => 10,
+ 'L' => 11,
+ 'M' => 12,
+ 'N' => 13,
+ 'O' => 14,
+ 'P' => 15,
+ 'Q' => 16,
+ 'R' => 17,
+ 'S' => 18,
+ 'T' => 19,
+ 'U' => 20,
+ 'V' => 21,
+ 'W' => 22,
+ 'X' => 23,
+ 'Y' => 24,
+ 'Z' => 25,
+ //Lower case
+ 'a' => 0,
+ 'b' => 1,
+ 'c' => 2,
+ 'd' => 3,
+ 'e' => 4,
+ 'f' => 5,
+ 'g' => 6,
+ 'h' => 7,
+ 'i' => 8,
+ 'j' => 9,
+ 'k' => 10,
+ 'l' => 11,
+ 'm' => 12,
+ 'n' => 13,
+ 'o' => 14,
+ 'p' => 15,
+ 'q' => 16,
+ 'r' => 17,
+ 's' => 18,
+ 't' => 19,
+ 'u' => 20,
+ 'v' => 21,
+ 'w' => 22,
+ 'x' => 23,
+ 'y' => 24,
+ 'z' => 25,
+ //Base10 digits
+ '2' => 26,
+ '3' => 27,
+ '4' => 28,
+ '5' => 29,
+ '6' => 30,
+ '7' => 31,
+
+ _=> throw new FormatException("Character found is not a Base32 encoded character")
+ };
+ }
+
+ /// <summary>
+ /// Calculates the maximum buffer size required to encode a binary block to its Base32
+ /// character encoding
+ /// </summary>
+ /// <param name="bufferSize">The binary buffer size used to calculate the base32 buffer size</param>
+ /// <returns>The maximum size (including padding) of the character buffer required to encode the binary data</returns>
+ public static int Base32CalcMaxBufferSize(int bufferSize)
+ {
+ /*
+ * Base32 encoding consumes 8 bytes for every 5 bytes
+ * of input data
+ */
+ //Add up to 8 bytes for padding
+ return (int)(Math.Ceiling(bufferSize / 5d) * 8) + (8 - (bufferSize % 8));
+ }
+
+ /// <summary>
+ /// Converts the binary buffer to a base32 character string with optional padding characters
+ /// </summary>
+ /// <param name="binBuffer">The buffer to encode</param>
+ /// <param name="withPadding">Should padding be included in the result</param>
+ /// <returns>The base32 encoded string representation of the specified buffer</returns>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
+ public static string ToBase32String(ReadOnlySpan<byte> binBuffer, bool withPadding = false)
+ {
+ string value;
+ //Calculate the base32 entropy to alloc an appropriate buffer (minium buffer of 2 chars)
+ int entropy = Base32CalcMaxBufferSize(binBuffer.Length);
+ //Alloc buffer for enough size (2*long bytes) is not an issue
+ using (UnsafeMemoryHandle<char> charBuffer = Memory.Memory.UnsafeAlloc<char>(entropy))
+ {
+ //Encode
+ ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span);
+ if (!encoded)
+ {
+ throw new InternalBufferTooSmallException("Base32 char buffer was too small");
+ }
+ //Convert with or w/o padding
+ if (withPadding)
+ {
+ value = charBuffer.Span[0..(int)encoded].ToString();
+ }
+ else
+ {
+ value = charBuffer.Span[0..(int)encoded].Trim('=').ToString();
+ }
+ }
+ return value;
+ }
+ /// <summary>
+ /// Converts the base32 character buffer to its structure representation
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="base32">The base32 character buffer</param>
+ /// <returns>The new structure of the base32 data</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
+ public static T FromBase32String<T>(ReadOnlySpan<char> base32) where T: unmanaged
+ {
+ //calc size of bin buffer
+ int size = base32.Length;
+ //Rent a bin buffer
+ using UnsafeMemoryHandle<byte> binBuffer = Memory.Memory.UnsafeAlloc<byte>(size);
+ //Try to decode the data
+ ERRNO decoded = TryFromBase32Chars(base32, binBuffer.Span);
+ //Marshal back to a struct
+ return decoded ? MemoryMarshal.Read<T>(binBuffer.Span[..(int)decoded]) : throw new InternalBufferTooSmallException("Binbuffer was too small");
+ }
+
+ /// <summary>
+ /// Gets a byte array of the base32 decoded data
+ /// </summary>
+ /// <param name="base32">The character array to decode</param>
+ /// <returns>The byte[] of the decoded binary data, or null if the supplied character array was empty</returns>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
+ public static byte[]? FromBase32String(ReadOnlySpan<char> base32)
+ {
+ if (base32.IsEmpty)
+ {
+ return null;
+ }
+ //Buffer size of the base32 string will always be enough buffer space
+ using UnsafeMemoryHandle<byte> tempBuffer = Memory.Memory.UnsafeAlloc<byte>(base32.Length);
+ //Try to decode the data
+ ERRNO decoded = TryFromBase32Chars(base32, tempBuffer.Span);
+
+ return decoded ? tempBuffer.Span[0..(int)decoded].ToArray() : throw new InternalBufferTooSmallException("Binbuffer was too small");
+ }
+
+ /// <summary>
+ /// Converts a structure to its base32 representation and returns the string of its value
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="value">The structure to encode</param>
+ /// <param name="withPadding">A value indicating if padding should be used</param>
+ /// <returns>The base32 string representation of the structure</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
+ public static string ToBase32String<T>(T value, bool withPadding = false) where T : unmanaged
+ {
+ //get the size of the structure
+ int binSize = Unsafe.SizeOf<T>();
+ //Rent a bin buffer
+ Span<byte> binBuffer = stackalloc byte[binSize];
+ //Write memory to buffer
+ MemoryMarshal.Write(binBuffer, ref value);
+ //Convert to base32
+ return ToBase32String(binBuffer, withPadding);
+ }
+
+ #endregion
+
+ #region percent encoding
+
+ private static readonly ReadOnlyMemory<byte> HexToUtf8Pos = new byte[16]
+ {
+ 0x30, //0
+ 0x31, //1
+ 0x32, //2
+ 0x33, //3
+ 0x34, //4
+ 0x35, //5
+ 0x36, //6
+ 0x37, //7
+ 0x38, //8
+ 0x39, //9
+
+ 0x41, //A
+ 0x42, //B
+ 0x43, //C
+ 0x44, //D
+ 0x45, //E
+ 0x46 //F
+ };
+
+ /// <summary>
+ /// Deterimes the size of the buffer needed to encode a utf8 encoded
+ /// character buffer into its url-safe percent/hex encoded representation
+ /// </summary>
+ /// <param name="utf8Bytes">The buffer to examine</param>
+ /// <param name="allowedChars">A sequence of characters that are excluded from encoding</param>
+ /// <returns>The size of the buffer required to encode</returns>
+ public static unsafe int PercentEncodeCalcBufferSize(ReadOnlySpan<byte> utf8Bytes, in ReadOnlySpan<byte> allowedChars = default)
+ {
+ /*
+ * For every illegal character, the percent encoding adds 3 bytes of
+ * entropy. So a single byte will be replaced by 3, so adding
+ * 2 bytes for every illegal character plus the length of the
+ * intial buffer, we get the size of the buffer needed to
+ * percent encode.
+ */
+ int count = 0, len = utf8Bytes.Length;
+ fixed (byte* utfBase = &MemoryMarshal.GetReference(utf8Bytes))
+ {
+ //Find all unsafe characters and add the entropy size
+ for (int i = 0; i < len; i++)
+ {
+ if (!IsUrlSafeChar(utfBase[i], in allowedChars))
+ {
+ count += 2;
+ }
+ }
+ }
+ //Size is initial buffer size + count bytes
+ return len + count;
+ }
+
+ /// <summary>
+ /// Percent encodes the buffer for utf8 encoded characters to its percent/hex encoded
+ /// utf8 character representation
+ /// </summary>
+ /// <param name="utf8Bytes">The buffer of utf8 encoded characters to encode</param>
+ /// <param name="utf8Output">The buffer to write the encoded characters to</param>
+ /// <param name="allowedChars">A sequence of characters that are excluded from encoding</param>
+ /// <returns>The number of characters encoded and written to the output buffer</returns>
+ public static ERRNO PercentEncode(ReadOnlySpan<byte> utf8Bytes, Span<byte> utf8Output, in ReadOnlySpan<byte> allowedChars = default)
+ {
+ int outPos = 0, len = utf8Bytes.Length;
+ ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.Span;
+ for (int i = 0; i < len; i++)
+ {
+ byte value = utf8Bytes[i];
+ //Check if value is url safe
+ if(IsUrlSafeChar(value, in allowedChars))
+ {
+ //Skip
+ utf8Output[outPos++] = value;
+ }
+ else
+ {
+ //Percent encode
+ utf8Output[outPos++] = 0x25; // '%'
+ //Calc and store the encoded by the upper 4 bits
+ utf8Output[outPos++] = lookupTable[(value & 0xf0) >> 4];
+ //Store lower 4 bits in encoded value
+ utf8Output[outPos++] = lookupTable[value & 0x0f];
+ }
+ }
+ //Return the size of the output buffer
+ return outPos;
+ }
+
+ private static bool IsUrlSafeChar(byte value, in ReadOnlySpan<byte> allowedChars)
+ {
+ return
+ // base10 digits
+ value > 0x2f && value < 0x3a
+ // '_' (underscore)
+ || value == 0x5f
+ // '-' (hyphen)
+ || value == 0x2d
+ // Uppercase letters
+ || value > 0x40 && value < 0x5b
+ // lowercase letters
+ || value > 0x60 && value < 0x7b
+ // Check allowed characters
+ || allowedChars.Contains(value);
+ }
+
+ //TODO: Implement decode with better performance, lookup table or math vs searching the table
+
+ /// <summary>
+ /// Decodes a percent (url/hex) encoded utf8 encoded character buffer to its utf8
+ /// encoded binary value
+ /// </summary>
+ /// <param name="utf8Encoded">The buffer containg characters to be decoded</param>
+ /// <param name="utf8Output">The buffer to write deocded values to</param>
+ /// <returns>The nuber of bytes written to the output buffer</returns>
+ /// <exception cref="FormatException"></exception>
+ public static ERRNO PercentDecode(ReadOnlySpan<byte> utf8Encoded, Span<byte> utf8Output)
+ {
+ int outPos = 0, len = utf8Encoded.Length;
+ ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.Span;
+ for (int i = 0; i < len; i++)
+ {
+ byte value = utf8Encoded[i];
+ //Begining of percent encoding character
+ if(value == 0x25)
+ {
+ //Calculate the base16 multiplier from the upper half of the
+ int multiplier = lookupTable.IndexOf(utf8Encoded[i + 1]);
+ //get the base16 lower half to add
+ int lower = lookupTable.IndexOf(utf8Encoded[i + 2]);
+ //Check format
+ if(multiplier < 0 || lower < 0)
+ {
+ throw new FormatException($"Encoded buffer contains invalid hexadecimal characters following the % character at position {i}");
+ }
+ //Calculate the new value, shift multiplier to the upper 4 bits, then mask + or the lower 4 bits
+ value = (byte)(((byte)(multiplier << 4)) | ((byte)lower & 0x0f));
+ //Advance the encoded index by the two consumed chars
+ i += 2;
+ }
+ utf8Output[outPos++] = value;
+ }
+ return outPos;
+ }
+
+ #endregion
+
+ #region Base64
+
+ /// <summary>
+ /// Tries to convert the specified span containing a string representation that is
+ /// encoded with base-64 digits into a span of 8-bit unsigned integers.
+ /// </summary>
+ /// <param name="base64">Base64 character data to recover</param>
+ /// <param name="buffer">The binary output buffer to write converted characters to</param>
+ /// <returns>The number of bytes written, or <see cref="ERRNO.E_FAIL"/> of the conversion was unsucessful</returns>
+ public static ERRNO TryFromBase64Chars(ReadOnlySpan<char> base64, Span<byte> buffer)
+ {
+ return Convert.TryFromBase64Chars(base64, buffer, out int bytesWritten) ? bytesWritten : ERRNO.E_FAIL;
+ }
+ /// <summary>
+ /// Tries to convert the 8-bit unsigned integers inside the specified read-only span
+ /// into their equivalent string representation that is encoded with base-64 digits.
+ /// You can optionally specify whether to insert line breaks in the return value.
+ /// </summary>
+ /// <param name="buffer">The binary buffer to convert characters from</param>
+ /// <param name="base64">The base64 output buffer</param>
+ /// <param name="options">
+ /// One of the enumeration values that specify whether to insert line breaks in the
+ /// return value. The default value is System.Base64FormattingOptions.None.
+ /// </param>
+ /// <returns>The number of characters encoded, or <see cref="ERRNO.E_FAIL"/> if conversion was unsuccessful</returns>
+ public static ERRNO TryToBase64Chars(ReadOnlySpan<byte> buffer, Span<char> base64, Base64FormattingOptions options = Base64FormattingOptions.None)
+ {
+ return Convert.TryToBase64Chars(buffer, base64, out int charsWritten, options: options) ? charsWritten : ERRNO.E_FAIL;
+ }
+
+
+ /*
+ * Calc base64 padding chars excluding the length mod 4 = 0 case
+ * by and-ing 0x03 (011) with the result
+ */
+
+ /// <summary>
+ /// Determines the number of missing padding bytes from the length of the base64
+ /// data sequence.
+ /// <code>
+ /// Formula (4 - (length mod 4) and 0x03
+ /// </code>
+ /// </summary>
+ /// <param name="length">The length of the base64 buffer</param>
+ /// <returns>The number of padding bytes to add to the end of the sequence</returns>
+ public static int Base64CalcRequiredPadding(int length) => (4 - (length % 4)) & 0x03;
+
+ /// <summary>
+ /// Converts a base64 utf8 encoded binary buffer to
+ /// its base64url encoded version
+ /// </summary>
+ /// <param name="base64">The binary buffer to convert</param>
+ public static unsafe void Base64ToUrlSafeInPlace(Span<byte> base64)
+ {
+ int len = base64.Length;
+
+ fixed(byte* ptr = &MemoryMarshal.GetReference(base64))
+ {
+ for (int i = 0; i < len; i++)
+ {
+ switch (ptr[i])
+ {
+ //Replace + with - (minus)
+ case 0x2b:
+ ptr[i] = 0x2d;
+ break;
+ //Replace / with _ (underscore)
+ case 0x2f:
+ ptr[i] = 0x5f;
+ break;
+ }
+ }
+ }
+ }
+ /// <summary>
+ /// Converts a base64url encoded utf8 encoded binary buffer to
+ /// its base64 encoded version
+ /// </summary>
+ /// <param name="uft8Base64Url">The base64url utf8 to decode</param>
+ public static unsafe void Base64FromUrlSafeInPlace(Span<byte> uft8Base64Url)
+ {
+ int len = uft8Base64Url.Length;
+
+ fixed (byte* ptr = &MemoryMarshal.GetReference(uft8Base64Url))
+ {
+ for (int i = 0; i < len; i++)
+ {
+ switch (ptr[i])
+ {
+ //Replace - with + (plus)
+ case 0x2d:
+ ptr[i] = 0x2b;
+ break;
+ //Replace _ with / (slash)
+ case 0x5f:
+ ptr[i] = 0x2f;
+ break;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Converts the input buffer to a url safe base64 encoded
+ /// utf8 buffer from the base64 input buffer. The base64 is copied
+ /// directly to the output then converted in place. This is
+ /// just a shortcut method for readonly spans
+ /// </summary>
+ /// <param name="base64">The base64 encoded data</param>
+ /// <param name="base64Url">The base64url encoded output</param>
+ /// <returns>The size of the <paramref name="base64"/> buffer</returns>
+ public static ERRNO Base64ToUrlSafe(ReadOnlySpan<byte> base64, Span<byte> base64Url)
+ {
+ //Aligned copy to the output buffer
+ base64.CopyTo(base64Url);
+ //One time convert the output buffer to url safe
+ Base64ToUrlSafeInPlace(base64Url);
+ return base64.Length;
+ }
+
+ /// <summary>
+ /// Converts the urlsafe input buffer to a base64 encoded
+ /// utf8 buffer from the base64 input buffer. The base64 is copied
+ /// directly to the output then converted in place. This is
+ /// just a shortcut method for readonly spans
+ /// </summary>
+ /// <param name="base64">The base64 encoded data</param>
+ /// <param name="base64Url">The base64url encoded output</param>
+ /// <returns>The size of the <paramref name="base64Url"/> buffer</returns>
+ public static ERRNO Base64FromUrlSafe(ReadOnlySpan<byte> base64Url, Span<byte> base64)
+ {
+ //Aligned copy to the output buffer
+ base64Url.CopyTo(base64);
+ //One time convert the output buffer to url safe
+ Base64FromUrlSafeInPlace(base64);
+ return base64Url.Length;
+ }
+
+ /// <summary>
+ /// Decodes a utf8 base64url encoded sequence of data and writes it
+ /// to the supplied output buffer
+ /// </summary>
+ /// <param name="utf8Base64Url">The utf8 base64 url encoded string</param>
+ /// <param name="output">The output buffer to write the decoded data to</param>
+ /// <returns>The number of bytes written or <see cref="ERRNO.E_FAIL"/> if the operation failed</returns>
+ public static ERRNO Base64UrlDecode(ReadOnlySpan<byte> utf8Base64Url, Span<byte> output)
+ {
+ if(utf8Base64Url.IsEmpty || output.IsEmpty)
+ {
+ return ERRNO.E_FAIL;
+ }
+ //url deocde
+ ERRNO count = Base64FromUrlSafe(utf8Base64Url, output);
+
+ //Writer for adding padding bytes
+ ForwardOnlyWriter<byte> writer = new (output);
+ writer.Advance(count);
+
+ //Calc required padding
+ int paddingToAdd = Base64CalcRequiredPadding(writer.Written);
+ //Add padding bytes
+ for (; paddingToAdd > 0; paddingToAdd--)
+ {
+ writer.Append(0x3d); // '='
+ }
+
+ //Base64 decode in place, we should have a buffer large enough
+ OperationStatus status = Base64.DecodeFromUtf8InPlace(writer.AsSpan(), out int bytesWritten);
+ //If status is successful return the number of bytes written
+ return status == OperationStatus.Done ? bytesWritten : ERRNO.E_FAIL;
+ }
+
+ /// <summary>
+ /// Decodes a base64url encoded character sequence
+ /// of data and writes it to the supplied output buffer
+ /// </summary>
+ /// <param name="chars">The character buffer to decode</param>
+ /// <param name="output">The output buffer to write decoded data to</param>
+ /// <param name="encoding">The character encoding</param>
+ /// <returns>The number of bytes written or <see cref="ERRNO.E_FAIL"/> if the operation failed</returns>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
+ public static ERRNO Base64UrlDecode(ReadOnlySpan<char> chars, Span<byte> output, Encoding? encoding = null)
+ {
+ if (chars.IsEmpty || output.IsEmpty)
+ {
+ return ERRNO.E_FAIL;
+ }
+ //Set the encoding to utf8
+ encoding ??= Encoding.UTF8;
+ //get the number of bytes to alloc a buffer
+ int decodedSize = encoding.GetByteCount(chars);
+
+ //alloc buffer
+ using UnsafeMemoryHandle<byte> decodeHandle = Memory.Memory.UnsafeAlloc<byte>(decodedSize);
+ //Get the utf8 binary data
+ int count = encoding.GetBytes(chars, decodeHandle);
+ return Base64UrlDecode(decodeHandle.Span[..count], output);
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Utils/tests/ERRNOTest.cs b/Utils/tests/ERRNOTest.cs
new file mode 100644
index 0000000..bd14b50
--- /dev/null
+++ b/Utils/tests/ERRNOTest.cs
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.UtilsTests
+* File: ERRNOTest.cs
+*
+* ERRNOTest.cs is part of VNLib.UtilsTests which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.UtilsTests is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.UtilsTests is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using VNLib.Utils;
+
+namespace VNLib.Utils.Tests
+{
+ [TestClass]
+ public class ERRNOTest
+ {
+
+ [TestMethod]
+ public unsafe void ERRNOSizeTest()
+ {
+ Assert.IsTrue(sizeof(ERRNO) == sizeof(nint));
+ }
+
+ }
+}
diff --git a/Utils/tests/Memory/MemoryHandleTest.cs b/Utils/tests/Memory/MemoryHandleTest.cs
new file mode 100644
index 0000000..02ef1f1
--- /dev/null
+++ b/Utils/tests/Memory/MemoryHandleTest.cs
@@ -0,0 +1,183 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.UtilsTests
+* File: MemoryHandleTest.cs
+*
+* MemoryHandleTest.cs is part of VNLib.UtilsTests which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.UtilsTests is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.UtilsTests is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/.
+*/
+
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using VNLib.Utils;
+using VNLib.Utils.Extensions;
+
+using static VNLib.Utils.Memory.Memory;
+
+namespace VNLib.Utils.Memory.Tests
+{
+ [TestClass]
+ public class MemoryHandleTest
+ {
+
+ [TestMethod]
+ public void MemoryHandleAllocLongExtensionTest()
+ {
+ //Check for negatives
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => Shared.Alloc<byte>(-1));
+
+ //Make sure over-alloc throws
+ Assert.ThrowsException<NativeMemoryOutOfMemoryException>(() => Shared.Alloc<byte>(ulong.MaxValue, false));
+ }
+#if TARGET_64_BIT
+ [TestMethod]
+ public unsafe void MemoryHandleBigAllocTest()
+ {
+ const long bigHandleSize = (long)uint.MaxValue + 1024;
+
+ using MemoryHandle<byte> handle = Shared.Alloc<byte>(bigHandleSize);
+
+ //verify size
+ Assert.AreEqual(handle.ByteLength, (ulong)bigHandleSize);
+ //Since handle is byte, should also match
+ Assert.AreEqual(handle.Length, (ulong)bigHandleSize);
+
+ //Should throw overflow
+ Assert.ThrowsException<OverflowException>(() => _ = handle.Span);
+ Assert.ThrowsException<OverflowException>(() => _ = handle.IntLength);
+
+ //Should get the remaining span
+ Span<byte> offsetTest = handle.GetOffsetSpan(int.MaxValue, 1024);
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.GetOffsetSpan((long)int.MaxValue + 1, 1024));
+
+ }
+#else
+
+#endif
+
+ [TestMethod]
+ public unsafe void BasicMemoryHandleTest()
+ {
+ using MemoryHandle<byte> handle = Shared.Alloc<byte>(128, true);
+
+ Assert.AreEqual(handle.IntLength, 128);
+
+ Assert.AreEqual(handle.Length, (ulong)128);
+
+ //Check span against base pointer deref
+
+ handle.Span[120] = 10;
+
+ Assert.AreEqual(*handle.GetOffset(120), 10);
+ }
+
+
+ [TestMethod]
+ public unsafe void MemoryHandleDisposedTest()
+ {
+ using MemoryHandle<byte> handle = Shared.Alloc<byte>(1024);
+
+ //Make sure handle is not invalid until disposed
+ Assert.IsFalse(handle.IsInvalid);
+ Assert.IsFalse(handle.IsClosed);
+ Assert.AreNotEqual(IntPtr.Zero, handle.BasePtr);
+
+ //Dispose the handle early and test
+ handle.Dispose();
+
+ Assert.IsTrue(handle.IsInvalid);
+ Assert.IsTrue(handle.IsClosed);
+
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = handle.Span);
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = handle.BasePtr);
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = handle.Base);
+ Assert.ThrowsException<ObjectDisposedException>(() => handle.Resize(10));
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = handle.GetOffset(10));
+ Assert.ThrowsException<ObjectDisposedException>(() => handle.ThrowIfClosed());
+ }
+
+ [TestMethod]
+ public unsafe void MemoryHandleCountDisposedTest()
+ {
+ using MemoryHandle<byte> handle = Shared.Alloc<byte>(1024);
+
+ //Make sure handle is not invalid until disposed
+ Assert.IsFalse(handle.IsInvalid);
+ Assert.IsFalse(handle.IsClosed);
+ Assert.AreNotEqual(IntPtr.Zero, handle.BasePtr);
+
+ bool test = false;
+ //Increase handle counter
+ handle.DangerousAddRef(ref test);
+ Assert.IsTrue(test);
+
+ //Dispose the handle early and test
+ handle.Dispose();
+
+ //Asser is valid still
+
+ //Make sure handle is not invalid until disposed
+ Assert.IsFalse(handle.IsInvalid);
+ Assert.IsFalse(handle.IsClosed);
+ Assert.AreNotEqual(IntPtr.Zero, handle.BasePtr);
+
+ //Dec handle count
+ handle.DangerousRelease();
+
+ //Now make sure the class is disposed
+
+ Assert.IsTrue(handle.IsInvalid);
+ Assert.IsTrue(handle.IsClosed);
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = handle.Span);
+ }
+
+ [TestMethod]
+ public unsafe void MemoryHandleExtensionsTest()
+ {
+ using MemoryHandle<byte> handle = Shared.Alloc<byte>(1024);
+
+ Assert.AreEqual(handle.IntLength, 1024);
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => handle.Resize(-1));
+
+ //Resize the handle
+ handle.Resize(2048);
+
+ Assert.AreEqual(handle.IntLength, 2048);
+
+ Assert.IsTrue(handle.AsSpan(2048).IsEmpty);
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.AsSpan(2049));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.GetOffset(2049));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.GetOffset(-1));
+
+ //test resize
+ handle.ResizeIfSmaller(100);
+ //Handle should be unmodified
+ Assert.AreEqual(handle.IntLength, 2048);
+
+ //test working
+ handle.ResizeIfSmaller(4096);
+ Assert.AreEqual(handle.IntLength, 4096);
+ }
+ }
+}
diff --git a/Utils/tests/Memory/MemoryTests.cs b/Utils/tests/Memory/MemoryTests.cs
new file mode 100644
index 0000000..5b68cf5
--- /dev/null
+++ b/Utils/tests/Memory/MemoryTests.cs
@@ -0,0 +1,244 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.UtilsTests
+* File: MemoryTests.cs
+*
+* MemoryTests.cs is part of VNLib.UtilsTests which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.UtilsTests is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.UtilsTests is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/.
+*/
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Utils.Memory.Tests
+{
+ [TestClass()]
+ public class MemoryTests
+ {
+ [TestMethod]
+ public void MemorySharedHeapLoadedTest()
+ {
+ Assert.IsNotNull(Memory.Shared);
+ }
+
+ [TestMethod()]
+ public void UnsafeAllocTest()
+ {
+ //test against negative number
+ Assert.ThrowsException<ArgumentException>(() => Memory.UnsafeAlloc<byte>(-1));
+
+ //Alloc large block test (100mb)
+ const int largTestSize = 100000 * 1024;
+ //Alloc super small block
+ const int smallTestSize = 5;
+
+ using (UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(largTestSize, false))
+ {
+ Assert.AreEqual(largTestSize, buffer.IntLength);
+ Assert.AreEqual(largTestSize, buffer.Span.Length);
+
+ buffer.Span[0] = 254;
+ Assert.AreEqual(buffer.Span[0], 254);
+ }
+
+ using (UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(smallTestSize, false))
+ {
+ Assert.AreEqual(smallTestSize, buffer.IntLength);
+ Assert.AreEqual(smallTestSize, buffer.Span.Length);
+
+ buffer.Span[0] = 254;
+ Assert.AreEqual(buffer.Span[0], 254);
+ }
+
+ //Different data type
+
+ using(UnsafeMemoryHandle<long> buffer = Memory.UnsafeAlloc<long>(largTestSize, false))
+ {
+ Assert.AreEqual(largTestSize, buffer.IntLength);
+ Assert.AreEqual(largTestSize, buffer.Span.Length);
+
+ buffer.Span[0] = long.MaxValue;
+ Assert.AreEqual(buffer.Span[0], long.MaxValue);
+ }
+
+ using (UnsafeMemoryHandle<long> buffer = Memory.UnsafeAlloc<long>(smallTestSize, false))
+ {
+ Assert.AreEqual(smallTestSize, buffer.IntLength);
+ Assert.AreEqual(smallTestSize, buffer.Span.Length);
+
+ buffer.Span[0] = long.MaxValue;
+ Assert.AreEqual(buffer.Span[0], long.MaxValue);
+ }
+ }
+
+ [TestMethod()]
+ public void UnsafeZeroMemoryAsSpanTest()
+ {
+ //Alloc test buffer
+ Span<byte> test = new byte[1024];
+ test.Fill(0);
+ //test other empty span
+ Span<byte> verify = new byte[1024];
+ verify.Fill(0);
+
+ //Fill test buffer with random values
+ Random.Shared.NextBytes(test);
+
+ //make sure buffers are not equal
+ Assert.IsFalse(test.SequenceEqual(verify));
+
+ //Zero buffer
+ Memory.UnsafeZeroMemory<byte>(test);
+
+ //Make sure buffers are equal
+ Assert.IsTrue(test.SequenceEqual(verify));
+ }
+
+ [TestMethod()]
+ public void UnsafeZeroMemoryAsMemoryTest()
+ {
+ //Alloc test buffer
+ Memory<byte> test = new byte[1024];
+ test.Span.Fill(0);
+ //test other empty span
+ Memory<byte> verify = new byte[1024];
+ verify.Span.Fill(0);
+
+ //Fill test buffer with random values
+ Random.Shared.NextBytes(test.Span);
+
+ //make sure buffers are not equal
+ Assert.IsFalse(test.Span.SequenceEqual(verify.Span));
+
+ //Zero buffer
+ Memory.UnsafeZeroMemory<byte>(test);
+
+ //Make sure buffers are equal
+ Assert.IsTrue(test.Span.SequenceEqual(verify.Span));
+ }
+
+ [TestMethod()]
+ public void InitializeBlockAsSpanTest()
+ {
+ //Alloc test buffer
+ Span<byte> test = new byte[1024];
+ test.Fill(0);
+ //test other empty span
+ Span<byte> verify = new byte[1024];
+ verify.Fill(0);
+
+ //Fill test buffer with random values
+ Random.Shared.NextBytes(test);
+
+ //make sure buffers are not equal
+ Assert.IsFalse(test.SequenceEqual(verify));
+
+ //Zero buffer
+ Memory.InitializeBlock(test);
+
+ //Make sure buffers are equal
+ Assert.IsTrue(test.SequenceEqual(verify));
+ }
+
+ [TestMethod()]
+ public void InitializeBlockMemoryTest()
+ {
+ //Alloc test buffer
+ Memory<byte> test = new byte[1024];
+ test.Span.Fill(0);
+ //test other empty span
+ Memory<byte> verify = new byte[1024];
+ verify.Span.Fill(0);
+
+ //Fill test buffer with random values
+ Random.Shared.NextBytes(test.Span);
+
+ //make sure buffers are not equal
+ Assert.IsFalse(test.Span.SequenceEqual(verify.Span));
+
+ //Zero buffer
+ Memory.InitializeBlock(test);
+
+ //Make sure buffers are equal
+ Assert.IsTrue(test.Span.SequenceEqual(verify.Span));
+ }
+
+ #region structmemory tests
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct TestStruct
+ {
+ public int X;
+ public int Y;
+ }
+
+ [TestMethod()]
+ public unsafe void ZeroStructAsPointerTest()
+ {
+ TestStruct* s = Memory.Shared.StructAlloc<TestStruct>();
+ s->X = 10;
+ s->Y = 20;
+ Assert.AreEqual(10, s->X);
+ Assert.AreEqual(20, s->Y);
+ //zero struct
+ Memory.ZeroStruct(s);
+ //Verify data was zeroed
+ Assert.AreEqual(0, s->X);
+ Assert.AreEqual(0, s->Y);
+ //Free struct
+ Memory.Shared.StructFree(s);
+ }
+
+ [TestMethod()]
+ public unsafe void ZeroStructAsVoidPointerTest()
+ {
+ TestStruct* s = Memory.Shared.StructAlloc<TestStruct>();
+ s->X = 10;
+ s->Y = 20;
+ Assert.AreEqual(10, s->X);
+ Assert.AreEqual(20, s->Y);
+ //zero struct
+ Memory.ZeroStruct<TestStruct>((void*)s);
+ //Verify data was zeroed
+ Assert.AreEqual(0, s->X);
+ Assert.AreEqual(0, s->Y);
+ //Free struct
+ Memory.Shared.StructFree(s);
+ }
+
+ [TestMethod()]
+ public unsafe void ZeroStructAsIntPtrTest()
+ {
+ TestStruct* s = Memory.Shared.StructAlloc<TestStruct>();
+ s->X = 10;
+ s->Y = 20;
+ Assert.AreEqual(10, s->X);
+ Assert.AreEqual(20, s->Y);
+ //zero struct
+ Memory.ZeroStruct<TestStruct>((IntPtr)s);
+ //Verify data was zeroed
+ Assert.AreEqual(0, s->X);
+ Assert.AreEqual(0, s->Y);
+ //Free struct
+ Memory.Shared.StructFree(s);
+ }
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Utils/tests/Memory/VnTableTests.cs b/Utils/tests/Memory/VnTableTests.cs
new file mode 100644
index 0000000..11350d4
--- /dev/null
+++ b/Utils/tests/Memory/VnTableTests.cs
@@ -0,0 +1,168 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.UtilsTests
+* File: VnTableTests.cs
+*
+* VnTableTests.cs is part of VNLib.UtilsTests which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.UtilsTests is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.UtilsTests is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/.
+*/
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+
+namespace VNLib.Utils.Memory.Tests
+{
+ [TestClass()]
+ public class VnTableTests
+ {
+ [TestMethod()]
+ public void VnTableTest()
+ {
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
+ {
+ using VnTable<int> table = new(-1, 0);
+ });
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
+ {
+ using VnTable<int> table = new(0, -1);
+ });
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
+ {
+ using VnTable<int> table = new(-1, -1);
+ });
+
+ //Empty table
+ using (VnTable<int> empty = new(0, 0))
+ {
+ Assert.IsTrue(empty.Empty);
+ //Test 0 rows/cols
+ Assert.AreEqual(0, empty.Rows);
+ Assert.AreEqual(0, empty.Cols);
+ }
+
+ using (VnTable<int> table = new(40000, 10000))
+ {
+ Assert.IsFalse(table.Empty);
+
+ //Test table size
+ Assert.AreEqual(40000, table.Rows);
+ Assert.AreEqual(10000, table.Cols);
+ }
+
+
+ //Test oom, should be native
+ Assert.ThrowsException<NativeMemoryOutOfMemoryException>(() =>
+ {
+ using VnTable<int> table = new(int.MaxValue, 2);
+ });
+ }
+
+ [TestMethod()]
+ public void VnTableTest1()
+ {
+ //No throw if empty
+ using VnTable<int> table = new(null!,0, 0);
+
+ //Throw if table is not empty
+ Assert.ThrowsException<ArgumentNullException>(() =>
+ {
+ using VnTable<int> table = new(null!,1, 1);
+ });
+
+ }
+
+ [TestMethod()]
+ public void GetSetTest()
+ {
+ static void TestIndexAt(VnTable<int> table, int row, int col, int value)
+ {
+ table[row, col] = value;
+ Assert.AreEqual(value, table[row, col]);
+ Assert.AreEqual(value, table.Get(row, col));
+ }
+
+ static void TestSetAt(VnTable<int> table, int row, int col, int value)
+ {
+ table.Set(row, col, value);
+ Assert.AreEqual(value, table[row, col]);
+ Assert.AreEqual(value, table.Get(row, col));
+ }
+
+ static void TestSetDirectAccess(VnTable<int> table, int row, int col, int value)
+ {
+ int address = row * table.Cols + col;
+ table[(uint)address] = value;
+
+ //Get value using indexer
+ Assert.AreEqual(value, table[row, col]);
+ }
+
+ static void TestGetDirectAccess(VnTable<int> table, int row, int col, int value)
+ {
+ table[row, col] = value;
+
+ int address = row * table.Cols + col;
+
+ //Test direct access
+ Assert.AreEqual(value, table[(uint)address]);
+
+ //Get value using indexer
+ Assert.AreEqual(value, table[row, col]);
+ Assert.AreEqual(value, table.Get(row, col));
+ }
+
+
+ using (VnTable<int> table = new(11, 11))
+ {
+ //Test index at 10,10
+ TestIndexAt(table, 10, 10, 11);
+ //Test same index with different value using the .set() method
+ TestSetAt(table, 10, 10, 25);
+
+ //Test direct access
+ TestSetDirectAccess(table, 10, 10, 50);
+
+ TestGetDirectAccess(table, 10, 10, 37);
+
+ //Test index at 0,0
+ TestIndexAt(table, 0, 0, 13);
+ TestSetAt(table, 0, 0, 85);
+
+ //Test at 0,0
+ TestSetDirectAccess(table, 0, 0, 100);
+ TestGetDirectAccess(table, 0, 0, 86);
+ }
+ }
+
+ [TestMethod()]
+ public void DisposeTest()
+ {
+ //Alloc table
+ VnTable<int> table = new(10, 10);
+ //Dispose table
+ table.Dispose();
+
+ //Test that methods throw on access
+ Assert.ThrowsException<ObjectDisposedException>(() => table[10, 10] = 10);
+ Assert.ThrowsException<ObjectDisposedException>(() => table.Set(10, 10, 10));
+ Assert.ThrowsException<ObjectDisposedException>(() => table[10, 10] == 10);
+ Assert.ThrowsException<ObjectDisposedException>(() => table.Get(10, 10));
+ }
+
+ }
+} \ No newline at end of file
diff --git a/Utils/tests/README.md b/Utils/tests/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Utils/tests/README.md
diff --git a/Utils/tests/VNLib.UtilsTests.csproj b/Utils/tests/VNLib.UtilsTests.csproj
new file mode 100644
index 0000000..89b3124
--- /dev/null
+++ b/Utils/tests/VNLib.UtilsTests.csproj
@@ -0,0 +1,36 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+
+ <IsPackable>false</IsPackable>
+
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
+ <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
+ <PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
+ <PackageReference Include="coverlet.collector" Version="3.2.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\src\VNLib.Utils.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Utils/tests/VnEncodingTests.cs b/Utils/tests/VnEncodingTests.cs
new file mode 100644
index 0000000..a4e52f0
--- /dev/null
+++ b/Utils/tests/VnEncodingTests.cs
@@ -0,0 +1,100 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.UtilsTests
+* File: VnEncodingTests.cs
+*
+* VnEncodingTests.cs is part of VNLib.UtilsTests which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.UtilsTests is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.UtilsTests is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using VNLib.Utils;
+
+namespace VNLib.Utils.Tests
+{
+ [TestClass()]
+ public class VnEncodingTests
+ {
+
+ [TestMethod()]
+ public void Base64ToUrlSafeInPlaceTest()
+ {
+ //Get randomd data to encode
+ byte[] dataToEncode = RandomNumberGenerator.GetBytes(64);
+ //Calc buffer size
+ int base64Output = Base64.GetMaxEncodedToUtf8Length(64);
+
+ byte[] encodeBuffer = new byte[base64Output];
+ //Base64 encode
+ OperationStatus status = Base64.EncodeToUtf8(dataToEncode, encodeBuffer, out _, out int bytesEncoded, true);
+
+ Assert.IsTrue(status == OperationStatus.Done);
+
+ Span<byte> encodeSpan = encodeBuffer.AsSpan(0, bytesEncoded);
+
+ //Make sure some illegal characters are encoded
+ Assert.IsTrue(encodeSpan.Contains((byte)'+') || encodeSpan.Contains((byte)'/'));
+
+ //Convert to url safe
+ VnEncoding.Base64ToUrlSafeInPlace(encodeSpan);
+
+ //Make sure the illegal characters are gone
+ Assert.IsFalse(encodeSpan.Contains((byte)'+') || encodeSpan.Contains((byte)'/'));
+ }
+
+ [TestMethod()]
+ public void Base64FromUrlSafeInPlaceTest()
+ {
+ //url safe base64 with known encoded characters
+ const string base64UrlSafe = "lZUABUd8q2BS7p8giysuC7PpEabAFBnMqBPL-9A-qgfR1lbTHQ4tMm8E8nimm2YAd5NGDIQ0vxfU9i5l53tF_WXa_H4vkHfzlv0Df-lLADJV7z8sn-8sfUGdaAiIS8_4OmVGnnY4-TppLMsVR6ov2t07HdOHPPsFFhSpBMXa2pwRveRATcxBA2XxVe09FOWgahhssNS7lU9eC7fRw7icD4ZoJcLSRBbxrjRmeVXKhPIaXR-4mnQ5-vqYzAr9S99CthgbAtVn_WjmDcda6pUB9JW9lp7ylDa9e1r_z39cihTXMOGaUSjVURJaWrNF8CkfW56_x2ODCBmZPov1YyEhww==";
+
+ //Convert to utf8 binary
+ byte[] utf8 = Encoding.UTF8.GetBytes(base64UrlSafe);
+
+ //url decode
+ VnEncoding.Base64FromUrlSafeInPlace(utf8);
+
+ //Confirm illegal chars have been converted back to base64
+ Assert.IsFalse(utf8.Contains((byte)'_') || utf8.Contains((byte)'-'));
+
+ //Decode in place to confrim its valid
+ OperationStatus status = Base64.DecodeFromUtf8InPlace(utf8, out int bytesWritten);
+
+ Assert.IsFalse(status == OperationStatus.NeedMoreData);
+ Assert.IsFalse(status == OperationStatus.DestinationTooSmall);
+ Assert.IsFalse(status == OperationStatus.InvalidData);
+ }
+
+ [TestMethod()]
+ public void TryToBase64CharsTest()
+ {
+
+ }
+
+
+ }
+} \ No newline at end of file