diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs index 36666271e..8200c4179 100644 --- a/Projects/Server/Buffers/SpanWriter.cs +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -20,6 +20,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using Microsoft.Toolkit.HighPerformance.Extensions; using Server.Network; using Server.Text; @@ -54,6 +55,45 @@ namespace System.Buffers public Span RawBuffer => _buffer; + /** + * Converts the writer to a Span using a SpanOwner. + * If the buffer was stackalloc, it will be copied to a rented buffer. + * Otherwise the existing rented buffer is used. + * + * Note: + * Do not use the SpanWriter after calling this method. + * This method will effectively dispose of the SpanWriter and is therefore considered terminal. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SpanOwner ToSpan() + { + var toReturn = _arrayToReturnToPool; + + SpanOwner apo; + if (_position == 0) + { + apo = new SpanOwner(_position, Array.Empty()); + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + else if (toReturn != null) + { + apo = new SpanOwner(_position, toReturn); + } + else + { + var buffer = ArrayPool.Shared.Rent(_position); + _buffer.CopyTo(buffer); + apo = new SpanOwner(_position, buffer); + } + + this = default; // Don't allow two references to the same buffer + return apo; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public SpanWriter(Span initialBuffer, bool resize = false) { _resize = resize; @@ -63,6 +103,7 @@ namespace System.Buffers _arrayToReturnToPool = null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public SpanWriter(int initialCapacity, bool resize = false) { _resize = resize; @@ -218,6 +259,7 @@ namespace System.Buffers Position += 8; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ReadOnlySpan buffer) { var count = buffer.Length; @@ -331,6 +373,7 @@ namespace System.Buffers Debug.Assert( origin != SeekOrigin.End || offset >= -_buffer.Length, + "Attempting to seek to a negative position using SeekOrigin.End" ); @@ -379,5 +422,35 @@ namespace System.Buffers ArrayPool.Shared.Return(toReturn); } } + + public struct SpanOwner : IDisposable + { + private readonly int _length; + private readonly byte[] _arrayToReturnToPool; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal SpanOwner(int length, byte[] buffer) + { + _length = length; + _arrayToReturnToPool = buffer; + } + + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + byte[] toReturn = _arrayToReturnToPool; + this = default; + if (_length > 0) + { + ArrayPool.Shared.Return(toReturn); + } + } + } } } diff --git a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs new file mode 100644 index 000000000..ef2c7b080 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs @@ -0,0 +1,52 @@ +using System; +using Server.Multis; +using Server.Network; +using Server.Tests; +using Server.Tests.Network; +using Xunit; + +namespace UOContent.Tests +{ + public class HousePacketTests + { + [Theory] + [InlineData(0x1001u)] + public void TestBeginHouseCustomization(uint serial) + { + var expected = new BeginHouseCustomization(serial).Compile(); + + var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendBeginHouseCustomization(serial); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Theory] + [InlineData(0x1001u)] + public void TestEndHouseCustomization(uint serial) + { + var expected = new EndHouseCustomization(serial).Compile(); + + var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendEndHouseCustomization(serial); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Theory] + [InlineData(0x1001u, 0)] + [InlineData(0x1001u, 100)] + public void TestDesignStateGeneral(uint serial, int revision) + { + var expected = new DesignStateGeneral(serial, revision).Compile(); + + var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendDesignStateGeneral(serial, revision); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + } +} diff --git a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs new file mode 100644 index 000000000..66b4076d1 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePackets.cs @@ -0,0 +1,46 @@ +namespace Server.Network +{ + public class BeginHouseCustomization : Packet + { + public BeginHouseCustomization(Serial house) : base(0xBF) + { + EnsureCapacity(17); + + Stream.Write((short)0x20); + Stream.Write(house); + Stream.Write((byte)0x04); + Stream.Write((ushort)0x0000); + Stream.Write((ushort)0xFFFF); + Stream.Write((ushort)0xFFFF); + Stream.Write((byte)0xFF); + } + } + + public class EndHouseCustomization : Packet + { + public EndHouseCustomization(Serial house) : base(0xBF) + { + EnsureCapacity(17); + + Stream.Write((short)0x20); + Stream.Write(house); + Stream.Write((byte)0x05); + Stream.Write((ushort)0x0000); + Stream.Write((ushort)0xFFFF); + Stream.Write((ushort)0xFFFF); + Stream.Write((byte)0xFF); + } + } + + public sealed class DesignStateGeneral : Packet + { + public DesignStateGeneral(Serial house, int revision) : base(0xBF) + { + EnsureCapacity(13); + + Stream.Write((short)0x1D); + Stream.Write(house); + Stream.Write(revision); + } + } +} diff --git a/Projects/UOContent/Multis/BaseHouse.cs b/Projects/UOContent/Multis/Houses/BaseHouse.cs similarity index 100% rename from Projects/UOContent/Multis/BaseHouse.cs rename to Projects/UOContent/Multis/Houses/BaseHouse.cs diff --git a/Projects/UOContent/Multis/ContestHouses.cs b/Projects/UOContent/Multis/Houses/ContestHouses.cs similarity index 100% rename from Projects/UOContent/Multis/ContestHouses.cs rename to Projects/UOContent/Multis/Houses/ContestHouses.cs diff --git a/Projects/UOContent/Multis/HouseFoundation.cs b/Projects/UOContent/Multis/Houses/HouseFoundation.cs similarity index 97% rename from Projects/UOContent/Multis/HouseFoundation.cs rename to Projects/UOContent/Multis/Houses/HouseFoundation.cs index cd0582742..4da353d76 100644 --- a/Projects/UOContent/Multis/HouseFoundation.cs +++ b/Projects/UOContent/Multis/Houses/HouseFoundation.cs @@ -869,7 +869,7 @@ namespace Server.Multis } DesignContext.Add(m, this); - m.Send(new BeginHouseCustomization(this)); + m.NetState.SendBeginHouseCustomization(Serial); var ns = m.NetState; if (ns != null) @@ -1237,7 +1237,7 @@ namespace Server.Multis DesignContext.Remove(from); // Notify the client that customization has ended - from.Send(new EndHouseCustomization(this)); + from.NetState.SendEndHouseCustomization(Serial); // Notify the core that the foundation has changed and should be resent to all clients Delta(ItemDelta.Update); @@ -1278,7 +1278,7 @@ namespace Server.Multis context.Foundation.Fixtures.Count)) * 500; var bankBalance = Banker.GetBalance(from); - from.SendGump(new ConfirmCommitGump(from, context.Foundation, bankBalance, oldPrice, newPrice)); + from.SendGump(new ConfirmCommitGump(context.Foundation, bankBalance, oldPrice, newPrice)); } } @@ -1704,7 +1704,7 @@ namespace Server.Multis DesignContext.Remove(from); // Notify the client that customization has ended - from.Send(new EndHouseCustomization(context.Foundation)); + from.NetState.SendEndHouseCustomization(context.Foundation.Serial); // Refresh client with current visible design state context.Foundation.SendInfoTo(state); @@ -1981,7 +1981,7 @@ namespace Server.Multis public void SendGeneralInfoTo(NetState state) { - state.Send(new DesignStateGeneral(Foundation, this)); + state.SendDesignStateGeneral(Foundation.Serial, Revision); } public void SendDetailedInfoTo(NetState state) @@ -2194,8 +2194,7 @@ namespace Server.Multis { private readonly HouseFoundation m_Foundation; - public ConfirmCommitGump(Mobile from, HouseFoundation foundation, int bankBalance, int oldPrice, int newPrice) - : base(50, 50) + public ConfirmCommitGump(HouseFoundation foundation, int bankBalance, int oldPrice, int newPrice) : base(50, 50) { m_Foundation = foundation; @@ -2383,53 +2382,6 @@ namespace Server.Multis } } - public class BeginHouseCustomization : Packet - { - public BeginHouseCustomization(HouseFoundation house) - : base(0xBF) - { - EnsureCapacity(17); - - Stream.Write((short)0x20); - Stream.Write(house.Serial); - Stream.Write((byte)0x04); - Stream.Write((ushort)0x0000); - Stream.Write((ushort)0xFFFF); - Stream.Write((ushort)0xFFFF); - Stream.Write((byte)0xFF); - } - } - - public class EndHouseCustomization : Packet - { - public EndHouseCustomization(HouseFoundation house) - : base(0xBF) - { - EnsureCapacity(17); - - Stream.Write((short)0x20); - Stream.Write(house.Serial); - Stream.Write((byte)0x05); - Stream.Write((ushort)0x0000); - Stream.Write((ushort)0xFFFF); - Stream.Write((ushort)0xFFFF); - Stream.Write((byte)0xFF); - } - } - - public sealed class DesignStateGeneral : Packet - { - public DesignStateGeneral(HouseFoundation house, DesignState state) - : base(0xBF) - { - EnsureCapacity(13); - - Stream.Write((short)0x1D); - Stream.Write(house.Serial); - Stream.Write(state.Revision); - } - } - public sealed class DesignStateDetailed : Packet { public const int MaxItemsPerStairBuffer = 750; @@ -2581,20 +2533,12 @@ namespace Server.Multis ++planeCount; - int size; - - if (i == 0) + int size = i switch { - size = width * height * 2; - } - else if (i < 5) - { - size = (width - 1) * (height - 2) * 2; - } - else - { - size = width * (height - 1) * 2; - } + 0 => width * height * 2, + < 5 => (width - 1) * (height - 2) * 2, + _ => width * (height - 1) * 2 + }; var inflatedBuffer = planeBuffers[i]; @@ -2630,12 +2574,7 @@ namespace Server.Multis { ++planeCount; - var count = totalStairsUsed - i * MaxItemsPerStairBuffer; - - if (count > MaxItemsPerStairBuffer) - { - count = MaxItemsPerStairBuffer; - } + var count = Math.Min(MaxItemsPerStairBuffer, totalStairsUsed - i * MaxItemsPerStairBuffer); var size = count * 5; diff --git a/Projects/UOContent/Multis/Houses/HousePackets.cs b/Projects/UOContent/Multis/Houses/HousePackets.cs new file mode 100644 index 000000000..0b7bcf17d --- /dev/null +++ b/Projects/UOContent/Multis/Houses/HousePackets.cs @@ -0,0 +1,84 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: HousePackets.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Buffers; +using Server.Network; + +namespace Server.Multis +{ + public static class HousePackets + { + private const int MaxItemsPerStairBuffer = 750; + + public static void SendBeginHouseCustomization(this NetState ns, Serial house) + { + if (ns == null) + { + return; + } + + var writer = new SpanWriter(stackalloc byte[17]); + writer.Write((byte)0xBF); // Packet Id + writer.Write((ushort)17); + writer.Write((short)0x20); // Sub-packet + writer.Write(house); + writer.Write((byte)0x04); // command + writer.Write((ushort)0x0000); + writer.Write((ushort)0xFFFF); + writer.Write((ushort)0xFFFF); + writer.Write((byte)0xFF); + + ns.Send(writer.Span); + } + + public static void SendEndHouseCustomization(this NetState ns, Serial house) + { + if (ns == null) + { + return; + } + + var writer = new SpanWriter(stackalloc byte[17]); + writer.Write((byte)0xBF); // Packet Id + writer.Write((ushort)17); + writer.Write((short)0x20); // Sub-packet + writer.Write(house); + writer.Write((byte)0x05); // command + writer.Write((ushort)0x0000); + writer.Write((ushort)0xFFFF); + writer.Write((ushort)0xFFFF); + writer.Write((byte)0xFF); + + ns.Send(writer.Span); + } + + public static void SendDesignStateGeneral(this NetState ns, Serial house, int revision) + { + if (ns == null) + { + return; + } + + var writer = new SpanWriter(stackalloc byte[13]); + writer.Write((byte)0xBF); // Packet Id + writer.Write((ushort)13); + writer.Write((short)0x1D); // Sub-packet + writer.Write(house); + writer.Write(revision); + + ns.Send(writer.Span); + } + } +} diff --git a/Projects/UOContent/Multis/HousePlacement.cs b/Projects/UOContent/Multis/Houses/HousePlacement.cs similarity index 100% rename from Projects/UOContent/Multis/HousePlacement.cs rename to Projects/UOContent/Multis/Houses/HousePlacement.cs diff --git a/Projects/UOContent/Multis/HousePlacementTool.cs b/Projects/UOContent/Multis/Houses/HousePlacementTool.cs similarity index 100% rename from Projects/UOContent/Multis/HousePlacementTool.cs rename to Projects/UOContent/Multis/Houses/HousePlacementTool.cs diff --git a/Projects/UOContent/Multis/HouseSign.cs b/Projects/UOContent/Multis/Houses/HouseSign.cs similarity index 100% rename from Projects/UOContent/Multis/HouseSign.cs rename to Projects/UOContent/Multis/Houses/HouseSign.cs diff --git a/Projects/UOContent/Multis/HouseTeleporter.cs b/Projects/UOContent/Multis/Houses/HouseTeleporter.cs similarity index 100% rename from Projects/UOContent/Multis/HouseTeleporter.cs rename to Projects/UOContent/Multis/Houses/HouseTeleporter.cs diff --git a/Projects/UOContent/Multis/Houses.cs b/Projects/UOContent/Multis/Houses/Houses.cs similarity index 100% rename from Projects/UOContent/Multis/Houses.cs rename to Projects/UOContent/Multis/Houses/Houses.cs diff --git a/Projects/UOContent/Multis/MovingCrate.cs b/Projects/UOContent/Multis/Houses/MovingCrate.cs similarity index 100% rename from Projects/UOContent/Multis/MovingCrate.cs rename to Projects/UOContent/Multis/Houses/MovingCrate.cs diff --git a/Projects/UOContent/Multis/PreviewHouse.cs b/Projects/UOContent/Multis/Houses/PreviewHouse.cs similarity index 100% rename from Projects/UOContent/Multis/PreviewHouse.cs rename to Projects/UOContent/Multis/Houses/PreviewHouse.cs