Browse Source

change: rename ByteArrayData to BinaryData and change binary data handling

Akeit0 6 months ago
parent
commit
db8b4f74bf

+ 16 - 0
src/Lua/IO/BinaryData.cs

@@ -0,0 +1,16 @@
+using System.Buffers;
+
+namespace Lua.IO;
+
+public class BinaryData(ReadOnlyMemory<byte> bytes) : IBinaryData
+{
+    public ReadOnlyMemory<byte> Memory => bytes;
+}
+
+public interface IBinaryData
+{
+    /// <summary>
+    /// Gets the bytes of the binary data.
+    /// </summary>
+    public ReadOnlyMemory<byte> Memory { get; }
+}

+ 17 - 18
src/Lua/IO/BinaryLuaIOStream.cs

@@ -6,20 +6,20 @@ internal sealed class BinaryLuaIOStream(LuaFileOpenMode mode, Stream innerStream
     ulong nextFlushSize = ulong.MaxValue;
 
     public LuaFileOpenMode Mode => mode;
-    public LuaFileContentType ContentType => LuaFileContentType.Bytes;
+    public LuaFileContentType ContentType => LuaFileContentType.Binary;
 
     public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
     {
         throw new InvalidOperationException("Cannot read lines from a binary stream. Use a text stream instead.");
     }
 
-    public ValueTask<LuaFileContent> ReadToEndAsync(CancellationToken cancellationToken)
+    public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
     {
         ThrowIfNotReadable();
         using var memoryStream = new MemoryStream();
         innerStream.CopyTo(memoryStream);
         var bytes = memoryStream.ToArray();
-        return new(new LuaFileContent(bytes,bytes.Length));
+        return new(new LuaFileContent(bytes));
     }
 
     public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
@@ -29,58 +29,57 @@ internal sealed class BinaryLuaIOStream(LuaFileOpenMode mode, Stream innerStream
 
     public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken)
     {
-        if (content.Type != LuaFileContentType.Bytes)
+        if (content.Type != LuaFileContentType.Binary)
         {
             throw new InvalidOperationException("Cannot write string to a binary stream.");
         }
-        
+
         return WriteBytesAsync(content.ReadBytes().Span, cancellationToken);
     }
-    
 
     public ValueTask<byte[]?> ReadBytesAsync(int count, CancellationToken cancellationToken)
     {
         ThrowIfNotReadable();
-        
-        if (count <= 0) return new ValueTask<byte[]?>((byte[]?)null);
-        
+
+        if (count <= 0) return new((byte[]?)null);
+
         var buffer = new byte[count];
         var totalRead = 0;
-        
+
         while (totalRead < count)
         {
             var bytesRead = innerStream.Read(buffer, totalRead, count - totalRead);
             if (bytesRead == 0) break; // End of stream
             totalRead += bytesRead;
         }
-        
-        if (totalRead == 0) return new ValueTask<byte[]?>((byte[]?)null);
+
+        if (totalRead == 0) return new((byte[]?)null);
         if (totalRead < count)
         {
             Array.Resize(ref buffer, totalRead);
         }
-        
-        return new ValueTask<byte[]?>(buffer);
+
+        return new(buffer);
     }
 
 
     public ValueTask WriteBytesAsync(ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
     {
         ThrowIfNotWritable();
-        
+
         if (mode is LuaFileOpenMode.Append or LuaFileOpenMode.ReadAppend)
         {
             innerStream.Seek(0, SeekOrigin.End);
         }
-        
+
         innerStream.Write(buffer);
-        
+
         if (nextFlushSize < (ulong)buffer.Length)
         {
             innerStream.Flush();
             nextFlushSize = flushSize;
         }
-        
+
         return new();
     }
 

+ 0 - 11
src/Lua/IO/ByteArrayData.cs

@@ -1,11 +0,0 @@
-namespace Lua.IO;
-
-public sealed class ByteArrayData(byte[] bytes)
-{
-    public ReadOnlySpan<byte> Bytes => new ReadOnlySpan<byte>(bytes);
-    
-    public LuaFileContent AsLuaFileContent()
-    {
-        return new LuaFileContent(bytes);
-    }
-}

+ 8 - 3
src/Lua/IO/ILuaFileSystem.cs

@@ -19,7 +19,7 @@ public interface ILuaIOStream : IDisposable
     public LuaFileOpenMode Mode { get; }
 
     public LuaFileContentType ContentType { get; }
-    public ValueTask<LuaFileContent> ReadToEndAsync(CancellationToken cancellationToken);
+    public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken);
     public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken);
     public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken);
     public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken);
@@ -29,7 +29,7 @@ public interface ILuaIOStream : IDisposable
 
     public static ILuaIOStream CreateStreamWrapper(Stream stream, LuaFileOpenMode mode, LuaFileContentType contentType = LuaFileContentType.Text)
     {
-        return contentType == LuaFileContentType.Bytes
+        return contentType == LuaFileContentType.Binary
             ? new BinaryLuaIOStream(mode, stream)
             : new TextLuaIOStream(mode, stream);
     }
@@ -87,7 +87,7 @@ public sealed class FileSystem : ILuaFileSystem
             stream = File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete);
         }
 
-        ILuaIOStream wrapper = contentType == LuaFileContentType.Bytes
+        ILuaIOStream wrapper = contentType == LuaFileContentType.Binary
             ? new BinaryLuaIOStream(luaMode, stream)
             : new TextLuaIOStream(luaMode, stream);
 
@@ -101,6 +101,11 @@ public sealed class FileSystem : ILuaFileSystem
 
     public ILuaIOStream Open(string path, LuaFileMode mode)
     {
+        if (mode is LuaFileMode.ReadBinaryOrText)
+        {
+            return new LuaChunkStream(File.OpenRead(path));
+        }
+
         var openMode = mode.GetOpenMode();
         var contentType = mode.GetContentType();
         return Open(path, openMode, contentType);

+ 98 - 0
src/Lua/IO/LuaChunkStream.cs

@@ -0,0 +1,98 @@
+using Lua.CodeAnalysis.Compilation;
+using System.Buffers;
+using System.Text;
+
+namespace Lua.IO
+{
+    public sealed class LuaChunkStream : ILuaIOStream
+    {
+        public LuaChunkStream(Stream stream)
+        {
+            using (stream)
+            {
+                var length = stream.Length;
+                if (length > int.MaxValue)
+                    throw new ArgumentOutOfRangeException(nameof(stream), "Stream length exceeds maximum size for Lua chunk.");
+
+                bytesToReturnToPool = ArrayPool<byte>.Shared.Rent((int)length);
+                try
+                {
+                    var count = stream.Read(bytesToReturnToPool.AsSpan());
+                    bytes = new ReadOnlyMemory<byte>(bytesToReturnToPool, 0, count);
+                }
+                catch (Exception)
+                {
+                    ArrayPool<byte>.Shared.Return(bytesToReturnToPool);
+                }
+            }
+        }
+
+        byte[]? bytesToReturnToPool;
+        char[]? charsToReturnToPool;
+        private readonly ReadOnlyMemory<byte> bytes;
+
+        public void Dispose()
+        {
+            if (bytesToReturnToPool is not null)
+            {
+                ArrayPool<byte>.Shared.Return(bytesToReturnToPool);
+            }
+            else if (charsToReturnToPool is not null)
+            {
+                ArrayPool<char>.Shared.Return(charsToReturnToPool);
+            }
+        }
+
+        public LuaFileOpenMode Mode => LuaFileOpenMode.Read;
+        public LuaFileContentType ContentType => LuaFileContentType.Unknown;
+
+        public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
+        {
+            var span = bytes.Span;
+            if (span.StartsWith(LuaCompiler.LuaByteCodeSignature))
+            {
+                var array = ArrayPool<byte>.Shared.Rent(span.Length);
+                bytesToReturnToPool = array;
+                return new ValueTask<LuaFileContent>(new LuaFileContent(array.AsMemory(span.Length)));
+            }
+            else
+            {
+                var encoding = Encoding.UTF8;
+                var array = ArrayPool<char>.Shared.Rent(encoding.GetMaxCharCount(span.Length));
+                var charCount = encoding.GetChars(span, array);
+                charsToReturnToPool = array;
+                return new ValueTask<LuaFileContent>(new LuaFileContent(array.AsMemory(0,charCount)));
+            }
+        }
+
+        public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public ValueTask FlushAsync(CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void SetVBuf(LuaFileBufferingMode mode, int size)
+        {
+            throw new NotSupportedException();
+        }
+
+        public long Seek(long offset, SeekOrigin origin)
+        {
+            throw new NotSupportedException();
+        }
+    }
+}

+ 6 - 2
src/Lua/IO/LuaFileMode.cs

@@ -33,7 +33,11 @@ public enum LuaFileMode
     ReadUpdateBinary = Read | Update | Binary, // r+b or rb+
     WriteUpdateBinary = Write | Update | Binary, // w+b or wb+
     AppendUpdateBinary = Append | Update | Binary, // a+b or ab+
-    
+
+    /// <summary>
+    /// This is used for load files. bt mode.
+    /// </summary>
+    ReadBinaryOrText = Read | Binary | Text, //(default is text, but can be binary)
 }
 
 public static class LuaFileModeExtensions
@@ -56,7 +60,7 @@ public static class LuaFileModeExtensions
     {
         // If binary flag is set, it's binary mode
         if ((mode & LuaFileMode.Binary) != 0)
-            return LuaFileContentType.Bytes;
+            return LuaFileContentType.Binary;
 
         // Otherwise it's text mode (even if Text flag is not explicitly set)
         return LuaFileContentType.Text;

+ 17 - 6
src/Lua/IO/TextLuaIOStream.cs

@@ -19,7 +19,7 @@ internal sealed class TextLuaIOStream(LuaFileOpenMode mode, Stream innerStream)
         return new(reader.ReadLine(innerStream));
     }
 
-    public ValueTask<LuaFileContent> ReadToEndAsync(CancellationToken cancellationToken)
+    public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
     {
         ThrowIfNotReadable();
         reader ??= new();
@@ -40,7 +40,7 @@ internal sealed class TextLuaIOStream(LuaFileOpenMode mode, Stream innerStream)
         {
             throw new InvalidOperationException("Cannot write binary content to a text stream. Use a binary stream instead.");
         }
-        
+
         return WriteAsync(content.ReadText(), cancellationToken);
     }
 
@@ -97,10 +97,11 @@ internal sealed class TextLuaIOStream(LuaFileOpenMode mode, Stream innerStream)
 
     public long Seek(long offset, SeekOrigin origin)
     {
-        if (reader != null&&origin== SeekOrigin.Current)
+        if (reader != null && origin == SeekOrigin.Current)
         {
             offset -= reader.Remain;
         }
+
         reader?.Clear();
         return innerStream.Seek(offset, origin);
     }
@@ -123,8 +124,18 @@ internal sealed class TextLuaIOStream(LuaFileOpenMode mode, Stream innerStream)
 
     public void Dispose()
     {
-        if (innerStream.CanWrite) innerStream.Flush();
-        innerStream.Dispose();
-        reader?.Dispose();
+        try
+        {
+            if (innerStream.CanWrite)
+            {
+                innerStream.Flush();
+            }
+
+            innerStream.Dispose();
+        }
+        finally
+        {
+            reader?.Dispose();
+        }
     }
 }

+ 23 - 87
src/Lua/LuaFileContent.cs

@@ -1,138 +1,74 @@
 using Lua.IO;
-using System.Buffers;
 
 namespace Lua;
 
 public enum LuaFileContentType
 {
+    Unknown = 0,
     Text,
-    Bytes
+    Binary
 }
 
-public readonly struct LuaFileContent : IDisposable
+public readonly struct LuaFileContent
 {
     public LuaFileContentType Type => type;
 
     readonly LuaFileContentType type;
-    private readonly int length = -1;
     readonly object referenceValue;
 
-    public LuaFileContent(string text, int length = -1)
+    public LuaFileContent(ReadOnlyMemory<char> memory)
     {
         type = LuaFileContentType.Text;
-        if (length == -1)
-            length = text.Length;
-
-
-        this.length = length;
-
-        referenceValue = text;
+        referenceValue = memory;
     }
 
-    public LuaFileContent(char[] text, int length = -1)
+    public LuaFileContent(ReadOnlyMemory<byte> memory)
     {
         type = LuaFileContentType.Text;
-        if (length == -1)
-            length = text.Length;
-
-        this.length = length;
-        referenceValue = text;
+        referenceValue = new BinaryData(memory);
     }
 
-    public LuaFileContent(byte[] bytes, int length = -1)
+    public LuaFileContent(IBinaryData data)
     {
-        type = LuaFileContentType.Bytes;
-        referenceValue = bytes;
-        if (length == -1)
-            length = bytes.Length;
-        this.length = length;
+        type = LuaFileContentType.Binary;
+        referenceValue = data;
     }
 
-
-    public LuaFileContent(IMemoryOwner<char> bytes)
+    public LuaFileContent(string text)
     {
         type = LuaFileContentType.Text;
-        referenceValue = bytes;
-    }
-
-    public LuaFileContent(IMemoryOwner<byte> bytes)
-    {
-        type = LuaFileContentType.Bytes;
-        referenceValue = bytes;
+        referenceValue = text;
     }
 
     public ReadOnlyMemory<char> ReadText()
     {
         if (type != LuaFileContentType.Text) throw new InvalidOperationException("Cannot read text from a LuaFileContent of type Bytes.");
-        if (referenceValue is IMemoryOwner<char> mem)
-        {
-            return mem.Memory;
-        }
-
-        if (referenceValue is char[] chars)
-        {
-            return chars.AsMemory(0, length);
-        }
-
-        return ((string)referenceValue).AsMemory(0, length);
+        if (referenceValue is string str) return str.AsMemory();
+        return ((ReadOnlyMemory<char>)referenceValue);
     }
-    
+
     public string ReadString()
     {
         if (type != LuaFileContentType.Text) throw new InvalidOperationException("Cannot read text from a LuaFileContent of type Bytes.");
-        if (referenceValue is string str && length == str.Length)
-        {
-            return (str);
-        }
-        if (referenceValue is IMemoryOwner<char> mem)
-        {
-            return mem.Memory.Span.ToString();
-        }
-
-        if (referenceValue is char[] chars)
-        {
-            return chars.AsSpan(0, length).ToString();
-        }
-
-        return ((string)referenceValue).Substring(0, length);
+        if (referenceValue is string str) return str;
+        return ((ReadOnlyMemory<char>)referenceValue).ToString();
     }
 
     public ReadOnlyMemory<byte> ReadBytes()
     {
-        if (type != LuaFileContentType.Bytes) throw new InvalidOperationException("Cannot read bytes from a LuaFileContent of type Text.");
-        if (referenceValue is IMemoryOwner<byte> mem)
-        {
-            return mem.Memory;
-        }
-
-        return ((byte[])referenceValue).AsMemory(0, length);
-    }
-
-    public void Dispose()
-    {
-        if (referenceValue is IDisposable memoryOwner)
-        {
-            memoryOwner.Dispose();
-        }
+        if (type != LuaFileContentType.Binary) throw new InvalidOperationException("Cannot read bytes from a LuaFileContent of type Text.");
+        return ((IBinaryData)referenceValue).Memory;
     }
 
     public LuaValue ToLuaValue()
     {
-        if (referenceValue is string str && length == str.Length)
+        if (type == LuaFileContentType.Binary)
         {
-            return (str);
+            return LuaValue.FromObject(referenceValue);
         }
-
-        using (this)
+        else
         {
-            if (type == LuaFileContentType.Bytes)
-            {
-                return LuaValue.FromObject(new ByteArrayData(ReadBytes().ToArray()));
-            }
-            else
-            {
-                return ReadText().Span.ToString();
-            }
+            return ReadString();
         }
     }
 }

+ 2 - 2
src/Lua/LuaStateExtensions.cs

@@ -51,9 +51,9 @@ public static class LuaStateExtensions
         }
 
         using var stream = state.FileSystem.Open(fileName, openFlags);
-        using var content = await stream.ReadToEndAsync(cancellationToken);
+        var content = await stream.ReadAllAsync(cancellationToken);
             
-        if (content.Type == LuaFileContentType.Bytes)
+        if (content.Type == LuaFileContentType.Binary)
         {
             closure = state.Load(content.ReadBytes().Span, name, mode, environment);
         }

+ 3 - 3
src/Lua/Standard/BasicLibrary.cs

@@ -200,13 +200,13 @@ public sealed class BasicLibrary
                 // TODO: 
                 throw new NotImplementedException();
             }
-            else if (arg0.TryRead<ByteArrayData>(out var rawBytes))
+            else if (arg0.TryRead<IBinaryData>(out var binaryData))
             {
-                return new(context.Return(context.State.Load(rawBytes.Bytes, name, "bt", arg3)));
+                return new(context.Return(context.State.Load(binaryData.Memory.Span, name, "bt", arg3)));
             }
             else
             {
-                LuaRuntimeException.BadArgument(context.Thread, 1, [LuaValueType.String, LuaValueType.Function], arg0.Type);
+                LuaRuntimeException.BadArgument(context.Thread, 1, ["string", "function,binary data"], arg0.TypeToString());
                 return default; // dummy
             }
         }

+ 1 - 1
src/Lua/Standard/FileHandle.cs

@@ -60,7 +60,7 @@ public class FileHandle : ILuaUserData
 
     public ValueTask<LuaFileContent> ReadToEndAsync(CancellationToken cancellationToken)
     {
-        return stream.ReadToEndAsync(cancellationToken);
+        return stream.ReadAllAsync(cancellationToken);
     }
 
     public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)

+ 3 - 3
src/Lua/Standard/Internal/IOHelper.cs

@@ -49,11 +49,11 @@ internal static class IOHelper
                     using var fileBuffer = new PooledArray<char>(64);
                     var span = fileBuffer.AsSpan();
                     d.TryFormat(span, out var charsWritten);
-                    await file.WriteAsync(new(fileBuffer.UnderlyingArray, charsWritten), cancellationToken);
+                    await file.WriteAsync(new(fileBuffer.UnderlyingArray.AsMemory(0,charsWritten) ), cancellationToken);
                 }
-                else if (arg.TryRead<ByteArrayData>(out var rawBytes))
+                else if (arg.TryRead<IBinaryData>(out var binaryData))
                 {
-                    await file.WriteAsync(rawBytes.AsLuaFileContent(), cancellationToken);
+                    await file.WriteAsync(new (binaryData), cancellationToken);
                 }
                 else
                 {

+ 2 - 8
tests/Lua.Tests/Helpers/CharMemoryStream.cs

@@ -45,16 +45,10 @@ internal sealed class ReadOnlyCharMemoryLuaIOStream(ReadOnlyMemory<char> buffer,
         return new(line);
     }
 
-    public override ValueTask<LuaFileContent> ReadToEndAsync(CancellationToken cancellationToken)
+    public override ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
     {
-        if (position >= Buffer.Length)
-        {
-            return new(new LuaFileContent(string.Empty));
-        }
-
-        var remaining = Buffer[position..];
         position = Buffer.Length;
-        return new( new LuaFileContent(remaining.ToString()));
+        return new( new LuaFileContent(Buffer.ToArray()));
     }
 
     public override ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)

+ 1 - 1
tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs

@@ -16,7 +16,7 @@ namespace Lua.Tests.Helpers
             throw IOThrowHelpers.GetNotSupportedException();
         }
 
-        public virtual ValueTask<LuaFileContent> ReadToEndAsync(CancellationToken cancellationToken)
+        public virtual ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
         {
             throw IOThrowHelpers.GetNotSupportedException();
         }

+ 63 - 63
tests/Lua.Tests/IOTests.cs

@@ -13,7 +13,7 @@ public class IOTests : IDisposable
     {
         testDirectory = Path.Combine(Path.GetTempPath(), $"LuaIOTests_{Guid.NewGuid()}");
         Directory.CreateDirectory(testDirectory);
-        fileSystem = new FileSystem();
+        fileSystem = new();
     }
 
     public void Dispose()
@@ -76,8 +76,8 @@ public class IOTests : IDisposable
     {
         Assert.That(LuaFileMode.Read.GetContentType(), Is.EqualTo(LuaFileContentType.Text));
         Assert.That(LuaFileMode.ReadText.GetContentType(), Is.EqualTo(LuaFileContentType.Text));
-        Assert.That(LuaFileMode.ReadBinary.GetContentType(), Is.EqualTo(LuaFileContentType.Bytes));
-        Assert.That(LuaFileMode.WriteBinary.GetContentType(), Is.EqualTo(LuaFileContentType.Bytes));
+        Assert.That(LuaFileMode.ReadBinary.GetContentType(), Is.EqualTo(LuaFileContentType.Binary));
+        Assert.That(LuaFileMode.WriteBinary.GetContentType(), Is.EqualTo(LuaFileContentType.Binary));
     }
 
     [Test]
