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:
Kamron Batman 2020-12-31 19:57:02 -08:00 committed by GitHub
parent 2a2af424ad
commit 582e1877b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 298 additions and 134 deletions

View file

@ -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>

View file

@ -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" />

View 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);
}
);
}
}
}

View file

@ -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
}; };
} }

View file

@ -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
}; };
} }

View file

@ -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
}; });
}
} }
} }

View file

@ -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 }
};
} }
} }

View file

@ -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" />

View file

@ -45,7 +45,7 @@ namespace Server.Guilds
return; return;
} }
var playerRank = pm.GuildRank; var playerRank = pm!.GuildRank;
switch (info.ButtonID) switch (info.ButtonID)
{ {

View file

@ -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)

View file

@ -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)

View file

@ -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);

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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>

View file

@ -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;
} }
} }

View file

@ -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" />