fix(houses): Converts house packets (1 of 2) (#502)

- [X] Adds `ToSpan()` for SpanWriter.
- [X] Moves house files to UOContent/Multis/Houses
- [X] Adds house packets

### SpanWriter.ToSpan()
`ToSpan()` returns an `SpanOwner` struct which allows access to the buffer as a Span by calling `Span`.

Example:
```cs
public SpanOwner CreatePacket()
{
  var writer = new SpanWriter(0x400);
  // write some stuff
  return writer.ToSpan();
}
```
In this example, we are creating a packet and returning the `SpanOwner` for caching. This means in the future we can do something like this:
```cs
ns.Send(_cachedPacket.Span);
```
Where `_cachedPacket` is the `SpanOwner`

Notes:
Do not use a SpanWriter, or attempt to dispose of it after calling `ToSpan()`. Doing so will probably cause an NPE.
This commit is contained in:
Kamron Batman 2021-02-13 09:18:12 -08:00 • committed by GitHub
parent 952a3e0265
commit d070efeab3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 267 additions and 73 deletions

View file

@ -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<byte> RawBuffer => _buffer;
/**
* Converts the writer to a Span<byte> 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<byte>());
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
}
else if (toReturn != null)
{
apo = new SpanOwner(_position, toReturn);
}
else
{
var buffer = ArrayPool<byte>.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<byte> 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<byte> 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<byte>.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<byte> 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<byte>.Shared.Return(toReturn);
}
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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);
}
}
}