@@ -90,14 +90,14 @@ public class IOTests : IDisposable
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
             Assert.That(stream.ContentType, Is.EqualTo(LuaFileContentType.Text));
-            await stream.WriteAsync(new LuaFileContent(testContent), CancellationToken.None);
+            await stream.WriteAsync(new(testContent), CancellationToken.None);
         }
 
         // Read text
         using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
         {
             Assert.That(stream.ContentType, Is.EqualTo(LuaFileContentType.Text));
-            using var content = await stream.ReadToEndAsync(CancellationToken.None);
+            var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Text));
             Assert.That(content.ReadString(), Is.EqualTo(testContent));
         }
@@ -112,16 +112,16 @@ public class IOTests : IDisposable
         // Write bytes
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteBinary))
         {
-            Assert.That(stream.ContentType, Is.EqualTo(LuaFileContentType.Bytes));
-            await stream.WriteAsync(new LuaFileContent(testBytes), CancellationToken.None);
+            Assert.That(stream.ContentType, Is.EqualTo(LuaFileContentType.Binary));
+            await stream.WriteAsync(new(testBytes), CancellationToken.None);
         }
 
         // Read bytes
         using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadBinary))
         {
-            Assert.That(stream.ContentType, Is.EqualTo(LuaFileContentType.Bytes));
-            using var content = await stream.ReadToEndAsync(CancellationToken.None);
-            Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Bytes));
+            Assert.That(stream.ContentType, Is.EqualTo(LuaFileContentType.Binary));
+            var content = await stream.ReadAllAsync(CancellationToken.None);
+            Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Binary));
             Assert.That(content.ReadBytes().ToArray(), Is.EqualTo(testBytes));
         }
     }
@@ -130,10 +130,10 @@ public class IOTests : IDisposable
     public async Task TextStream_Cannot_Write_Binary_Content()
     {
         var testFile = GetTestFilePath("text_binary_mix.txt");
-        
+
         using var stream = fileSystem.Open(testFile, LuaFileMode.WriteText);
         var binaryContent = new LuaFileContent(new byte[] { 0x00, 0x01 });
-        
+
         Assert.ThrowsAsync<InvalidOperationException>(
             async () => await stream.WriteAsync(binaryContent, CancellationToken.None)
         );
@@ -143,10 +143,10 @@ public class IOTests : IDisposable
     public async Task BinaryStream_Cannot_Write_Text_Content()
     {
         var testFile = GetTestFilePath("binary_text_mix.bin");
-        
+
         using var stream = fileSystem.Open(testFile, LuaFileMode.WriteBinary);
         var textContent = new LuaFileContent("Hello");
-        
+
         Assert.ThrowsAsync<InvalidOperationException>(
             async () => await stream.WriteAsync(textContent, CancellationToken.None)
         );
@@ -157,11 +157,11 @@ public class IOTests : IDisposable
     {
         var testFile = GetTestFilePath("multiline.txt");
         var lines = new[] { "Line 1", "Line 2", "Line 3" };
-        
+
         // Write multiple lines
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
-            await stream.WriteAsync(new LuaFileContent(string.Join("\n", lines)), CancellationToken.None);
+            await stream.WriteAsync(new(string.Join("\n", lines)), CancellationToken.None);
         }
 
         // Read lines one by one
@@ -172,7 +172,7 @@ public class IOTests : IDisposable
                 var line = await stream.ReadLineAsync(CancellationToken.None);
                 Assert.That(line, Is.EqualTo(lines[i]));
             }
-            
+
             // EOF should return null
             var eofLine = await stream.ReadLineAsync(CancellationToken.None);
             Assert.That(eofLine, Is.Null);
@@ -184,11 +184,11 @@ public class IOTests : IDisposable
     {
         var testFile = GetTestFilePath("read_string.txt");
         var testContent = "Hello, World!";
-        
+
         // Write content
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
-            await stream.WriteAsync(new LuaFileContent(testContent), CancellationToken.None);
+            await stream.WriteAsync(new(testContent), CancellationToken.None);
         }
 
         // Read partial strings
@@ -196,13 +196,13 @@ public class IOTests : IDisposable
         {
             var part1 = await stream.ReadStringAsync(5, CancellationToken.None);
             Assert.That(part1, Is.EqualTo("Hello"));
-            
+
             var part2 = await stream.ReadStringAsync(7, CancellationToken.None);
             Assert.That(part2, Is.EqualTo(", World"));
-            
+
             var part3 = await stream.ReadStringAsync(1, CancellationToken.None);
             Assert.That(part3, Is.EqualTo("!")); // Only 1 char left
-            
+
             var eof = await stream.ReadStringAsync(10, CancellationToken.None);
             Assert.That(eof, Is.Null);
         }
@@ -212,10 +212,10 @@ public class IOTests : IDisposable
     public async Task BinaryStream_Cannot_Use_Text_Operations()
     {
         var testFile = GetTestFilePath("binary_no_text.bin");
-        
+
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteBinary))
         {
-            await stream.WriteAsync(new LuaFileContent(new byte[] { 0x01, 0x02 }), CancellationToken.None);
+            await stream.WriteAsync(new(new byte[] { 0x01, 0x02 }), CancellationToken.None);
         }
 
         using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadBinary))
@@ -223,7 +223,7 @@ public class IOTests : IDisposable
             Assert.ThrowsAsync<InvalidOperationException>(
                 async () => await stream.ReadLineAsync(CancellationToken.None)
             );
-            
+
             Assert.ThrowsAsync<InvalidOperationException>(
                 async () => await stream.ReadStringAsync(10, CancellationToken.None)
             );
@@ -234,23 +234,23 @@ public class IOTests : IDisposable
     public async Task Append_Mode_Appends_Content()
     {
         var testFile = GetTestFilePath("append_test.txt");
-        
+
         // Write initial content
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
-            await stream.WriteAsync(new LuaFileContent("Hello"), CancellationToken.None);
+            await stream.WriteAsync(new("Hello"), CancellationToken.None);
         }
-        
+
         // Append content
         using (var stream = fileSystem.Open(testFile, LuaFileMode.AppendText))
         {
-            await stream.WriteAsync(new LuaFileContent(" World"), CancellationToken.None);
+            await stream.WriteAsync(new(" World"), CancellationToken.None);
         }
-        
+
         // Read and verify
         using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
         {
-            using var content = await stream.ReadToEndAsync(CancellationToken.None);
+            var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.ReadString(), Is.EqualTo("Hello World"));
         }
     }
@@ -260,11 +260,11 @@ public class IOTests : IDisposable
     {
         var testFile = GetTestFilePath("seek_test.txt");
         var testContent = "0123456789";
-        
+
         // Write content
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
-            await stream.WriteAsync(new LuaFileContent(testContent), CancellationToken.None);
+            await stream.WriteAsync(new(testContent), CancellationToken.None);
         }
 
         // Test seeking
@@ -274,12 +274,12 @@ public class IOTests : IDisposable
             stream.Seek(5, SeekOrigin.Begin);
             var afterBegin = await stream.ReadStringAsync(3, CancellationToken.None);
             Assert.That(afterBegin, Is.EqualTo("567"));
-            
+
             // Seek from current
             stream.Seek(-2, SeekOrigin.Current);
             var afterCurrent = await stream.ReadStringAsync(2, CancellationToken.None);
             Assert.That(afterCurrent, Is.EqualTo("67"));
-            
+
             // Seek from end
             stream.Seek(-3, SeekOrigin.End);
             var afterEnd = await stream.ReadStringAsync(3, CancellationToken.None);
@@ -292,11 +292,11 @@ public class IOTests : IDisposable
     {
         var oldPath = GetTestFilePath("old_name.txt");
         var newPath = GetTestFilePath("new_name.txt");
-        
+
         File.WriteAllText(oldPath, "test content");
-        
+
         fileSystem.Rename(oldPath, newPath);
-        
+
         Assert.That(File.Exists(oldPath), Is.False);
         Assert.That(File.Exists(newPath), Is.True);
         Assert.That(File.ReadAllText(newPath), Is.EqualTo("test content"));
@@ -306,12 +306,12 @@ public class IOTests : IDisposable
     public void FileSystem_Remove_Works()
     {
         var testFile = GetTestFilePath("remove_test.txt");
-        
+
         File.WriteAllText(testFile, "test content");
         Assert.That(File.Exists(testFile), Is.True);
-        
+
         fileSystem.Remove(testFile);
-        
+
         Assert.That(File.Exists(testFile), Is.False);
     }
 
@@ -320,9 +320,9 @@ public class IOTests : IDisposable
     {
         var existingFile = GetTestFilePath("readable.txt");
         var nonExistentFile = GetTestFilePath("non_existent.txt");
-        
+
         File.WriteAllText(existingFile, "test");
-        
+
         Assert.That(fileSystem.IsReadable(existingFile), Is.True);
         Assert.That(fileSystem.IsReadable(nonExistentFile), Is.False);
     }
@@ -331,16 +331,16 @@ public class IOTests : IDisposable
     public async Task FileSystem_TempFile_Works()
     {
         string? tempPath = null;
-        
+
         try
         {
             using (var tempStream = fileSystem.OpenTempFileStream())
             {
-                await tempStream.WriteAsync(new LuaFileContent("temp content"), CancellationToken.None);
-                
+                await tempStream.WriteAsync(new("temp content"), CancellationToken.None);
+
                 // Seek and read
                 tempStream.Seek(0, SeekOrigin.Begin);
-                using var content = await tempStream.ReadToEndAsync(CancellationToken.None);
+                var content = await tempStream.ReadAllAsync(CancellationToken.None);
                 Assert.That(content.ReadString(), Is.EqualTo("temp content"));
             }
         }
@@ -366,25 +366,25 @@ public class IOTests : IDisposable
     public async Task Buffering_Modes_Work()
     {
         var testFile = GetTestFilePath("buffer_test.txt");
-        
+
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
             // Set no buffering
             stream.SetVBuf(LuaFileBufferingMode.NoBuffering, 0);
-            await stream.WriteAsync(new LuaFileContent("No buffer"), CancellationToken.None);
-            
+            await stream.WriteAsync(new("No buffer"), CancellationToken.None);
+
             // Set line buffering
             stream.SetVBuf(LuaFileBufferingMode.LineBuffering, 1024);
-            await stream.WriteAsync(new LuaFileContent("\nLine buffer"), CancellationToken.None);
-            
+            await stream.WriteAsync(new("\nLine buffer"), CancellationToken.None);
+
             // Set full buffering
             stream.SetVBuf(LuaFileBufferingMode.FullBuffering, 4096);
-            await stream.WriteAsync(new LuaFileContent("\nFull buffer"), CancellationToken.None);
-            
+            await stream.WriteAsync(new("\nFull buffer"), CancellationToken.None);
+
             // Explicit flush
             await stream.FlushAsync(CancellationToken.None);
         }
-        
+
         // Verify content was written
         var writtenContent = File.ReadAllText(testFile);
         Assert.That(writtenContent, Does.Contain("No buffer"));
@@ -396,30 +396,30 @@ public class IOTests : IDisposable
     public async Task LuaFileContent_Memory_Variations()
     {
         var testFile = GetTestFilePath("memory_test.txt");
-        
+
         // Test with char array
         var charArray = "Hello from char array".ToCharArray();
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
-            await stream.WriteAsync(new LuaFileContent(charArray), CancellationToken.None);
+            await stream.WriteAsync(new(charArray), CancellationToken.None);
         }
-        
+
         using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
         {
-            using var content = await stream.ReadToEndAsync(CancellationToken.None);
+            var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.ReadString(), Is.EqualTo("Hello from char array"));
         }
-        
+
         // Test with partial char array
         var longCharArray = "Hello World!!!".ToCharArray();
         using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
         {
-            await stream.WriteAsync(new LuaFileContent(longCharArray, 11), CancellationToken.None); // Only "Hello World"
+            await stream.WriteAsync(new(longCharArray.AsMemory(0, 11)), CancellationToken.None); // Only "Hello World"
         }
-        
+
         using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
         {
-            using var content = await stream.ReadToEndAsync(CancellationToken.None);
+            var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.ReadString(), Is.EqualTo("Hello World"));
         }
     }