diff --git a/src/MHServerEmu.Core/Network/MessagePackageOut.cs b/src/MHServerEmu.Core/Network/MessagePackageOut.cs index 03854f1c..af6ef0e2 100644 --- a/src/MHServerEmu.Core/Network/MessagePackageOut.cs +++ b/src/MHServerEmu.Core/Network/MessagePackageOut.cs @@ -1,4 +1,5 @@ using Google.ProtocolBuffers; +using MHServerEmu.Core.Serialization; namespace MHServerEmu.Core.Network { @@ -43,5 +44,15 @@ namespace MHServerEmu.Core.Network stream.WriteRawVarint32((uint)Message.SerializedSize); Message.WriteTo(stream); } + + /// + /// Writes this to the provided . + /// + public void WriteTo(ICodedOutputStreamEx stream) + { + stream.WriteRawVarint32(Id); + stream.WriteRawVarint32((uint)Message.SerializedSize); + Message.WriteTo(stream); + } } } diff --git a/src/MHServerEmu.Core/Network/MuxPacket.cs b/src/MHServerEmu.Core/Network/MuxPacket.cs index 993bd537..b9d66750 100644 --- a/src/MHServerEmu.Core/Network/MuxPacket.cs +++ b/src/MHServerEmu.Core/Network/MuxPacket.cs @@ -1,10 +1,9 @@ -using System.Buffers; -using System.Collections; +using System.Collections; using Google.ProtocolBuffers; -using MHServerEmu.Core.Helpers; using MHServerEmu.Core.Logging; using MHServerEmu.Core.Memory; using MHServerEmu.Core.Network.Tcp; +using MHServerEmu.Core.Serialization; namespace MHServerEmu.Core.Network { @@ -17,7 +16,6 @@ namespace MHServerEmu.Core.Network // Packets apparently go as high as 2800+ messages based on logs, so we presize pooled lists to 4096 to fit that and extra. private static readonly ConcurrentPool> MessageListPool = new(4096, static () => new(4096)); - private static readonly ArrayPool BufferPool = ArrayPool.Create(); private readonly List _outboundMessageList = null; @@ -137,15 +135,10 @@ namespace MHServerEmu.Core.Network if (_outboundMessageList.Count == 0) return Logger.WarnReturn(false, "SerializeData(): Data packet contains no messages"); - // Use pooled buffers for coded output streams with reflection hackery, see ProtobufHelper for more info. - byte[] buffer = BufferPool.Rent(4096); + using FastCodedOutputStream cos = FastCodedOutputStream.CreateInstance(stream); - CodedOutputStream cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(stream, buffer); foreach (MessagePackageOut messagePackage in _outboundMessageList) messagePackage.WriteTo(cos); - cos.Flush(); - - BufferPool.Return(buffer); return true; } diff --git a/src/MHServerEmu.Core/Serialization/FastCodedOutputStream.cs b/src/MHServerEmu.Core/Serialization/FastCodedOutputStream.cs new file mode 100644 index 00000000..37cfd3d9 --- /dev/null +++ b/src/MHServerEmu.Core/Serialization/FastCodedOutputStream.cs @@ -0,0 +1,471 @@ +using System.Buffers; +using System.Collections; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Google.ProtocolBuffers; +using Google.ProtocolBuffers.Descriptors; +using MHServerEmu.Core.Helpers; + +namespace MHServerEmu.Core.Serialization +{ + /// + /// A more memory efficient version of . + /// + public sealed class FastCodedOutputStream : ICodedOutputStreamEx, IDisposable + { + private const int DefaultBufferSize = 4096; + + private static readonly ConcurrentBag InstancePool = new(); + private static readonly ArrayPool BufferPool = ArrayPool.Create(); + + private readonly byte[] _floatBuffer = new byte[sizeof(float)]; + + private byte[] _buffer; + private CodedOutputStream _cos; + + private FastCodedOutputStream() { } + + private void Initialize(Stream stream, int bufferSize) + { + // Use pooled buffers for coded output streams with reflection hackery, see ProtobufHelper for more info. + _buffer = BufferPool.Rent(bufferSize); + _cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(stream, _buffer); + } + + public static FastCodedOutputStream CreateInstance(Stream stream, int bufferSize = DefaultBufferSize) + { + if (InstancePool.TryTake(out FastCodedOutputStream cos) == false) + cos = new(); + + cos.Initialize(stream, bufferSize); + return cos; + } + + #region IDisposable + + public void Dispose() + { + if (_cos != null) + { + _cos.Flush(); + _cos = null; + } + + if (_buffer != null) + { + BufferPool.Return(_buffer); + _buffer = null; + } + + InstancePool.Add(this); + } + + #endregion + + #region ICodedOutputStream + + // For most of this we just pass everything to the default implementation. + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Flush() + { + _cos.Flush(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteArray(FieldType fieldType, int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteArray(fieldType, fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBool(int fieldNumber, string fieldName, bool value) + { + _cos.WriteBool(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBoolArray(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteBoolArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBytes(int fieldNumber, string fieldName, ByteString value) + { + _cos.WriteBytes(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBytesArray(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteBytesArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDouble(int fieldNumber, string fieldName, double value) + { + _cos.WriteDouble(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDoubleArray(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteDoubleArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteEnum(int fieldNumber, string fieldName, int value, object rawValue) + { + _cos.WriteEnum(fieldNumber, fieldName, value, rawValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteEnumArray(int fieldNumber, string fieldName, IEnumerable list) where T : struct, IComparable, IFormattable + { + _cos.WriteEnumArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteField(FieldType fieldType, int fieldNumber, string fieldName, object value) + { + _cos.WriteField(fieldType, fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFixed32(int fieldNumber, string fieldName, uint value) + { + _cos.WriteFixed32(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFixed32Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteFixed32Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFixed64(int fieldNumber, string fieldName, ulong value) + { + _cos.WriteFixed64(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFixed64Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteFixed64Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFloat(int fieldNumber, string fieldName, float value) + { + //_cos.WriteFloat(fieldNumber, fieldName, value); + + _cos.WriteTag(fieldNumber, WireFormat.WireType.Fixed32); + + MemoryMarshal.Cast(_floatBuffer)[0] = value; + _cos.WriteRawBytes(_floatBuffer, 0, 4); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFloatArray(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteFloatArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteGroup(int fieldNumber, string fieldName, IMessageLite value) + { + _cos.WriteGroup(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteGroupArray(int fieldNumber, string fieldName, IEnumerable list) where T : IMessageLite + { + _cos.WriteGroupArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(int fieldNumber, string fieldName, int value) + { + _cos.WriteInt32(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteInt32Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(int fieldNumber, string fieldName, long value) + { + _cos.WriteInt64(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteInt64Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteMessage(int fieldNumber, string fieldName, IMessageLite value) + { + _cos.WriteMessage(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteMessageArray(int fieldNumber, string fieldName, IEnumerable list) where T : IMessageLite + { + _cos.WriteMessageArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteMessageEnd() + { + _cos.Flush(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteMessageSetExtension(int fieldNumber, string fieldName, IMessageLite value) + { + _cos.WriteMessageSetExtension(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteMessageSetExtension(int fieldNumber, string fieldName, ByteString value) + { + _cos.WriteMessageSetExtension(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteMessageStart() + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedArray(FieldType fieldType, int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WritePackedArray(fieldType, fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedBoolArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedBoolArray(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedDoubleArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedDoubleArray(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedEnumArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) where T : struct, IComparable, IFormattable + { + _cos.WritePackedEnumArray(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedFixed32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedFixed32Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedFixed64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedFixed64Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedFloatArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedFloatArray(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedInt32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedInt32Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedInt64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedInt64Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedSFixed32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedSFixed32Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedSFixed64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedSFixed64Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedSInt32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedSInt32Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedSInt64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedSInt64Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedUInt32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedUInt32Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackedUInt64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable list) + { + _cos.WritePackedUInt64Array(fieldNumber, fieldName, calculatedSize, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSFixed32(int fieldNumber, string fieldName, int value) + { + _cos.WriteSFixed32(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSFixed32Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteSFixed32Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSFixed64(int fieldNumber, string fieldName, long value) + { + _cos.WriteSFixed64(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSFixed64Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteSFixed64Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSInt32(int fieldNumber, string fieldName, int value) + { + _cos.WriteSInt32(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSInt32Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteSInt32Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSInt64(int fieldNumber, string fieldName, long value) + { + _cos.WriteSInt64(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSInt64Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteSInt64Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteString(int fieldNumber, string fieldName, string value) + { + _cos.WriteString(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteStringArray(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteStringArray(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(int fieldNumber, string fieldName, uint value) + { + _cos.WriteUInt32(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteUInt32Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(int fieldNumber, string fieldName, ulong value) + { + _cos.WriteUInt64(fieldNumber, fieldName, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64Array(int fieldNumber, string fieldName, IEnumerable list) + { + _cos.WriteUInt64Array(fieldNumber, fieldName, list); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUnknownBytes(int fieldNumber, ByteString value) + { + _cos.WriteUnknownBytes(fieldNumber, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUnknownField(int fieldNumber, WireFormat.WireType wireType, ulong value) + { + _cos.WriteUnknownField(fieldNumber, wireType, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete] + public void WriteUnknownGroup(int fieldNumber, IMessageLite value) + { + _cos.WriteUnknownGroup(fieldNumber, value); + } + + #endregion + + #region ICodedOutputStreamEx + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteRawVarint32(uint value) + { + _cos.WriteRawVarint32(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteRawVarint64(ulong value) + { + _cos.WriteRawVarint64(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteRawByte(byte value) + { + _cos.WriteRawByte(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteRawBytes(byte[] value) + { + _cos.WriteRawBytes(value); + } + + #endregion + } +} diff --git a/src/MHServerEmu.Core/Serialization/ICodedOutputStreamEx.cs b/src/MHServerEmu.Core/Serialization/ICodedOutputStreamEx.cs new file mode 100644 index 00000000..0bd1577a --- /dev/null +++ b/src/MHServerEmu.Core/Serialization/ICodedOutputStreamEx.cs @@ -0,0 +1,18 @@ +using Google.ProtocolBuffers; + +namespace MHServerEmu.Core.Serialization +{ + /// + /// Extended version of that exposes additional low level writing functionality. + /// + public interface ICodedOutputStreamEx : ICodedOutputStream + { + void WriteRawVarint32(uint value); + + void WriteRawVarint64(ulong value); + + void WriteRawByte(byte value); + + void WriteRawBytes(byte[] value); + } +}