feat(core): Makes spanwriter resizable (#376)
SpanWriter now has an argument that allows it to be resizable.
```cs
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
public SpanWriter(int initialCapacity, bool resize = false)
```
If a SpanWriter is set to be resizable, or given an initial capacity instead of a buffer, it must be disposed:
```cs
public static void SomeMethod()
{
using var writer = new SpanWriter(stackalloc byte[512], true);
// stuff
// Automatic Dispose of writer
}
```
This is because the SpanWriter uses `ArrayPool<byte>.Shared` _rented buffers_ to resize. If the writer is not disposed, those buffers will never be reused resulting in a _memory leak_.
It is possible that the SpanWriter will outright ditch the initial buffer if resize is set to true and growing is needed. To check/account for that we can do the following:
```cs
public static void SomeMethod()
{
Span<byte> span = stackalloc byte[512];
using var writer = new SpanWriter(span, true);
// write some stuff that causes the span to grow
span = writer.RawBuffer;
// Do stuff with the span
}
```
Make sure you don't accidentally `Dispose()` the writer or use the `RawBuffer` outside of the `using` block. If you do, bad things will happen! (NullPointerException, or a fresh SpanWriter with no buffer, depending on the situation).
SpanWriter can also now be used with a fixed statement since it has a `PinnableReference()` function.
This commit is contained in:
parent
2a2af424ad
commit
582e1877b8
18 changed files with 298 additions and 134 deletions
|
|
@ -52,14 +52,7 @@
|
||||||
<AnalysisLevel>latest</AnalysisLevel>
|
<AnalysisLevel>latest</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37">
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.4.165-alpha">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
|
|
||||||
<Version>3.3.1</Version>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.0-preview-20201123-03" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||||
|
|
|
||||||
63
Projects/Server.Tests/Tests/Buffers/SpanWriterTests.cs
Normal file
63
Projects/Server.Tests/Tests/Buffers/SpanWriterTests.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Server.Tests
|
||||||
|
{
|
||||||
|
public class SpanWriterTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public unsafe void TestSpanWriterResizes()
|
||||||
|
{
|
||||||
|
Span<byte> smallStack = stackalloc byte[8];
|
||||||
|
using var writer = new SpanWriter(smallStack, true);
|
||||||
|
writer.Write(0x1024L);
|
||||||
|
writer.Write(0x1024L);
|
||||||
|
|
||||||
|
var span = writer.RawBuffer;
|
||||||
|
fixed (byte* spanPtr = span)
|
||||||
|
{
|
||||||
|
fixed (byte* stackPtr = smallStack)
|
||||||
|
{
|
||||||
|
Assert.True(spanPtr != stackPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(span.Length > smallStack.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public unsafe void TestSpanWriterOnlyStackAlloc()
|
||||||
|
{
|
||||||
|
Span<byte> smallStack = stackalloc byte[8];
|
||||||
|
using var writer = new SpanWriter(smallStack, true);
|
||||||
|
writer.Write(0x1024L);
|
||||||
|
|
||||||
|
var span = writer.RawBuffer;
|
||||||
|
fixed (byte* spanPtr = span)
|
||||||
|
{
|
||||||
|
fixed (byte* stackPtr = smallStack)
|
||||||
|
{
|
||||||
|
Assert.True(spanPtr == stackPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(span.Length == smallStack.Length);
|
||||||
|
AssertThat.Equal(smallStack, stackalloc byte[] { 0, 0, 0, 0, 0, 0, 0x10, 0x24 });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestSpanWriterNoResizeThrows()
|
||||||
|
{
|
||||||
|
Assert.Throws<OutOfMemoryException>(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
Span<byte> smallStack = stackalloc byte[8];
|
||||||
|
using var writer = new SpanWriter(smallStack);
|
||||||
|
writer.Write(0x1024L);
|
||||||
|
writer.Write(0x1024L);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -370,7 +370,7 @@ namespace Server.Network
|
||||||
Position = origin switch
|
Position = origin switch
|
||||||
{
|
{
|
||||||
SeekOrigin.Begin => offset,
|
SeekOrigin.Begin => offset,
|
||||||
SeekOrigin.End => Length - offset,
|
SeekOrigin.End => Length + offset,
|
||||||
_ => Position + offset // Current
|
_ => Position + offset // Current
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -528,7 +528,7 @@ namespace System.Buffers
|
||||||
Position = origin switch
|
Position = origin switch
|
||||||
{
|
{
|
||||||
SeekOrigin.Begin => offset,
|
SeekOrigin.Begin => offset,
|
||||||
SeekOrigin.End => Length - offset,
|
SeekOrigin.End => Length + offset,
|
||||||
_ => Position + offset // Current
|
_ => Position + offset // Current
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
*************************************************************************/
|
*************************************************************************/
|
||||||
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -176,12 +177,44 @@ namespace System.Buffers
|
||||||
public string ReadAscii() => ReadString(Encoding.ASCII);
|
public string ReadAscii() => ReadString(Encoding.ASCII);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Seek(int offset, SeekOrigin origin) =>
|
public int Seek(int offset, SeekOrigin origin)
|
||||||
Position = origin switch
|
{
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.End || offset <= 0,
|
||||||
|
"Attempting to seek to a position beyond capacity using SeekOrigin.End"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.End || offset >= -_buffer.Length,
|
||||||
|
"Attempting to seek to a negative position using SeekOrigin.End"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Begin || offset >= 0,
|
||||||
|
"Attempting to seek to a negative position using SeekOrigin.Begin"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Begin || offset <= _buffer.Length,
|
||||||
|
"Attempting to seek to a position beyond the capacity using SeekOrigin.Begin"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Current || Position + offset >= 0,
|
||||||
|
"Attempting to seek to a negative position using SeekOrigin.Current"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Current || Position + offset <= _buffer.Length,
|
||||||
|
"Attempting to seek to a position beyond the capacity using SeekOrigin.Current"
|
||||||
|
);
|
||||||
|
|
||||||
|
return Position = Math.Max(0, origin switch
|
||||||
{
|
{
|
||||||
SeekOrigin.Begin => offset,
|
SeekOrigin.Current => Position + offset,
|
||||||
SeekOrigin.End => Length - offset,
|
SeekOrigin.End => _buffer.Length + offset,
|
||||||
_ => Position + offset // Current
|
_ => offset // Begin
|
||||||
};
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,10 @@
|
||||||
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Server;
|
using Server;
|
||||||
|
|
||||||
|
|
@ -24,122 +26,173 @@ namespace System.Buffers
|
||||||
{
|
{
|
||||||
public ref struct SpanWriter
|
public ref struct SpanWriter
|
||||||
{
|
{
|
||||||
private readonly Span<byte> _buffer;
|
private readonly bool _resize;
|
||||||
|
private byte[]? _arrayToReturnToPool;
|
||||||
|
private Span<byte> _buffer;
|
||||||
|
private int _position;
|
||||||
|
|
||||||
public int Length => _buffer.Length;
|
public int Length { get; private set; }
|
||||||
|
|
||||||
|
public int Position
|
||||||
|
{
|
||||||
|
get => _position;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_position = value;
|
||||||
|
|
||||||
|
if (value > Length)
|
||||||
|
{
|
||||||
|
Length = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Capacity => _buffer.Length;
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Span => _buffer.Slice(0, Position);
|
public ReadOnlySpan<byte> Span => _buffer.Slice(0, Position);
|
||||||
|
|
||||||
public int Position { get; private set; }
|
public Span<byte> RawBuffer => _buffer;
|
||||||
|
|
||||||
public SpanWriter(Span<byte> span)
|
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
|
||||||
{
|
{
|
||||||
_buffer = span;
|
_resize = resize;
|
||||||
Position = 0;
|
_buffer = initialBuffer;
|
||||||
|
_position = 0;
|
||||||
|
Length = 0;
|
||||||
|
_arrayToReturnToPool = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpanWriter(int initialCapacity, bool resize = false)
|
||||||
|
{
|
||||||
|
_resize = resize;
|
||||||
|
_arrayToReturnToPool = ArrayPool<byte>.Shared.Rent(initialCapacity);
|
||||||
|
_buffer = _arrayToReturnToPool;
|
||||||
|
_position = 0;
|
||||||
|
Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void Grow(int additionalCapacity)
|
||||||
|
{
|
||||||
|
var newSize = Math.Max(Length + additionalCapacity, _buffer.Length * 2);
|
||||||
|
byte[] poolArray = ArrayPool<byte>.Shared.Rent(newSize);
|
||||||
|
|
||||||
|
_buffer.Slice(0, Length).CopyTo(poolArray);
|
||||||
|
|
||||||
|
byte[]? toReturn = _arrayToReturnToPool;
|
||||||
|
_buffer = _arrayToReturnToPool = poolArray;
|
||||||
|
if (toReturn != null)
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(toReturn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void GrowIfNeeded(int count)
|
||||||
|
{
|
||||||
|
if (_position + count > _buffer.Length)
|
||||||
|
{
|
||||||
|
if (!_resize)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Grow(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer);
|
||||||
|
|
||||||
|
public void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity > _buffer.Length)
|
||||||
|
{
|
||||||
|
if (!_resize)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Grow(capacity - Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public unsafe void Write(bool value)
|
public unsafe void Write(bool value)
|
||||||
{
|
{
|
||||||
if (Position >= Length)
|
GrowIfNeeded(1);
|
||||||
{
|
_buffer[Position++] = *(byte*)&value;
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffer[Position++] = *(byte*) & value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(byte value)
|
public void Write(byte value)
|
||||||
{
|
{
|
||||||
|
GrowIfNeeded(1);
|
||||||
_buffer[Position++] = value;
|
_buffer[Position++] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(sbyte value)
|
public void Write(sbyte value)
|
||||||
{
|
{
|
||||||
|
GrowIfNeeded(1);
|
||||||
_buffer[Position++] = (byte)value;
|
_buffer[Position++] = (byte)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(short value)
|
public void Write(short value)
|
||||||
{
|
{
|
||||||
if (!BinaryPrimitives.TryWriteInt16BigEndian(_buffer.Slice(Position), value))
|
GrowIfNeeded(2);
|
||||||
{
|
BinaryPrimitives.WriteInt16BigEndian(_buffer.Slice(_position), value);
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position += 2;
|
Position += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(ushort value)
|
public void Write(ushort value)
|
||||||
{
|
{
|
||||||
if (!BinaryPrimitives.TryWriteUInt16BigEndian(_buffer.Slice(Position), value))
|
GrowIfNeeded(2);
|
||||||
{
|
BinaryPrimitives.WriteUInt16BigEndian(_buffer.Slice(_position), value);
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position += 2;
|
Position += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(int value)
|
public void Write(int value)
|
||||||
{
|
{
|
||||||
if (!BinaryPrimitives.TryWriteInt32BigEndian(_buffer.Slice(Position), value))
|
GrowIfNeeded(4);
|
||||||
{
|
BinaryPrimitives.WriteInt32BigEndian(_buffer.Slice(_position), value);
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position += 4;
|
Position += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(uint value)
|
public void Write(uint value)
|
||||||
{
|
{
|
||||||
if (!BinaryPrimitives.TryWriteUInt32BigEndian(_buffer.Slice(Position), value))
|
GrowIfNeeded(4);
|
||||||
{
|
BinaryPrimitives.WriteUInt32BigEndian(_buffer.Slice(_position), value);
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position += 4;
|
Position += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(long value)
|
public void Write(long value)
|
||||||
{
|
{
|
||||||
if (!BinaryPrimitives.TryWriteInt64BigEndian(_buffer.Slice(Position), value))
|
GrowIfNeeded(8);
|
||||||
{
|
BinaryPrimitives.WriteInt64BigEndian(_buffer.Slice(_position), value);
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position += 8;
|
Position += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write(ulong value)
|
public void Write(ulong value)
|
||||||
{
|
{
|
||||||
if (!BinaryPrimitives.TryWriteUInt64BigEndian(_buffer.Slice(Position), value))
|
GrowIfNeeded(8);
|
||||||
{
|
BinaryPrimitives.WriteUInt64BigEndian(_buffer.Slice(_position), value);
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position += 8;
|
Position += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(ReadOnlySpan<byte> buffer)
|
public void Write(ReadOnlySpan<byte> buffer)
|
||||||
{
|
{
|
||||||
var size = buffer.Length;
|
var count = buffer.Length;
|
||||||
if (Position + size > Length)
|
GrowIfNeeded(count);
|
||||||
{
|
buffer.CopyTo(_buffer.Slice(_position));
|
||||||
throw new OutOfMemoryException();
|
Position += count;
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Slice(0, size).CopyTo(_buffer.Slice(Position));
|
|
||||||
Position += size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteString<T>(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable<T>
|
public void WriteString<T>(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable<T>
|
||||||
{
|
{
|
||||||
int sizeT = Unsafe.SizeOf<T>();
|
int sizeT = Unsafe.SizeOf<T>();
|
||||||
|
|
@ -156,12 +209,9 @@ namespace System.Buffers
|
||||||
|
|
||||||
var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value);
|
var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value);
|
||||||
|
|
||||||
if (Position + byteCount > Length)
|
GrowIfNeeded(byteCount);
|
||||||
{
|
|
||||||
throw new OutOfMemoryException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytesWritten = encoding.GetBytes(src, _buffer.Slice(Position));
|
var bytesWritten = encoding.GetBytes(src, _buffer.Slice(_position));
|
||||||
Position += bytesWritten;
|
Position += bytesWritten;
|
||||||
|
|
||||||
if (fixedLength > -1)
|
if (fixedLength > -1)
|
||||||
|
|
@ -194,7 +244,7 @@ namespace System.Buffers
|
||||||
public void WriteBigUniNull(string value)
|
public void WriteBigUniNull(string value)
|
||||||
{
|
{
|
||||||
WriteString<char>(value, Utility.Unicode);
|
WriteString<char>(value, Utility.Unicode);
|
||||||
Write((ushort)0);
|
Write((ushort)0); // '\0'
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -207,7 +257,7 @@ namespace System.Buffers
|
||||||
public void WriteUTF8Null(string value)
|
public void WriteUTF8Null(string value)
|
||||||
{
|
{
|
||||||
WriteString<byte>(value, Utility.UTF8);
|
WriteString<byte>(value, Utility.UTF8);
|
||||||
Write((byte)0);
|
Write((byte)0); // '\0'
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -217,38 +267,77 @@ namespace System.Buffers
|
||||||
public void WriteAsciiNull(string value)
|
public void WriteAsciiNull(string value)
|
||||||
{
|
{
|
||||||
WriteString<byte>(value, Encoding.ASCII);
|
WriteString<byte>(value, Encoding.ASCII);
|
||||||
Write((byte)0);
|
Write((byte)0); // '\0'
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void WriteAscii(string value, int fixedLength) => WriteString<byte>(value, Encoding.ASCII, fixedLength);
|
public void WriteAscii(string value, int fixedLength) => WriteString<byte>(value, Encoding.ASCII, fixedLength);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Clear()
|
public void Clear(int count)
|
||||||
{
|
{
|
||||||
_buffer.Slice(Position).Clear();
|
GrowIfNeeded(count);
|
||||||
Position = Length;
|
_buffer.Slice(_position, count).Clear();
|
||||||
|
Position += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Clear(int amount)
|
public int Seek(int offset, SeekOrigin origin)
|
||||||
{
|
{
|
||||||
if (Position + amount > Length)
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.End || _resize || offset <= 0,
|
||||||
|
"Attempting to seek to a position beyond capacity using SeekOrigin.End without resize"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.End || offset >= -_buffer.Length,
|
||||||
|
"Attempting to seek to a negative position using SeekOrigin.End"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Begin || offset >= 0,
|
||||||
|
"Attempting to seek to a negative position using SeekOrigin.Begin"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length,
|
||||||
|
"Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Current || _position + offset >= 0,
|
||||||
|
"Attempting to seek to a negative position using SeekOrigin.Current"
|
||||||
|
);
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length,
|
||||||
|
"Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize"
|
||||||
|
);
|
||||||
|
|
||||||
|
var newPosition = Math.Max(0, origin switch
|
||||||
{
|
{
|
||||||
throw new OutOfMemoryException();
|
SeekOrigin.Current => _position + offset,
|
||||||
|
SeekOrigin.End => Length + offset,
|
||||||
|
_ => offset // Begin
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newPosition >= _buffer.Length)
|
||||||
|
{
|
||||||
|
Grow(newPosition - _buffer.Length + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Slice(Position, amount).Clear();
|
return _position = newPosition;
|
||||||
Position += amount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Seek(int offset, SeekOrigin origin) =>
|
public void Dispose()
|
||||||
Position = origin switch
|
{
|
||||||
|
byte[] toReturn = _arrayToReturnToPool;
|
||||||
|
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
|
||||||
|
if (toReturn != null)
|
||||||
{
|
{
|
||||||
SeekOrigin.Begin => offset,
|
ArrayPool<byte>.Shared.Return(toReturn);
|
||||||
SeekOrigin.End => Length - offset,
|
}
|
||||||
_ => Position + offset // Current
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.0-preview-20201123-03" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Server.Guilds
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var playerRank = pm.GuildRank;
|
var playerRank = pm!.GuildRank;
|
||||||
|
|
||||||
switch (info.ButtonID)
|
switch (info.ButtonID)
|
||||||
{
|
{
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Server.Gumps
|
||||||
m_Page = page;
|
m_Page = page;
|
||||||
m_List = list;
|
m_List = list;
|
||||||
|
|
||||||
var p = (Point2D)prop.GetValue(o, null);
|
var p = (Point2D)(prop?.GetValue(o, null) ?? new Point2D());
|
||||||
|
|
||||||
AddPage(0);
|
AddPage(0);
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ namespace Server.Gumps
|
||||||
var y = BorderSize + OffsetSize;
|
var y = BorderSize + OffsetSize;
|
||||||
|
|
||||||
AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID);
|
AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID);
|
||||||
AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop.Name);
|
AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop?.Name);
|
||||||
x += EntryWidth + OffsetSize;
|
x += EntryWidth + OffsetSize;
|
||||||
|
|
||||||
if (SetGumpID != 0)
|
if (SetGumpID != 0)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Server.Gumps
|
||||||
m_Page = page;
|
m_Page = page;
|
||||||
m_List = list;
|
m_List = list;
|
||||||
|
|
||||||
var p = (Point3D)prop.GetValue(o, null);
|
var p = (Point3D)(prop?.GetValue(o, null) ?? new Point3D());
|
||||||
|
|
||||||
AddPage(0);
|
AddPage(0);
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ namespace Server.Gumps
|
||||||
var y = BorderSize + OffsetSize;
|
var y = BorderSize + OffsetSize;
|
||||||
|
|
||||||
AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID);
|
AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID);
|
||||||
AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop.Name);
|
AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop?.Name);
|
||||||
x += EntryWidth + OffsetSize;
|
x += EntryWidth + OffsetSize;
|
||||||
|
|
||||||
if (SetGumpID != 0)
|
if (SetGumpID != 0)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ namespace Server.Gumps
|
||||||
m_Page = page;
|
m_Page = page;
|
||||||
m_List = list;
|
m_List = list;
|
||||||
|
|
||||||
var ts = (TimeSpan)prop.GetValue(o, null);
|
var ts = (TimeSpan)(prop?.GetValue(o, null) ?? new TimeSpan());
|
||||||
|
|
||||||
AddPage(0);
|
AddPage(0);
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ namespace Server.Gumps
|
||||||
OffsetGumpID
|
OffsetGumpID
|
||||||
);
|
);
|
||||||
|
|
||||||
AddRect(0, prop.Name, 0, -1);
|
AddRect(0, prop?.Name, 0, -1);
|
||||||
AddRect(1, ts.ToString(), 0, -1);
|
AddRect(1, ts.ToString(), 0, -1);
|
||||||
AddRect(2, "Zero", 1, -1);
|
AddRect(2, "Zero", 1, -1);
|
||||||
AddRect(3, "From H:M:S", 2, -1);
|
AddRect(3, "From H:M:S", 2, -1);
|
||||||
|
|
|
||||||
|
|
@ -29,24 +29,22 @@ namespace Server.Items
|
||||||
|
|
||||||
public override void OnDoubleClick(Mobile from)
|
public override void OnDoubleClick(Mobile from)
|
||||||
{
|
{
|
||||||
var pm = from as PlayerMobile;
|
|
||||||
|
|
||||||
if (!IsChildOf(from.Backpack))
|
if (!IsChildOf(from.Backpack))
|
||||||
{
|
{
|
||||||
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||||
}
|
}
|
||||||
else if (pm == null || from.Skills.Alchemy.Base < 100.0)
|
else if (from is not PlayerMobile pm || from.Skills.Alchemy.Base < 100.0)
|
||||||
{
|
{
|
||||||
pm.SendMessage("Only a Grandmaster Alchemist can learn from this book.");
|
from.SendMessage("Only a Grandmaster Alchemist can learn from this book.");
|
||||||
}
|
}
|
||||||
else if (pm.Glassblowing)
|
else if (pm.Glassblowing)
|
||||||
{
|
{
|
||||||
pm.SendMessage("You have already learned this information.");
|
from.SendMessage("You have already learned this information.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pm.Glassblowing = true;
|
pm.Glassblowing = true;
|
||||||
pm.SendMessage(
|
from.SendMessage(
|
||||||
"You have learned to make items from glass. You will need to find miners to mine find sand for you to make these items."
|
"You have learned to make items from glass. You will need to find miners to mine find sand for you to make these items."
|
||||||
);
|
);
|
||||||
Delete();
|
Delete();
|
||||||
|
|
|
||||||
|
|
@ -29,24 +29,22 @@ namespace Server.Items
|
||||||
|
|
||||||
public override void OnDoubleClick(Mobile from)
|
public override void OnDoubleClick(Mobile from)
|
||||||
{
|
{
|
||||||
var pm = from as PlayerMobile;
|
|
||||||
|
|
||||||
if (!IsChildOf(from.Backpack))
|
if (!IsChildOf(from.Backpack))
|
||||||
{
|
{
|
||||||
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||||
}
|
}
|
||||||
else if (pm == null || from.Skills.Carpentry.Base < 100.0)
|
else if (from is not PlayerMobile pm || from.Skills.Carpentry.Base < 100.0)
|
||||||
{
|
{
|
||||||
pm.SendMessage("Only a Grandmaster Carpenter can learn from this book.");
|
from.SendMessage("Only a Grandmaster Carpenter can learn from this book.");
|
||||||
}
|
}
|
||||||
else if (pm.Masonry)
|
else if (pm.Masonry)
|
||||||
{
|
{
|
||||||
pm.SendMessage("You have already learned this information.");
|
from.SendMessage("You have already learned this information.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pm.Masonry = true;
|
pm.Masonry = true;
|
||||||
pm.SendMessage(
|
from.SendMessage(
|
||||||
"You have learned to make items from stone. You will need miners to gather stones for you to make these items."
|
"You have learned to make items from stone. You will need miners to gather stones for you to make these items."
|
||||||
);
|
);
|
||||||
Delete();
|
Delete();
|
||||||
|
|
|
||||||
|
|
@ -29,24 +29,22 @@ namespace Server.Items
|
||||||
|
|
||||||
public override void OnDoubleClick(Mobile from)
|
public override void OnDoubleClick(Mobile from)
|
||||||
{
|
{
|
||||||
var pm = from as PlayerMobile;
|
|
||||||
|
|
||||||
if (!IsChildOf(from.Backpack))
|
if (!IsChildOf(from.Backpack))
|
||||||
{
|
{
|
||||||
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||||
}
|
}
|
||||||
else if (pm == null || from.Skills.Mining.Base < 100.0)
|
else if (from is not PlayerMobile pm || from.Skills.Mining.Base < 100.0)
|
||||||
{
|
{
|
||||||
pm.SendMessage("Only a Grandmaster Miner can learn from this book.");
|
from.SendMessage("Only a Grandmaster Miner can learn from this book.");
|
||||||
}
|
}
|
||||||
else if (pm.SandMining)
|
else if (pm.SandMining)
|
||||||
{
|
{
|
||||||
pm.SendMessage("You have already learned this information.");
|
from.SendMessage("You have already learned this information.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pm.SandMining = true;
|
pm.SandMining = true;
|
||||||
pm.SendMessage(
|
from.SendMessage(
|
||||||
"You have learned how to mine fine sand. Target sand areas when mining to look for fine sand."
|
"You have learned how to mine fine sand. Target sand areas when mining to look for fine sand."
|
||||||
);
|
);
|
||||||
Delete();
|
Delete();
|
||||||
|
|
|
||||||
|
|
@ -156,10 +156,6 @@ namespace Server.Items
|
||||||
|
|
||||||
protected virtual bool CheckUse(Mobile from)
|
protected virtual bool CheckUse(Mobile from)
|
||||||
{
|
{
|
||||||
// DateTime now = DateTime.UtcNow;
|
|
||||||
|
|
||||||
var pm = from as PlayerMobile;
|
|
||||||
|
|
||||||
if (Deleted || !IsAccessibleTo(from))
|
if (Deleted || !IsAccessibleTo(from))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -235,11 +231,10 @@ namespace Server.Items
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pm.AcceleratedStart > DateTime.UtcNow)
|
if ((from as PlayerMobile)?.AcceleratedStart > DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
from.SendLocalizedMessage(
|
// You may not use a soulstone while your character is under the effects of a Scroll of Alacrity.
|
||||||
1078115
|
from.SendLocalizedMessage(1078115);
|
||||||
); // You may not use a soulstone while your character is under the effects of a Scroll of Alacrity.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -748,8 +743,7 @@ namespace Server.Items
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pm = from as PlayerMobile;
|
if ((from as PlayerMobile)?.AcceleratedStart > DateTime.UtcNow)
|
||||||
if (pm.AcceleratedStart > DateTime.UtcNow)
|
|
||||||
{
|
{
|
||||||
// <CENTER>Unable to Absorb Selected Skill from Soulstone</CENTER>
|
// <CENTER>Unable to Absorb Selected Skill from Soulstone</CENTER>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,12 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
foreach (var m in GetMobilesInRange(RangePerception))
|
foreach (var m in GetMobilesInRange(RangePerception))
|
||||||
{
|
{
|
||||||
var p = m as PlayerMobile;
|
if (m is PlayerMobile pm && IsValidTarget(pm))
|
||||||
|
|
||||||
if (IsValidTarget(p))
|
|
||||||
{
|
{
|
||||||
p.PeacedUntil = DateTime.UtcNow + duration;
|
pm.PeacedUntil = DateTime.UtcNow + duration;
|
||||||
p.SendLocalizedMessage(1072065); // You gaze upon the dryad's beauty, and forget to continue battling!
|
m.SendLocalizedMessage(1072065); // You gaze upon the dryad's beauty, and forget to continue battling!
|
||||||
p.FixedParticles(0x376A, 1, 20, 0x7F5, EffectLayer.Waist);
|
m.FixedParticles(0x376A, 1, 20, 0x7F5, EffectLayer.Waist);
|
||||||
p.Combatant = null;
|
m.Combatant = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<ProjectReference Include="..\Server\Server.csproj" Private="false" PrivateAssets="All" IncludeAssets="None">
|
<ProjectReference Include="..\Server\Server.csproj" Private="false" PrivateAssets="All" IncludeAssets="None">
|
||||||
<IncludeInPackage>false</IncludeInPackage>
|
<IncludeInPackage>false</IncludeInPackage>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<PackageReference Include="MailKit" Version="2.8.0" />
|
<PackageReference Include="MailKit" Version="2.10.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.0.0-preview4" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.0.0-preview4" />
|
||||||
<PackageReference Include="Zlib.Bindings" Version="1.4.0" />
|
<PackageReference Include="Zlib.Bindings" Version="1.4.0" />
|
||||||
<PackageReference Include="Argon2.Bindings" Version="1.8.0" />
|
<PackageReference Include="Argon2.Bindings" Version="1.8.0" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue