fix: Fixes sending packets and sidesteps a major issue with stackalloc and PGO in .NET 8 (#1607)
### Summary - Works around a sneaky edge case bug in the JIT with stackalloc where sometimes the buffer is not zero'd. - Fixes SendDisplayBoatHS - Fixes sending health bars in the `SendEverything()` logic. - Fixes a bug in sizing for some string helper functions. ### Developer Note We are enabled `SkipLocalsInit` - do not rely on `stackalloc` to be zero'd. To zero the buffer, use `span.Clear();` Closes #1606
This commit is contained in:
parent
79ba1ea917
commit
03f850fe03
18 changed files with 67 additions and 76 deletions
|
|
@ -27,7 +27,6 @@
|
|||
<DefineConstants Condition="'$(IsOSX)'=='true' OR '$(IsLinux)'=='true'">UNIX</DefineConstants>
|
||||
<DefineConstants Condition="'$(IsX64)'=='true'">CPU_X64</DefineConstants>
|
||||
<DefineConstants Condition="'$(IsArm64)'=='true'">CPU_ARM64</DefineConstants>
|
||||
<DefineConstants Condition="'$(SkipLocalsInitAttribute)'=='true'">NO_LOCAL_INIT</DefineConstants>
|
||||
<DefineConstants>MUO</DefineConstants>
|
||||
<GitVersionBaseDirectory>$(SolutionDir)</GitVersionBaseDirectory>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ namespace Server.Json;
|
|||
|
||||
public class Point2DConverter : JsonConverter<Point2D>
|
||||
{
|
||||
private Point2D DeserializeArray(ref Utf8JsonReader reader)
|
||||
private static Point2D DeserializeArray(ref Utf8JsonReader reader)
|
||||
{
|
||||
Span<int> data = stackalloc int[2];
|
||||
data.Clear();
|
||||
var count = 0;
|
||||
|
||||
while (true)
|
||||
|
|
@ -53,9 +54,10 @@ public class Point2DConverter : JsonConverter<Point2D>
|
|||
return new Point2D(data[0], data[1]);
|
||||
}
|
||||
|
||||
private Point2D DeserializeObj(ref Utf8JsonReader reader)
|
||||
private static Point2D DeserializeObj(ref Utf8JsonReader reader)
|
||||
{
|
||||
Span<int> data = stackalloc int[2];
|
||||
data.Clear();
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@ namespace Server.Json;
|
|||
|
||||
public class Point3DConverter : JsonConverter<Point3D>
|
||||
{
|
||||
private Point3D DeserializeArray(ref Utf8JsonReader reader)
|
||||
private static Point3D DeserializeArray(ref Utf8JsonReader reader)
|
||||
{
|
||||
Span<int> data = stackalloc int[3];
|
||||
data.Clear();
|
||||
|
||||
var count = 0;
|
||||
|
||||
while (true)
|
||||
|
|
@ -53,9 +55,10 @@ public class Point3DConverter : JsonConverter<Point3D>
|
|||
return new Point3D(data[0], data[1], data[2]);
|
||||
}
|
||||
|
||||
private Point3D DeserializeObj(ref Utf8JsonReader reader)
|
||||
private static Point3D DeserializeObj(ref Utf8JsonReader reader)
|
||||
{
|
||||
Span<int> data = stackalloc int[3];
|
||||
data.Clear();
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@ namespace Server.Json;
|
|||
|
||||
public class Rectangle3DConverter : JsonConverter<Rectangle3D>
|
||||
{
|
||||
private Rectangle3D DeserializeArray(ref Utf8JsonReader reader)
|
||||
private static Rectangle3D DeserializeArray(ref Utf8JsonReader reader)
|
||||
{
|
||||
Span<int> data = stackalloc int[6];
|
||||
data.Clear();
|
||||
|
||||
var count = 0;
|
||||
|
||||
while (true)
|
||||
|
|
@ -53,9 +55,10 @@ public class Rectangle3DConverter : JsonConverter<Rectangle3D>
|
|||
return new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]);
|
||||
}
|
||||
|
||||
private Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
private static Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
Span<int> data = stackalloc int[6];
|
||||
data.Clear();
|
||||
|
||||
// 0 - xyzwhd, 1 - x1y1z1x2y2z2, 2 - start/end
|
||||
var objType = -1;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@ public class WorldLocationConverter : JsonConverter<WorldLocation>
|
|||
private static Point3DConverter _point3DConverter;
|
||||
private static MapConverter _mapConverter;
|
||||
|
||||
private WorldLocation DeserializeArray(ref Utf8JsonReader reader)
|
||||
private static WorldLocation DeserializeArray(ref Utf8JsonReader reader)
|
||||
{
|
||||
Span<int> data = stackalloc int[3];
|
||||
data.Clear();
|
||||
|
||||
var count = 0;
|
||||
var hasMap = false;
|
||||
Map map = null;
|
||||
|
|
@ -77,9 +79,11 @@ public class WorldLocationConverter : JsonConverter<WorldLocation>
|
|||
return new WorldLocation(data[0], data[1], data[2], map);
|
||||
}
|
||||
|
||||
private WorldLocation DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
private static WorldLocation DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
Span<int> data = stackalloc int[3];
|
||||
data.Clear();
|
||||
|
||||
var hasLoc = false;
|
||||
var hasXYZ = false;
|
||||
var hasMap = false;
|
||||
|
|
|
|||
|
|
@ -2721,9 +2721,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
Span<byte> facialHairPacket = stackalloc byte[facialHairLength].InitializePacket();
|
||||
|
||||
const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength;
|
||||
const int width = OutgoingMobilePackets.MobileMovingPacketLength;
|
||||
|
||||
var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width);
|
||||
Span<byte> mobileMovingCache = stackalloc byte[cacheLength];
|
||||
mobileMovingCache.Clear();
|
||||
|
||||
var ourState = m_NetState;
|
||||
|
||||
|
|
@ -4369,9 +4369,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
if (moveClientQueue.Count > 0)
|
||||
{
|
||||
const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength;
|
||||
const int width = OutgoingMobilePackets.MobileMovingPacketLength;
|
||||
|
||||
var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width);
|
||||
Span<byte> mobileMovingCache = stackalloc byte[cacheLength];
|
||||
mobileMovingCache.Clear();
|
||||
|
||||
while (moveClientQueue.Count > 0)
|
||||
{
|
||||
|
|
@ -6923,8 +6923,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
|
||||
if (ns.StygianAbyss)
|
||||
{
|
||||
ns.SendMobileHealthbar(m, Healthbar.Poison);
|
||||
ns.SendMobileHealthbar(m, Healthbar.Yellow);
|
||||
if (m.Blessed || m.YellowHealthbar)
|
||||
{
|
||||
ns.SendMobileHealthbar(m, Healthbar.Yellow);
|
||||
}
|
||||
else if (m.Poisoned)
|
||||
{
|
||||
ns.SendMobileHealthbar(m, Healthbar.Poison);
|
||||
}
|
||||
}
|
||||
|
||||
if (m.IsDeadBondedPet)
|
||||
|
|
|
|||
4
Projects/Server/Module.cs
Normal file
4
Projects/Server/Module.cs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Skips initializing stackalloc with zeros.
|
||||
[module: SkipLocalsInit]
|
||||
|
|
@ -35,24 +35,7 @@ public static class PacketUtilities
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<byte> InitializePacket(this Span<byte> buffer)
|
||||
{
|
||||
#if NO_LOCAL_INIT
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer[0] = 0;
|
||||
}
|
||||
#endif
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<byte> InitializePackets(this Span<byte> buffer, int width)
|
||||
{
|
||||
#if NO_LOCAL_INIT
|
||||
for (var i = 0; i < buffer.Length; i += width)
|
||||
{
|
||||
buffer[i] = 0;
|
||||
}
|
||||
#endif
|
||||
buffer[0] = 0;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Server.Items;
|
||||
using Server.Text;
|
||||
|
||||
namespace Server.Network;
|
||||
|
||||
|
|
@ -25,6 +28,7 @@ public static class OutgoingEntityPackets
|
|||
public const int RemoveEntityLength = 5;
|
||||
public const int MaxWorldEntityPacketLength = 26;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CreateOPLInfo(Span<byte> buffer, Item item) =>
|
||||
CreateOPLInfo(buffer, item.Serial, item.PropertyList.Hash);
|
||||
|
||||
|
|
@ -41,6 +45,7 @@ public static class OutgoingEntityPackets
|
|||
writer.Write(hash);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SendOPLInfo(this NetState ns, IObjectPropertyListEntity obj) =>
|
||||
ns.SendOPLInfo(obj.Serial, obj.PropertyList.Hash);
|
||||
|
||||
|
|
|
|||
|
|
@ -604,9 +604,7 @@ public static class OutgoingMobilePackets
|
|||
}
|
||||
|
||||
Span<bool> layers = stackalloc bool[256];
|
||||
#if NO_LOCAL_INIT
|
||||
layers.Clear();
|
||||
#endif
|
||||
layers.Clear();
|
||||
|
||||
var eq = beheld.Items;
|
||||
var maxLength = 23 + (eq.Count + 2) * 9;
|
||||
|
|
|
|||
|
|
@ -82,27 +82,14 @@ public static class StringHelpers
|
|||
return "";
|
||||
}
|
||||
|
||||
Span<char> span = a.Length < 1024 ? stackalloc char[a.Length] : null;
|
||||
char[] chrs;
|
||||
if (span == null)
|
||||
{
|
||||
chrs = STArrayPool<char>.Shared.Rent(a.Length);
|
||||
span = chrs.AsSpan();
|
||||
}
|
||||
else
|
||||
{
|
||||
chrs = null;
|
||||
}
|
||||
char[] chrs = STArrayPool<char>.Shared.Rent(a.Length);
|
||||
var span = chrs.AsSpan(0, a.Length);
|
||||
|
||||
a.Remove(b, comparison, span, out var size);
|
||||
|
||||
var str = span[..size].ToString();
|
||||
|
||||
if (chrs != null)
|
||||
{
|
||||
STArrayPool<char>.Shared.Return(chrs);
|
||||
}
|
||||
|
||||
STArrayPool<char>.Shared.Return(chrs);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
|
@ -114,17 +101,8 @@ public static class StringHelpers
|
|||
return value;
|
||||
}
|
||||
|
||||
Span<char> span = value.Length < 1024 ? stackalloc char[value.Length] : null;
|
||||
char[] chrs;
|
||||
if (span == null)
|
||||
{
|
||||
chrs = STArrayPool<char>.Shared.Rent(value.Length);
|
||||
span = chrs.AsSpan();
|
||||
}
|
||||
else
|
||||
{
|
||||
chrs = null;
|
||||
}
|
||||
char[] chrs = STArrayPool<char>.Shared.Rent(value.Length);
|
||||
var span = chrs.AsSpan(0, value.Length);
|
||||
|
||||
var sliced = value.AsSpan();
|
||||
// Copy over the previous span
|
||||
|
|
@ -161,11 +139,7 @@ public static class StringHelpers
|
|||
|
||||
var str = span.ToString();
|
||||
|
||||
if (chrs != null)
|
||||
{
|
||||
STArrayPool<char>.Shared.Return(chrs);
|
||||
}
|
||||
|
||||
STArrayPool<char>.Shared.Return(chrs);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1001,6 +1001,8 @@ public static class Utility
|
|||
|
||||
var length = source.Length;
|
||||
Span<bool> list = stackalloc bool[length];
|
||||
list.Clear();
|
||||
|
||||
var sampleList = new T[count];
|
||||
|
||||
var i = 0;
|
||||
|
|
@ -1025,6 +1027,8 @@ public static class Utility
|
|||
|
||||
var length = source.Count;
|
||||
Span<bool> list = stackalloc bool[length];
|
||||
list.Clear();
|
||||
|
||||
var sampleList = new List<T>(count);
|
||||
|
||||
var i = 0;
|
||||
|
|
@ -1049,6 +1053,7 @@ public static class Utility
|
|||
|
||||
var length = source.Length;
|
||||
Span<bool> list = stackalloc bool[length];
|
||||
list.Clear();
|
||||
|
||||
var i = 0;
|
||||
do
|
||||
|
|
|
|||
|
|
@ -140,15 +140,14 @@ namespace Server.Engines.PartySystem
|
|||
return;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[10];
|
||||
var writer = new SpanWriter(buffer);
|
||||
var writer = new SpanWriter(stackalloc byte[10]);
|
||||
writer.Write((byte)0xBF); // Packet ID
|
||||
writer.Write((ushort)10);
|
||||
writer.Write((ushort)0x06); // Sub-packet
|
||||
writer.Write((byte)0x07); // command
|
||||
writer.Write(leader);
|
||||
|
||||
ns.Send(buffer);
|
||||
ns.Send(writer.Span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ public class ImageTypeInfo
|
|||
|
||||
var length = m_Table.Length;
|
||||
Span<bool> list = stackalloc bool[length];
|
||||
list.Clear();
|
||||
|
||||
var imageTypes = new ImageType[count];
|
||||
|
||||
var i = 0;
|
||||
|
|
@ -100,4 +102,4 @@ public class ImageTypeInfo
|
|||
|
||||
return imageTypes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ namespace Server.Network
|
|||
|
||||
Span<byte> textBuffer = stackalloc byte[TextEncoding.UTF8.GetMaxByteCount(longestTextLine)];
|
||||
|
||||
var writer = maxLength > 81920 ? new SpanWriter(maxLength) : new SpanWriter(stackalloc byte[maxLength]);
|
||||
var writer = maxLength > 1024 ? new SpanWriter(maxLength) : new SpanWriter(stackalloc byte[maxLength]);
|
||||
writer.Write((byte)0x71); // Packet ID
|
||||
writer.Seek(2, SeekOrigin.Current);
|
||||
writer.Write((byte)(content ? 0x02 : 0x01)); // Command
|
||||
|
|
|
|||
4
Projects/UOContent/Module.cs
Normal file
4
Projects/UOContent/Module.cs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Skips initializing stackalloc with zeros.
|
||||
[module: SkipLocalsInit]
|
||||
|
|
@ -106,8 +106,6 @@ public static class BoatPackets
|
|||
|
||||
using var builder = new PacketContainerBuilder(stackalloc byte[minLength]);
|
||||
|
||||
Span<byte> buffer = builder.GetSpan(OutgoingEntityPackets.MaxWorldEntityPacketLength);
|
||||
|
||||
foreach (var entity in boat.GetMovingEntities(true))
|
||||
{
|
||||
if (!beholder.CanSee(entity))
|
||||
|
|
@ -115,7 +113,7 @@ public static class BoatPackets
|
|||
continue;
|
||||
}
|
||||
|
||||
buffer.InitializePacket();
|
||||
Span<byte> buffer = builder.GetSpan(OutgoingEntityPackets.MaxWorldEntityPacketLength).InitializePacket();
|
||||
var bytesWritten = OutgoingEntityPackets.CreateWorldEntity(buffer, entity, true);
|
||||
builder.Advance(bytesWritten);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ namespace Server.Multis
|
|||
using var planesWriter = new SpanWriter(planeLength * planeCount);
|
||||
|
||||
Span<bool> planesUsed = stackalloc bool[9];
|
||||
planesUsed.Clear();
|
||||
|
||||
int index;
|
||||
var totalPlaneOffsets = 0;
|
||||
var totalPlanes = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue