feat: Replaces Zlib with LibDeflate (#1774)
> [!Warning] > Users on Linux/OSX will need to follow the Readme > and make sure `libdeflate` is properly installed > [!Note] > **Developer Note** > The API for compression has changed. Use `Deflate.Standard` for the same functionality. ### Summary * Replaces Zlib with LibDeflate for a 50% performance improvement! * Adds MacOS 14 to properly test Arm64
This commit is contained in:
parent
35a292d2c7
commit
1f701e7b55
11 changed files with 279 additions and 282 deletions
14
.github/workflows/build-test.yml
vendored
14
.github/workflows/build-test.yml
vendored
|
|
@ -14,10 +14,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: macos-12
|
|
||||||
name: MacOS 12
|
|
||||||
- os: macos-13
|
- os: macos-13
|
||||||
name: MacOS 13
|
name: MacOS 13
|
||||||
|
- os: macos-14
|
||||||
|
name: MacOS 14
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -27,6 +27,12 @@ jobs:
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 8.0.x
|
||||||
|
- name: Install Prerequisites
|
||||||
|
run: |
|
||||||
|
brew update
|
||||||
|
brew install icu4c libdeflate zstd argon2
|
||||||
|
- name: Set Library Path
|
||||||
|
run: echo "DYLD_LIBRARY_PATH=/opt/homebrew/lib:\$DYLD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./publish.cmd Release
|
run: ./publish.cmd Release
|
||||||
- name: Migration Changes
|
- name: Migration Changes
|
||||||
|
|
@ -75,10 +81,10 @@ jobs:
|
||||||
run: dnf upgrade --refresh -y && dnf install -y epel-release epel-next-release
|
run: dnf upgrade --refresh -y && dnf install -y epel-release epel-next-release
|
||||||
if: ${{ startsWith(matrix.name, 'CentOS') }}
|
if: ${{ startsWith(matrix.name, 'CentOS') }}
|
||||||
- name: Install Prerequisites using dnf
|
- name: Install Prerequisites using dnf
|
||||||
run: dnf makecache --refresh && dnf install -y findutils libicu zlib-devel zstd libargon2-devel tzdata
|
run: dnf makecache --refresh && dnf install -y findutils libicu libdeflate-devel zstd libargon2-devel
|
||||||
if: ${{ matrix.packageManager == 'dnf' }}
|
if: ${{ matrix.packageManager == 'dnf' }}
|
||||||
- name: Install Prerequisites using apt
|
- name: Install Prerequisites using apt
|
||||||
run: apt-get update -y && apt-get install -y curl libicu-dev libz-dev zstd libargon2-dev tzdata
|
run: apt-get update -y && apt-get install -y curl libicu-dev libdeflate-dev zstd libargon2-dev
|
||||||
if: ${{ matrix.packageManager == 'apt' }}
|
if: ${{ matrix.packageManager == 'apt' }}
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Server.Compression;
|
||||||
using Server.Gumps;
|
using Server.Gumps;
|
||||||
using Server.Network;
|
using Server.Network;
|
||||||
|
|
||||||
|
|
@ -165,14 +166,11 @@ namespace Server.Tests
|
||||||
wantLength &= ~4095;
|
wantLength &= ~4095;
|
||||||
|
|
||||||
var packBuffer = ArrayPool<byte>.Shared.Rent(wantLength);
|
var packBuffer = ArrayPool<byte>.Shared.Rent(wantLength);
|
||||||
|
var bytesPacked = Deflate.Standard.Pack(packBuffer, buffer.AsSpan(0, length));
|
||||||
|
|
||||||
var packLength = wantLength;
|
Stream.Write(4 + bytesPacked);
|
||||||
|
|
||||||
Zlib.Pack(packBuffer, ref packLength, buffer, length, ZlibQuality.Default);
|
|
||||||
|
|
||||||
Stream.Write(4 + packLength);
|
|
||||||
Stream.Write(length);
|
Stream.Write(length);
|
||||||
Stream.Write(packBuffer, 0, packLength);
|
Stream.Write(packBuffer, 0, bytesPacked);
|
||||||
|
|
||||||
ArrayPool<byte>.Shared.Return(packBuffer);
|
ArrayPool<byte>.Shared.Return(packBuffer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
Projects/Server/Compression/Deflate.cs
Normal file
23
Projects/Server/Compression/Deflate.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*************************************************************************
|
||||||
|
* ModernUO *
|
||||||
|
* Copyright 2019-2024 - ModernUO Development Team *
|
||||||
|
* Email: hi@modernuo.com *
|
||||||
|
* File: Deflate.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.IO.Compression;
|
||||||
|
|
||||||
|
namespace Server.Compression;
|
||||||
|
|
||||||
|
public static class Deflate
|
||||||
|
{
|
||||||
|
public static LibDeflateBinding Standard { get; } = new();
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using Server.Buffers;
|
using Server.Buffers;
|
||||||
|
using Server.Compression;
|
||||||
|
|
||||||
namespace Server;
|
namespace Server;
|
||||||
|
|
||||||
|
|
@ -72,8 +73,8 @@ public static class MultiData
|
||||||
}
|
}
|
||||||
|
|
||||||
var decompressedSize = entry.Size;
|
var decompressedSize = entry.Size;
|
||||||
if (Zlib.Unpack(compressionBuffer, ref decompressedSize, buffer, entry.CompressedSize) != ZlibError.Okay
|
if (Deflate.Standard.Unpack(compressionBuffer, buffer, out var bytesDecompressed) != LibDeflateResult.Success
|
||||||
|| decompressedSize != entry.Size)
|
|| decompressedSize != bytesDecompressed)
|
||||||
{
|
{
|
||||||
throw new FileLoadException($"Error loading file {stream.Name}. Failed to unpack entry {i}.");
|
throw new FileLoadException($"Error loading file {stream.Name}. Failed to unpack entry {i}.");
|
||||||
}
|
}
|
||||||
|
|
@ -118,11 +119,7 @@ public static class MultiData
|
||||||
}
|
}
|
||||||
|
|
||||||
STArrayPool<byte>.Shared.Return(buffer);
|
STArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
STArrayPool<byte>.Shared.Return(compressionBuffer);
|
||||||
if (compressionBuffer != null)
|
|
||||||
{
|
|
||||||
STArrayPool<byte>.Shared.Return(compressionBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LoadMul(bool postHSMulFormat)
|
private static void LoadMul(bool postHSMulFormat)
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Server.Buffers;
|
|
||||||
using Server.Collections;
|
using Server.Collections;
|
||||||
|
using Server.Compression;
|
||||||
using Server.Gumps;
|
using Server.Gumps;
|
||||||
using Server.Logging;
|
using Server.Logging;
|
||||||
|
|
||||||
|
|
@ -49,7 +48,6 @@ public static class OutgoingGumpPackets
|
||||||
|
|
||||||
private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
|
private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
|
||||||
private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
|
private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
|
||||||
private static readonly byte[] _packBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
|
|
||||||
private static readonly OrderedSet<string> _stringsList = new();
|
private static readonly OrderedSet<string> _stringsList = new();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -63,36 +61,21 @@ public static class OutgoingGumpPackets
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var wantLength = Zlib.MaxPackSize(length);
|
var dest = writer.RawBuffer[(writer.Position + 8)..];
|
||||||
var packBuffer = _packBuffer;
|
|
||||||
byte[] rentedBuffer = null;
|
|
||||||
|
|
||||||
if (wantLength > packBuffer.Length)
|
var bytesPacked = Deflate.Standard.Pack(dest, span);
|
||||||
|
if (bytesPacked == 0)
|
||||||
{
|
{
|
||||||
packBuffer = rentedBuffer = STArrayPool<byte>.Shared.Rent(wantLength);
|
logger.Warning("Gump compression failed");
|
||||||
}
|
|
||||||
|
|
||||||
var packLength = wantLength;
|
|
||||||
|
|
||||||
var error = Zlib.Pack(packBuffer, ref packLength, span, length, ZlibQuality.Default);
|
|
||||||
|
|
||||||
if (error != ZlibError.Okay)
|
|
||||||
{
|
|
||||||
logger.Warning("Gump compression failed: {Error}", error);
|
|
||||||
|
|
||||||
writer.Write(4);
|
writer.Write(4);
|
||||||
writer.Write(0);
|
writer.Write(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Write(4 + packLength);
|
writer.Write(4 + bytesPacked);
|
||||||
writer.Write(length);
|
writer.Write(length);
|
||||||
writer.Write(packBuffer.AsSpan(0, packLength));
|
writer.Seek(bytesPacked, SeekOrigin.Current);
|
||||||
|
|
||||||
if (rentedBuffer != null)
|
|
||||||
{
|
|
||||||
STArrayPool<byte>.Shared.Return(rentedBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendDisplayGump(this NetState ns, Gump gump, out int switches, out int entries)
|
public static void SendDisplayGump(this NetState ns, Gump gump, out int switches, out int entries)
|
||||||
|
|
@ -141,11 +124,8 @@ public static class OutgoingGumpPackets
|
||||||
stringsWriter.WriteBigUni(s);
|
stringsWriter.WriteBigUni(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
var worstLayoutLength = Zlib.MaxPackSize(layoutWriter.BytesWritten);
|
|
||||||
var worstStringsLength = Zlib.MaxPackSize(stringsWriter.BytesWritten);
|
|
||||||
var maxLength = 40 + worstLayoutLength + worstStringsLength;
|
|
||||||
|
|
||||||
var writer = new SpanWriter(maxLength);
|
var writer = new SpanWriter(0x10000);
|
||||||
writer.Write((byte)0xDD); // Packet ID
|
writer.Write((byte)0xDD); // Packet ID
|
||||||
writer.Seek(2, SeekOrigin.Current);
|
writer.Seek(2, SeekOrigin.Current);
|
||||||
|
|
||||||
|
|
@ -171,6 +151,8 @@ public static class OutgoingGumpPackets
|
||||||
{
|
{
|
||||||
_stringsList.Clear();
|
_stringsList.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendDisplaySignGump(this NetState ns, Serial serial, int gumpId, string unknown, string caption)
|
public static void SendDisplaySignGump(this NetState ns, Serial serial, int gumpId, string unknown, string caption)
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,15 @@
|
||||||
<Delete Files="..\..\Distribution\Serilog.Sinks.Async.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Serilog.Sinks.Async.dll" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\Serilog.Sinks.Console.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Serilog.Sinks.Console.dll" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\wepoll.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\wepoll.dll" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\ZLib.Bindings.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\LibDeflate.Bindings.dll" ContinueOnError="true" />
|
||||||
|
<Delete Files="..\..\Distribution\libdeflate.dll" ContinueOnError="true" />
|
||||||
|
<Delete Files="..\..\Distribution\libdeflate.dylib" ContinueOnError="true" />
|
||||||
</Target>
|
</Target>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
|
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
|
||||||
|
<PackageReference Include="LibDeflate.Bindings" Version="1.0.0.120" />
|
||||||
<PackageReference Include="PollGroup" Version="1.4.3" />
|
<PackageReference Include="PollGroup" Version="1.4.3" />
|
||||||
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
|
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
|
||||||
<PackageReference Include="Zlib.Bindings" Version="1.11.0" />
|
|
||||||
|
|
||||||
<PackageReference Include="ModernUO.Serialization.Annotations" Version="2.9.1" />
|
<PackageReference Include="ModernUO.Serialization.Annotations" Version="2.9.1" />
|
||||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="2.10.9" />
|
<PackageReference Include="ModernUO.Serialization.Generator" Version="2.10.9" />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using Server.Compression;
|
||||||
|
|
||||||
namespace Server.Network
|
namespace Server.Network
|
||||||
{
|
{
|
||||||
|
|
@ -209,20 +210,14 @@ namespace Server.Network
|
||||||
|
|
||||||
var inflatedBuffer = planeBuffers[i];
|
var inflatedBuffer = planeBuffers[i];
|
||||||
|
|
||||||
var deflatedLength = deflatedBuffer.Length;
|
var deflatedLength = Deflate.Standard.Pack(
|
||||||
var ce = Zlib.Pack(
|
|
||||||
deflatedBuffer,
|
deflatedBuffer,
|
||||||
ref deflatedLength,
|
inflatedBuffer.AsSpan(0, size)
|
||||||
inflatedBuffer,
|
|
||||||
size,
|
|
||||||
ZlibQuality.Default
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ce != ZlibError.Okay)
|
if (deflatedLength == 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce);
|
Console.WriteLine("Compression error");
|
||||||
deflatedLength = 0;
|
|
||||||
size = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write((byte)(0x20 | i));
|
Write((byte)(0x20 | i));
|
||||||
|
|
@ -247,20 +242,14 @@ namespace Server.Network
|
||||||
|
|
||||||
var inflatedBuffer = stairBuffers[i];
|
var inflatedBuffer = stairBuffers[i];
|
||||||
|
|
||||||
var deflatedLength = deflatedBuffer.Length;
|
var deflatedLength = Deflate.Standard.Pack(
|
||||||
var ce = Zlib.Pack(
|
|
||||||
deflatedBuffer,
|
deflatedBuffer,
|
||||||
ref deflatedLength,
|
inflatedBuffer.AsSpan(0, size)
|
||||||
inflatedBuffer,
|
|
||||||
size,
|
|
||||||
ZlibQuality.Default
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ce != ZlibError.Okay)
|
if (deflatedLength == 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ZLib error: {0} (#{1})", ce, (int)ce);
|
Console.WriteLine("Compression error");
|
||||||
deflatedLength = 0;
|
|
||||||
size = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write((byte)(9 + i));
|
Write((byte)(9 + i));
|
||||||
|
|
|
||||||
|
|
@ -16,252 +16,245 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Server.Compression;
|
||||||
using Server.Logging;
|
using Server.Logging;
|
||||||
using Server.Network;
|
using Server.Network;
|
||||||
|
|
||||||
namespace Server.Multis
|
namespace Server.Multis;
|
||||||
|
|
||||||
|
public static class HousePackets
|
||||||
{
|
{
|
||||||
public static class HousePackets
|
private static readonly ILogger logger = LogFactory.GetLogger(typeof(HousePackets));
|
||||||
|
|
||||||
|
public static void SendBeginHouseCustomization(this NetState ns, Serial house)
|
||||||
{
|
{
|
||||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(HousePackets));
|
if (ns.CannotSendPackets())
|
||||||
|
|
||||||
public static void SendBeginHouseCustomization(this NetState ns, Serial house)
|
|
||||||
{
|
{
|
||||||
if (ns.CannotSendPackets())
|
return;
|
||||||
{
|
|
||||||
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)
|
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.CannotSendPackets())
|
||||||
{
|
{
|
||||||
if (ns.CannotSendPackets())
|
return;
|
||||||
{
|
|
||||||
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)
|
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.CannotSendPackets())
|
||||||
{
|
{
|
||||||
if (ns.CannotSendPackets())
|
return;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const int planeCount = 9;
|
var writer = new SpanWriter(stackalloc byte[13]);
|
||||||
private const int maxPlaneLength = 0x400;
|
writer.Write((byte)0xBF); // Packet Id
|
||||||
private const int maxPerPlaneOffsetBuffer = 750;
|
writer.Write((ushort)13);
|
||||||
private static readonly int maxPackedPlaneOffsetBuffer = Zlib.MaxPackSize(maxPerPlaneOffsetBuffer * 5);
|
writer.Write((short)0x1D); // Sub-packet
|
||||||
|
writer.Write(house);
|
||||||
|
writer.Write(revision);
|
||||||
|
|
||||||
public static byte[] CreateHouseDesignStateDetailed(Serial serial, int revision, MultiComponentList components)
|
ns.Send(writer.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int planeCount = 9;
|
||||||
|
private const int maxPlaneLength = 0x400;
|
||||||
|
private const int maxPerPlaneOffsetBuffer = 750;
|
||||||
|
|
||||||
|
public static byte[] CreateHouseDesignStateDetailed(Serial serial, int revision, MultiComponentList components)
|
||||||
|
{
|
||||||
|
var xMin = components.Min.X;
|
||||||
|
var yMin = components.Min.Y;
|
||||||
|
var xMax = components.Max.X;
|
||||||
|
var yMax = components.Max.Y;
|
||||||
|
var width = xMax - xMin + 1;
|
||||||
|
var height = yMax - yMin + 1;
|
||||||
|
var tiles = components.List;
|
||||||
|
|
||||||
|
var planeLength = width * height * 2;
|
||||||
|
using var planeOffsetWriter = new SpanWriter(0x500, true);
|
||||||
|
using var planesWriter = new SpanWriter(planeLength * planeCount);
|
||||||
|
|
||||||
|
Span<bool> planesUsed = stackalloc bool[9];
|
||||||
|
planesUsed.Clear();
|
||||||
|
|
||||||
|
int index;
|
||||||
|
var totalPlaneOffsets = 0;
|
||||||
|
var totalPlanes = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < tiles.Length; ++i)
|
||||||
{
|
{
|
||||||
var xMin = components.Min.X;
|
var mte = tiles[i];
|
||||||
var yMin = components.Min.Y;
|
var x = mte.OffsetX - xMin;
|
||||||
var xMax = components.Max.X;
|
var y = mte.OffsetY - yMin;
|
||||||
var yMax = components.Max.Y;
|
int z = mte.OffsetZ;
|
||||||
var width = xMax - xMin + 1;
|
var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0;
|
||||||
var height = yMax - yMin + 1;
|
|
||||||
var tiles = components.List;
|
|
||||||
|
|
||||||
var planeLength = width * height * 2;
|
int plane = z switch
|
||||||
using var planeOffsetWriter = new SpanWriter(0x500, true);
|
|
||||||
using var planesWriter = new SpanWriter(planeLength * planeCount);
|
|
||||||
|
|
||||||
Span<bool> planesUsed = stackalloc bool[9];
|
|
||||||
planesUsed.Clear();
|
|
||||||
|
|
||||||
int index;
|
|
||||||
var totalPlaneOffsets = 0;
|
|
||||||
var totalPlanes = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < tiles.Length; ++i)
|
|
||||||
{
|
{
|
||||||
var mte = tiles[i];
|
0 => 0,
|
||||||
var x = mte.OffsetX - xMin;
|
7 => 1,
|
||||||
var y = mte.OffsetY - yMin;
|
27 => 2,
|
||||||
int z = mte.OffsetZ;
|
47 => 3,
|
||||||
var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0;
|
67 => 4,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
|
||||||
int plane = z switch
|
if (plane > -1)
|
||||||
|
{
|
||||||
|
int size;
|
||||||
|
if (plane == 0)
|
||||||
{
|
{
|
||||||
0 => 0,
|
size = height;
|
||||||
7 => 1,
|
}
|
||||||
27 => 2,
|
else if (floor)
|
||||||
47 => 3,
|
|
||||||
67 => 4,
|
|
||||||
_ => -1
|
|
||||||
};
|
|
||||||
|
|
||||||
if (plane > -1)
|
|
||||||
{
|
{
|
||||||
int size;
|
size = height - 2;
|
||||||
if (plane == 0)
|
x -= 1;
|
||||||
{
|
y -= 1;
|
||||||
size = height;
|
}
|
||||||
}
|
else
|
||||||
else if (floor)
|
{
|
||||||
{
|
size = height - 1;
|
||||||
size = height - 2;
|
plane += 4;
|
||||||
x -= 1;
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
size = height - 1;
|
|
||||||
plane += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = (x * size + y) * 2;
|
|
||||||
|
|
||||||
if (x >= 0 && y >= 0 && y < size && index + 1 < maxPlaneLength)
|
|
||||||
{
|
|
||||||
var planeUsed = planesUsed[plane];
|
|
||||||
var planeWriterIndex = planeLength * plane;
|
|
||||||
if (!planeUsed)
|
|
||||||
{
|
|
||||||
planesUsed[plane] = true;
|
|
||||||
totalPlanes++;
|
|
||||||
planesWriter.Seek(planeWriterIndex, SeekOrigin.Begin);
|
|
||||||
planesWriter.Clear(planeLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
planesWriter.Seek(planeWriterIndex + index, SeekOrigin.Begin);
|
|
||||||
planesWriter.Write(mte.ItemId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
planeOffsetWriter.Write(mte.ItemId);
|
index = (x * size + y) * 2;
|
||||||
planeOffsetWriter.Write((byte)mte.OffsetX);
|
|
||||||
planeOffsetWriter.Write((byte)mte.OffsetY);
|
|
||||||
planeOffsetWriter.Write((byte)mte.OffsetZ);
|
|
||||||
totalPlaneOffsets++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxPlanesLength = (Zlib.MaxPackSize(planeLength) + 4) * totalPlanes;
|
if (x >= 0 && y >= 0 && y < size && index + 1 < maxPlaneLength)
|
||||||
var maxPlaneOffsetLength = totalPlaneOffsets == 0 ? 0
|
|
||||||
: (maxPackedPlaneOffsetBuffer + 4) * (totalPlaneOffsets / maxPerPlaneOffsetBuffer + 1);
|
|
||||||
|
|
||||||
var buffer = GC.AllocateUninitializedArray<byte>(18 + maxPlanesLength + maxPlaneOffsetLength);
|
|
||||||
var writer = new SpanWriter(buffer);
|
|
||||||
writer.Write((byte)0xD8); // Packet ID
|
|
||||||
writer.Seek(2, SeekOrigin.Current); // Length
|
|
||||||
|
|
||||||
writer.Write((byte)0x03); // Compression Type
|
|
||||||
writer.Write((byte)0x00); // Unknown
|
|
||||||
writer.Write(serial);
|
|
||||||
writer.Write(revision);
|
|
||||||
writer.Write((short)tiles.Length);
|
|
||||||
writer.Seek(3, SeekOrigin.Current); // Buffer Length, Plane Count
|
|
||||||
|
|
||||||
var totalLength = 1; // includes plane count
|
|
||||||
|
|
||||||
for (var i = 0; i < planeCount; i++)
|
|
||||||
{
|
|
||||||
if (!planesUsed[i])
|
|
||||||
{
|
{
|
||||||
|
var planeUsed = planesUsed[plane];
|
||||||
|
var planeWriterIndex = planeLength * plane;
|
||||||
|
if (!planeUsed)
|
||||||
|
{
|
||||||
|
planesUsed[plane] = true;
|
||||||
|
totalPlanes++;
|
||||||
|
planesWriter.Seek(planeWriterIndex, SeekOrigin.Begin);
|
||||||
|
planesWriter.Clear(planeLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
planesWriter.Seek(planeWriterIndex + index, SeekOrigin.Begin);
|
||||||
|
planesWriter.Write(mte.ItemId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int size = i switch
|
|
||||||
{
|
|
||||||
0 => planeLength,
|
|
||||||
< 5 => (width - 1) * (height - 2) * 2,
|
|
||||||
_ => width * (height - 1) * 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var planeWriterIndex = planeLength * i;
|
|
||||||
|
|
||||||
var source = planesWriter.RawBuffer.Slice(planeWriterIndex, size);
|
|
||||||
|
|
||||||
writer.Write((byte)(0x20 | i));
|
|
||||||
WritePacked(source, ref writer, out int destLength);
|
|
||||||
|
|
||||||
totalLength += 4 + destLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
index = 0;
|
planeOffsetWriter.Write(mte.ItemId);
|
||||||
while (totalPlaneOffsets > 0)
|
planeOffsetWriter.Write((byte)mte.OffsetX);
|
||||||
{
|
planeOffsetWriter.Write((byte)mte.OffsetY);
|
||||||
var count = Math.Min(maxPerPlaneOffsetBuffer, totalPlaneOffsets);
|
planeOffsetWriter.Write((byte)mte.OffsetZ);
|
||||||
totalPlaneOffsets -= count;
|
totalPlaneOffsets++;
|
||||||
|
|
||||||
var source = planeOffsetWriter.RawBuffer.Slice(index * 5, count * 5);
|
|
||||||
|
|
||||||
writer.Write((byte)(9 + index++));
|
|
||||||
WritePacked(source, ref writer, out int destLength);
|
|
||||||
totalLength += 4 + destLength;
|
|
||||||
totalPlanes++;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Seek(15, SeekOrigin.Begin);
|
|
||||||
writer.Write((short)totalLength);
|
|
||||||
writer.Write((byte)totalPlanes);
|
|
||||||
writer.WritePacketLength();
|
|
||||||
|
|
||||||
// TODO: Avoid this somehow.
|
|
||||||
Array.Resize(ref buffer, writer.BytesWritten);
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
var writer = new SpanWriter(0x10000);
|
||||||
private static void WritePacked(ReadOnlySpan<byte> source, ref SpanWriter writer, out int length)
|
writer.Write((byte)0xD8); // Packet ID
|
||||||
|
writer.Seek(2, SeekOrigin.Current); // Length
|
||||||
|
|
||||||
|
writer.Write((byte)0x03); // Compression Type
|
||||||
|
writer.Write((byte)0x00); // Unknown
|
||||||
|
writer.Write(serial);
|
||||||
|
writer.Write(revision);
|
||||||
|
writer.Write((short)tiles.Length);
|
||||||
|
writer.Seek(3, SeekOrigin.Current); // Buffer Length, Plane Count
|
||||||
|
|
||||||
|
var totalLength = 1; // includes plane count
|
||||||
|
|
||||||
|
for (var i = 0; i < planeCount; i++)
|
||||||
{
|
{
|
||||||
var size = source.Length;
|
if (!planesUsed[i])
|
||||||
var dest = writer.RawBuffer[(writer.Position + 3)..];
|
|
||||||
length = dest.Length;
|
|
||||||
|
|
||||||
var ce = Zlib.Pack(dest, ref length, source, ZlibQuality.Default);
|
|
||||||
|
|
||||||
if (ce != ZlibError.Okay)
|
|
||||||
{
|
{
|
||||||
logger.Warning("ZLib error: {Error} (#{ErrorCode})", ce, (int)ce);
|
continue;
|
||||||
length = 0;
|
|
||||||
size = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int size = i switch
|
||||||
|
{
|
||||||
|
0 => planeLength,
|
||||||
|
< 5 => (width - 1) * (height - 2) * 2,
|
||||||
|
_ => width * (height - 1) * 2
|
||||||
|
};
|
||||||
|
|
||||||
|
var planeWriterIndex = planeLength * i;
|
||||||
|
|
||||||
|
var source = planesWriter.RawBuffer.Slice(planeWriterIndex, size);
|
||||||
|
|
||||||
|
writer.Write((byte)(0x20 | i));
|
||||||
writer.Write((byte)size);
|
writer.Write((byte)size);
|
||||||
writer.Write((byte)length);
|
WritePacked(source, ref writer, out int destLength);
|
||||||
writer.Write((byte)(((size >> 4) & 0xF0) | ((length >> 8) & 0xF)));
|
|
||||||
writer.Seek(length, SeekOrigin.Current);
|
totalLength += 4 + destLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
while (totalPlaneOffsets > 0)
|
||||||
|
{
|
||||||
|
var count = Math.Min(maxPerPlaneOffsetBuffer, totalPlaneOffsets);
|
||||||
|
totalPlaneOffsets -= count;
|
||||||
|
var size = count * 5;
|
||||||
|
|
||||||
|
var source = planeOffsetWriter.RawBuffer.Slice(index * 5, size);
|
||||||
|
|
||||||
|
writer.Write((byte)(9 + index++));
|
||||||
|
writer.Write((byte)size);
|
||||||
|
WritePacked(source, ref writer, out int destLength);
|
||||||
|
totalLength += 4 + destLength;
|
||||||
|
totalPlanes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Seek(15, SeekOrigin.Begin);
|
||||||
|
writer.Write((short)totalLength);
|
||||||
|
writer.Write((byte)totalPlanes);
|
||||||
|
writer.WritePacketLength();
|
||||||
|
|
||||||
|
var buffer = writer.Span.ToArray();
|
||||||
|
writer.Dispose();
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void WritePacked(ReadOnlySpan<byte> source, ref SpanWriter writer, out int length)
|
||||||
|
{
|
||||||
|
var size = source.Length;
|
||||||
|
var dest = writer.RawBuffer[(writer.Position + 2)..];
|
||||||
|
|
||||||
|
length = Deflate.Standard.Pack(dest, source);
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
logger.Warning("House packet compression failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write((byte)length);
|
||||||
|
writer.Write((byte)(((size >> 4) & 0xF0) | ((length >> 8) & 0xF)));
|
||||||
|
writer.Seek(length, SeekOrigin.Current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,9 @@
|
||||||
<Delete Files="..\..\Distribution\Assemblies\ref\$(AssemblyName).dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Assemblies\ref\$(AssemblyName).dll" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).deps.json" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).deps.json" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).pdb" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).pdb" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\Assemblies\ZLib.Bindings.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\LibDeflate.Bindings.dll" ContinueOnError="true" />
|
||||||
|
<Delete Files="..\..\Distribution\libdeflate.dll" ContinueOnError="true" />
|
||||||
|
<Delete Files="..\..\Distribution\libdeflate.dylib" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\Assemblies\Zstd.Binaries.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Assemblies\Zstd.Binaries.dll" ContinueOnError="true" />
|
||||||
<Delete Files="..\..\Distribution\Assemblies\ModernUO.Serialization.Annotations.dll" ContinueOnError="true" />
|
<Delete Files="..\..\Distribution\Assemblies\ModernUO.Serialization.Annotations.dll" ContinueOnError="true" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
@ -32,10 +34,10 @@
|
||||||
<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="LibDeflate.Bindings" Version="1.0.0.120" />
|
||||||
<PackageReference Include="MailKit" Version="4.5.0" />
|
<PackageReference Include="MailKit" Version="4.5.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
|
||||||
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
|
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
|
||||||
<PackageReference Include="Zlib.Bindings" Version="1.11.0" />
|
|
||||||
<PackageReference Include="Argon2.Bindings" Version="1.16.1" />
|
<PackageReference Include="Argon2.Bindings" Version="1.16.1" />
|
||||||
<PackageReference Include="Zstd.Binaries" Version="1.6.0" />
|
<PackageReference Include="Zstd.Binaries" Version="1.6.0" />
|
||||||
|
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -62,7 +62,7 @@ ModernUO [] [os] [arch (default: x64)]`
|
- Run `./publish.cmd [release|debug (default: release)] [os] [arch (default: x64)]`
|
||||||
- `os` - [Supported operating systems](https://github.com/dotnet/core/blob/main/release-notes/7.0/supported-os.md)
|
- `os` - [Supported operating systems](https://github.com/dotnet/core/blob/main/release-notes/7.0/supported-os.md)
|
||||||
- `win` - Windows 10/11/2019/2022
|
- `win` - Windows 10/11/2019/2022
|
||||||
- `osx` - MacOS 11/12/13 (Big Sur, Monterey, Ventura)
|
- `osx` - MacOS 12/13/14 (Sonoma, Big Sur, Monterey)
|
||||||
- `linux` - Linux
|
- `linux` - Linux
|
||||||
- `arch`
|
- `arch`
|
||||||
- `x64` - Intel 64-bit
|
- `x64` - Intel 64-bit
|
||||||
|
|
@ -74,13 +74,18 @@ ModernUO [![Discord](https://img.shields.io/discord/751317910504603701?logo=disc
|
||||||
dnf upgrade --refresh -y
|
dnf upgrade --refresh -y
|
||||||
# CentOS does not come with EPEL enabled
|
# CentOS does not come with EPEL enabled
|
||||||
dnf install -y epel-release epel-next-release
|
dnf install -y epel-release epel-next-release
|
||||||
dnf install -y findutils libicu zlib-devel zstd libargon2-devel tzdata
|
dnf install -y findutils libicu libdeflate-devel zstd libargon2-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ubuntu, Debian, etc
|
### Ubuntu, Debian, etc
|
||||||
```shell
|
```shell
|
||||||
apt-get update -y
|
apt-get update -y
|
||||||
apt-get install -y libicu-dev libz-dev zstd libargon2-dev tzdata
|
apt-get install -y libicu-dev libdeflate-dev zstd libargon2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## OSX Requirements
|
||||||
|
```shell
|
||||||
|
brew install icu4c libdeflate zstd argon2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the Server
|
## Running the Server
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
||||||
"version": "0.13.2"
|
"version": "0.13.3"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue