From 528a2b59e1b5519ddf0bebff6e92300705656952 Mon Sep 17 00:00:00 2001 From: chai <215380520@qq.com> Date: Fri, 13 Oct 2023 20:28:43 +0800 Subject: + MultiplayerToolkit --- "Documents/\350\265\204\346\226\231.xlsx" | Bin 1572027 -> 2453852 bytes .../Assets/MultiplayerToolkit/IRecyclable.cs | 29 -- .../Assets/MultiplayerToolkit/Message.meta | 8 + .../MultiplayerToolkit/Message/IRecyclable.cs | 29 ++ .../MultiplayerToolkit/Message/MessageReader.cs | 469 +++++++++++++++++++++ .../Message/MessageReader.cs.meta | 11 + .../MultiplayerToolkit/Message/MessageWriter.cs | 318 ++++++++++++++ .../Message/MessageWriter.cs.meta | 11 + .../MultiplayerToolkit/Message/ObjectPool.cs | 108 +++++ .../Assets/MultiplayerToolkit/MessageReader.cs | 456 -------------------- .../MultiplayerToolkit/MessageReader.cs.meta | 11 - .../Assets/MultiplayerToolkit/MessageWriter.cs | 307 -------------- .../MultiplayerToolkit/MessageWriter.cs.meta | 11 - .../Assets/MultiplayerToolkit/ObjectPool.cs | 108 ----- .../Assets/MultiplayerToolkit/Test/UnitTests.meta | 8 + .../Test/UnitTests/MessageUnitTests.cs | 11 + .../Test/UnitTests/MessageUnitTests.cs.meta | 11 + Projects/Message/Message/.gitignore | 194 +++++++++ Projects/Message/Message/IRecyclable.cs | 29 ++ Projects/Message/Message/Message.csproj | 74 ++++ Projects/Message/Message/Message.lutconfig | 6 + Projects/Message/Message/Message.sln | 25 ++ Projects/Message/Message/MessageReader.cs | 469 +++++++++++++++++++++ Projects/Message/Message/MessageWriter.cs | 318 ++++++++++++++ Projects/Message/Message/ObjectPool.cs | 108 +++++ .../Message/Message/Properties/AssemblyInfo.cs | 20 + Projects/Message/Message/UnitTest1.cs | 16 + Projects/Message/Message/packages.config | 5 + .../Hazel.UnitTests/MessageReaderTests.cs | 29 +- Tools/Hazel-Networking/Hazel.lutconfig | 6 + Tools/Hazel-Networking/Hazel/MessageReader.cs | 74 ++-- Tools/Hazel-Networking/Hazel/MessageWriter.cs | 22 +- .../Hazel/Udp/UdpClientConnection.cs | 6 +- .../Hazel/Udp/UdpConnection.Reliable.cs | 15 +- Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs | 4 +- 35 files changed, 2367 insertions(+), 959 deletions(-) delete mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/IRecyclable.cs create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message/IRecyclable.cs create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs.meta create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs.meta create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Message/ObjectPool.cs delete mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs delete mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs.meta delete mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs delete mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs.meta delete mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/ObjectPool.cs create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs create mode 100644 MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta create mode 100644 Projects/Message/Message/.gitignore create mode 100644 Projects/Message/Message/IRecyclable.cs create mode 100644 Projects/Message/Message/Message.csproj create mode 100644 Projects/Message/Message/Message.lutconfig create mode 100644 Projects/Message/Message/Message.sln create mode 100644 Projects/Message/Message/MessageReader.cs create mode 100644 Projects/Message/Message/MessageWriter.cs create mode 100644 Projects/Message/Message/ObjectPool.cs create mode 100644 Projects/Message/Message/Properties/AssemblyInfo.cs create mode 100644 Projects/Message/Message/UnitTest1.cs create mode 100644 Projects/Message/Message/packages.config create mode 100644 Tools/Hazel-Networking/Hazel.lutconfig diff --git "a/Documents/\350\265\204\346\226\231.xlsx" "b/Documents/\350\265\204\346\226\231.xlsx" index 94d5672..f390a9a 100644 Binary files "a/Documents/\350\265\204\346\226\231.xlsx" and "b/Documents/\350\265\204\346\226\231.xlsx" differ diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/IRecyclable.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/IRecyclable.cs deleted file mode 100644 index 17d3104..0000000 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/IRecyclable.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace MultiplayerToolkit -{ - /// - /// Interface for all items that can be returned to an object pool. - /// - /// - public interface IRecyclable - { - /// - /// Returns this object back to the object pool. - /// - /// - /// - /// Calling this when you are done with the object returns the object back to a pool in order to be reused. - /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this. - /// - /// - /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are - /// completely finished with the object as the contents can be overwritten at any point after. - /// - /// - void Recycle(); - } -} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta new file mode 100644 index 0000000..b3bcd16 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3227b22007e540546967afc661e59c92 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/IRecyclable.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/IRecyclable.cs new file mode 100644 index 0000000..17d3104 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/IRecyclable.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MultiplayerToolkit +{ + /// + /// Interface for all items that can be returned to an object pool. + /// + /// + public interface IRecyclable + { + /// + /// Returns this object back to the object pool. + /// + /// + /// + /// Calling this when you are done with the object returns the object back to a pool in order to be reused. + /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this. + /// + /// + /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are + /// completely finished with the object as the contents can be overwritten at any point after. + /// + /// + void Recycle(); + } +} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs new file mode 100644 index 0000000..7ec134b --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs @@ -0,0 +1,469 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace MultiplayerToolkit +{ + /// + /// + /// + public class MessageReader : IRecyclable + { + public static readonly ObjectPool ReaderPool = new ObjectPool(() => new MessageReader()); + + public byte[] Buffer; // √ 缓冲区,会被子协议共享这个缓冲区 + public byte Tag; // √ 协议名 + + public int Length; // √ 协议长度,对应的是MessageWriter里写入的2bytes包长度(不含length和tag) + public int Offset; // √ length和tag后面的位置(在Buffer中的位置),不是游标,不会随读取发生改变。会影响readHead的值 + + //数据位于Buffer中Offset索引开始Length长度,协议位于Buffer中Offset-3开始的Length+3长度 + + public int BytesRemaining => this.Length - this.Position; + + private MessageReader Parent; // 保存父协议索引,和父协议共享父协议的Buffer + + public int Position + { + get { return this._position; } + set + { + this._position = value; // value是 + this.readHead = value + Offset; + } + } + + private int _position; // √ 读取游标,相对于协议内容部分的偏移,从0开始,子协议也是从0开始,不是在Buffer中的索引 + private int readHead; // √ 读取游标,协议头部在Buffer中的位置,参考ReadMessage() + + public static MessageReader GetSized(int minSize) + { + var output = ReaderPool.GetObject(); + + if (output.Buffer == null || output.Buffer.Length < minSize) + { + output.Buffer = new byte[minSize]; + } + else + { + Array.Clear(output.Buffer, 0, output.Buffer.Length); + } + + output.Offset = 0; + output.Position = 0; + output.Tag = byte.MaxValue; + return output; + } + + public static MessageReader Get(byte[] buffer) + { + var output = ReaderPool.GetObject(); + + output.Buffer = buffer; + output.Offset = 0; + output.Position = 0; + output.Length = buffer.Length; + output.Tag = byte.MaxValue; + + return output; + } + + /// + /// 创建一个新的父message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// + /// + /// + public static MessageReader CopyMessageIntoParent(MessageReader source) + { + var output = MessageReader.GetSized(source.Length + 3); //3=2bytes(length) + 1byte(tag) + System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); + + output.Offset = 0; + output.Position = 0; + output.Length = source.Length + 3; + + return output; + } + + /// + /// 完全复制 一份 + /// + /// + /// + public static MessageReader Get(MessageReader source) + { + var output = MessageReader.GetSized(source.Buffer.Length); + System.Buffer.BlockCopy(source.Buffer, 0, output.Buffer, 0, source.Buffer.Length); + + output.Offset = source.Offset; + + output._position = source._position; + output.readHead = source.readHead; + + output.Length = source.Length; + output.Tag = source.Tag; + + return output; + } + + public static MessageReader Get(byte[] buffer, int offset) + { + // Ensure there is at least a header + if (offset + 3 > buffer.Length) return null; + + var output = ReaderPool.GetObject(); + + output.Buffer = buffer; + output.Offset = offset; + output.Position = 0; + + output.Length = output.ReadUInt16(); // 读到的值为length + output.Tag = output.ReadByte(); + + output.Offset += 3; + output.Position = 0; + + return output; + } + + /// + /// Produces a MessageReader using the parent's buffer. This MessageReader should **NOT** be recycled. + /// + public MessageReader ReadMessage() + { + // Ensure there is at least a header + if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); + + MessageReader output = new MessageReader(); + + output.Parent = this; + output.Buffer = this.Buffer; + output.Offset = this.readHead; // 下面会读取 + output.Position = 0; + + // 读length和tag的值并保存在字段里 + output.Length = output.ReadUInt16(); + output.Tag = output.ReadByte(); + + // Offset, readHead齐步走,移到Length和Tag后面,_position=0 + output.Offset += 3; // 3=length+tag + output.Position = 0; + + if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); + + this.Position += output.Length + 3; //跳过整个子协议 + return output; + } + + /// + /// Produces a MessageReader with a new buffer. This MessageReader should be recycled. + /// + public MessageReader ReadMessageAsNewBuffer() + { + if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); + + var len = this.ReadUInt16(); + var tag = this.ReadByte(); + + if (this.BytesRemaining < len) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {len} of {this.BytesRemaining}"); + + var output = MessageReader.GetSized(len); + + Array.Copy(this.Buffer, this.readHead, output.Buffer, 0, len); + + output.Length = len; + output.Tag = tag; + + this.Position += output.Length; + return output; + } + + public MessageWriter StartWriter() + { + var output = new MessageWriter(this.Buffer); + output.Position = this.readHead; + return output; + } + + public MessageReader Duplicate() + { + var output = GetSized(this.Length); + Array.Copy(this.Buffer, this.Offset, output.Buffer, 0, this.Length); + output.Length = this.Length; + output.Offset = 0; + output.Position = 0; + + return output; + } + + public void RemoveMessage(MessageReader reader) + { + var temp = MessageReader.GetSized(reader.Buffer.Length); + try + { + var headerOffset = reader.Offset - 3; + var endOfMessage = reader.Offset + reader.Length; + var len = reader.Buffer.Length - endOfMessage; + + Array.Copy(reader.Buffer, endOfMessage, temp.Buffer, 0, len); + Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset, len); + + this.AdjustLength(reader.Offset, reader.Length + 3); + } + finally + { + temp.Recycle(); + } + } + + //public void InsertMessage(MessageReader reader, MessageWriter writer) + //{ + // var temp = MessageReader.GetSized(reader.Buffer.Length); + // try + // { + // var headerOffset = reader.Offset - 3; + // var startOfMessage = reader.Offset; + // var len = reader.Buffer.Length - startOfMessage; + // int writerOffset = 3; + // switch (writer.SendOption) + // { + // case SendOption.Reliable: + // writerOffset = 3; + // break; + // case SendOption.None: + // writerOffset = 1; + // break; + // } + + // //store the original buffer in temp + // Array.Copy(reader.Buffer, headerOffset, temp.Buffer, 0, len); + + // //put the contents of writer in at headerOffset + // Array.Copy(writer.Buffer, writerOffset, this.Buffer, headerOffset, writer.Length - writerOffset); + + // //put the original buffer in after that + // Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset + (writer.Length - writerOffset), len - writer.Length); + + // this.AdjustLength(-1 * reader.Offset, -1 * (writer.Length - writerOffset)); + // } + // finally + // { + // temp.Recycle(); + // } + //} + + private void AdjustLength(int offset, int amount) + { + if (this.readHead > offset) + { + this.Position -= amount; + } + + if (Parent != null) + { + var lengthOffset = this.Offset - 3; + var curLen = this.Buffer[lengthOffset] + | (this.Buffer[lengthOffset + 1] << 8); + + curLen -= amount; + this.Length -= amount; + + this.Buffer[lengthOffset] = (byte)curLen; + this.Buffer[lengthOffset + 1] = (byte)(this.Buffer[lengthOffset + 1] >> 8); + + Parent.AdjustLength(offset, amount); + } + } + + public void Recycle() + { + this.Parent = null; + ReaderPool.PutObject(this); + } + + #region Read Methods + public bool ReadBoolean() + { + byte val = this.FastByte(); + return val != 0; + } + + public sbyte ReadSByte() + { + return (sbyte)this.FastByte(); + } + + public byte ReadByte() + { + return this.FastByte(); + } + + public ushort ReadUInt16() + { + ushort output = + (ushort)(this.FastByte() + | this.FastByte() << 8); + return output; + } + + public short ReadInt16() + { + short output = + (short)(this.FastByte() + | this.FastByte() << 8); + return output; + } + + public uint ReadUInt32() + { + uint output = this.FastByte() + | (uint)this.FastByte() << 8 + | (uint)this.FastByte() << 16 + | (uint)this.FastByte() << 24; + + return output; + } + + public int ReadInt32() + { + int output = this.FastByte() + | this.FastByte() << 8 + | this.FastByte() << 16 + | this.FastByte() << 24; + + return output; + } + + public ulong ReadUInt64() + { + ulong output = (ulong)this.FastByte() + | (ulong)this.FastByte() << 8 + | (ulong)this.FastByte() << 16 + | (ulong)this.FastByte() << 24 + | (ulong)this.FastByte() << 32 + | (ulong)this.FastByte() << 40 + | (ulong)this.FastByte() << 48 + | (ulong)this.FastByte() << 56; + + return output; + } + + public long ReadInt64() + { + long output = (long)this.FastByte() + | (long)this.FastByte() << 8 + | (long)this.FastByte() << 16 + | (long)this.FastByte() << 24 + | (long)this.FastByte() << 32 + | (long)this.FastByte() << 40 + | (long)this.FastByte() << 48 + | (long)this.FastByte() << 56; + + return output; + } + + public unsafe float ReadSingle() + { + float output = 0; + fixed (byte* bufPtr = &this.Buffer[this.readHead]) + { + byte* outPtr = (byte*)&output; + + *outPtr = *bufPtr; + *(outPtr + 1) = *(bufPtr + 1); + *(outPtr + 2) = *(bufPtr + 2); + *(outPtr + 3) = *(bufPtr + 3); + } + + this.Position += 4; + return output; + } + + public string ReadString() + { + int len = this.ReadPackedInt32(); + if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); + + string output = UTF8Encoding.UTF8.GetString(this.Buffer, this.readHead, len); + + this.Position += len; + return output; + } + + public byte[] ReadBytesAndSize() + { + int len = this.ReadPackedInt32(); + if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); + + return this.ReadBytes(len); + } + + public byte[] ReadBytes(int length) + { + if (this.BytesRemaining < length) throw new InvalidDataException($"Read length is longer than message length: {length} of {this.BytesRemaining}"); + + byte[] output = new byte[length]; + Array.Copy(this.Buffer, this.readHead, output, 0, output.Length); + this.Position += output.Length; + return output; + } + + /// + public int ReadPackedInt32() + { + return (int)this.ReadPackedUInt32(); + } + + /// + public uint ReadPackedUInt32() + { + bool readMore = true; + int shift = 0; + uint output = 0; + + while (readMore) + { + if (this.BytesRemaining < 1) throw new InvalidDataException($"Read length is longer than message length."); + + byte b = this.ReadByte(); + if (b >= 0x80) + { + readMore = true; + b ^= 0x80; + } + else + { + readMore = false; + } + + output |= (uint)(b << shift); + shift += 7; + } + + return output; + } + #endregion + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte FastByte() + { + this._position++; + return this.Buffer[this.readHead++]; + } + + public unsafe static bool IsLittleEndian() + { + byte b; + unsafe + { + int i = 1; + byte* bp = (byte*)&i; + b = *bp; + } + + return b == 1; + } + } +} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs.meta new file mode 100644 index 0000000..2f470c6 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 35714cf36eee0fc458f427f0a6cea532 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs new file mode 100644 index 0000000..b23ce82 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace MultiplayerToolkit +{ + + /// + /// AmongUsùȥ˰ͷش + /// ṹ + /// ------------------------------------- + /// 2bytes (ushort)Эijȣ2bytes + /// ------------------------------------- + /// 1byte ЭͣЭ0ԶЭ1 + /// (1byte) (ԶЭ飬ṩһmodţSteam.UGC.Itemidulong 8bytesе󣬻ţ֧255mod) + /// 2byte (ushort)ЭID + /// + /// ------------------------------------- + /// + public class MessageWriter : IRecyclable + { + public const int BYTE_COUNT_OF_LENGTH = 2; // ռ2 bytes + public static int BufferSize = 64000; // Э֧64000ֽڣ62.5KB + public static readonly ObjectPool WriterPool = new ObjectPool(() => new MessageWriter(BufferSize)); + + public byte[] Buffer; + public int Length; // ЧbufferеijȣܰǶЭ + public int Position; // дλ + + // lengthһڻĴСLengthһÿЭЭݲֵֽ + + private Stack messageStarts = new Stack(); // ¼ЭbufferеʼλãǶ׽ṹ + + public MessageWriter(byte[] buffer) + { + this.Buffer = buffer; + this.Length = this.Buffer.Length; + } + + /// + public MessageWriter(int bufferSize) + { + this.Buffer = new byte[bufferSize]; + } + + public byte[] ToByteArray() + { + byte[] output = new byte[this.Length]; + System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); + return output; + } + + /// The option specifying how the message should be sent. + public static MessageWriter Get() + { + MessageWriter output = WriterPool.GetObject(); + output.Clear(); + + return output; + } + + /// + /// Ƿexpectedȵ + /// + /// + /// + public bool HasBytes(int expected) + { + return this.Length >= expected; + } + + /// + public void StartMessage() + { + var messageStart = this.Position; + messageStarts.Push(messageStart); + this.Buffer[messageStart] = 0; // length + this.Buffer[messageStart + 1] = 0; // length + this.Position += 2; // ushort length + } + + /// + public void EndMessage() + { + var lastMessageStart = messageStarts.Pop(); + ushort length = (ushort)(this.Position - lastMessageStart - 2); // Minus length + this.Buffer[lastMessageStart] = (byte)length; + this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); + } + + /// + /// ȡǰ༭messageصһ + /// + public void CancelMessage() + { + this.Position = this.messageStarts.Pop(); + this.Length = this.Position; + } + + public void Clear() + { + Array.Clear(this.Buffer, 0, this.Buffer.Length); + this.messageStarts.Clear(); + this.Length = 0; + this.Position = 0; + } + + /// + /// message + /// + public void Recycle() + { + this.Position = this.Length = 0; + WriterPool.PutObject(this); + } + +#region WriteMethods + + public void CopyFrom(MessageReader target) + { + int offset, length; + if (target.Tag == byte.MaxValue) + { + offset = target.Offset; + length = target.Length; + } + else + { + offset = target.Offset - 3; + length = target.Length + 3; + } + + System.Buffer.BlockCopy(target.Buffer, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(bool value) + { + this.Buffer[this.Position++] = (byte)(value ? 1 : 0); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(sbyte value) + { + this.Buffer[this.Position++] = (byte)value; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte value) + { + this.Buffer[this.Position++] = value; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(short value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(ushort value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(uint value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(int value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(ulong value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + this.Buffer[this.Position++] = (byte)(value >> 32); + this.Buffer[this.Position++] = (byte)(value >> 40); + this.Buffer[this.Position++] = (byte)(value >> 48); + this.Buffer[this.Position++] = (byte)(value >> 56); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(long value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + this.Buffer[this.Position++] = (byte)(value >> 32); + this.Buffer[this.Position++] = (byte)(value >> 40); + this.Buffer[this.Position++] = (byte)(value >> 48); + this.Buffer[this.Position++] = (byte)(value >> 56); + if (this.Position > this.Length) this.Length = this.Position; + } + + public unsafe void Write(float value) + { + fixed (byte* ptr = &this.Buffer[this.Position]) + { + byte* valuePtr = (byte*)&value; + + *ptr = *valuePtr; + *(ptr + 1) = *(valuePtr + 1); + *(ptr + 2) = *(valuePtr + 2); + *(ptr + 3) = *(valuePtr + 3); + } + + this.Position += 4; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(string value) + { + var bytes = UTF8Encoding.UTF8.GetBytes(value); + this.WritePacked(bytes.Length); + this.Write(bytes); + } + + public void WriteBytesAndSize(byte[] bytes) + { + this.WritePacked((uint)bytes.Length); + this.Write(bytes); + } + + public void WriteBytesAndSize(byte[] bytes, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, length); + } + + public void WriteBytesAndSize(byte[] bytes, int offset, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, offset, length); + } + + public void Write(byte[] bytes) + { + Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length); + this.Position += bytes.Length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte[] bytes, int offset, int length) + { + Array.Copy(bytes, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte[] bytes, int length) + { + Array.Copy(bytes, 0, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + /// + public void WritePacked(int value) + { + this.WritePacked((uint)value); + } + + /// + public void WritePacked(uint value) + { + do + { + byte b = (byte)(value & 0xFF); + if (value >= 0x80) + { + b |= 0x80; + } + + this.Write(b); + value >>= 7; + } while (value > 0); + } +#endregion + + public void Write(MessageWriter msg) + { + this.Write(msg.Buffer, 0, msg.Length); + } + + public unsafe static bool IsLittleEndian() + { + byte b; + unsafe + { + int i = 1; + byte* bp = (byte*)&i; + b = *bp; + } + + return b == 1; + } + } +} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs.meta new file mode 100644 index 0000000..ec9023e --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d9ee77993c8eff4ba8b0a9026505b40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/ObjectPool.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/ObjectPool.cs new file mode 100644 index 0000000..e42cdd6 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/ObjectPool.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace MultiplayerToolkit +{ + /// + /// A fairly simple object pool for items that will be created a lot. + /// + /// The type that is pooled. + /// + public sealed class ObjectPool where T : IRecyclable + { + private int numberCreated; + public int NumberCreated { get { return numberCreated; } } + + public int NumberInUse { get { return this.inuse.Count; } } + public int NumberNotInUse { get { return this.pool.Count; } } + public int Size { get { return this.NumberInUse + this.NumberNotInUse; } } + +#if HAZEL_BAG + private readonly ConcurrentBag pool = new ConcurrentBag(); +#else + private readonly List pool = new List(); +#endif + + // Unavailable objects + private readonly ConcurrentDictionary inuse = new ConcurrentDictionary(); + + /// + /// The generator for creating new objects. + /// + /// + private readonly Func objectFactory; + + /// + /// Internal constructor for our ObjectPool. + /// + internal ObjectPool(Func objectFactory) + { + this.objectFactory = objectFactory; + } + + /// + /// Returns a pooled object of type T, if none are available another is created. + /// + /// An instance of T. + internal T GetObject() + { +#if HAZEL_BAG + if (!pool.TryTake(out T item)) + { + Interlocked.Increment(ref numberCreated); + item = objectFactory.Invoke(); + } +#else + T item; + lock (this.pool) + { + if (this.pool.Count > 0) + { + var idx = this.pool.Count - 1; + item = this.pool[idx]; + this.pool.RemoveAt(idx); + } + else + { + Interlocked.Increment(ref numberCreated); + item = objectFactory.Invoke(); + } + } +#endif + + if (!inuse.TryAdd(item, true)) + { + throw new Exception("Duplicate pull " + typeof(T).Name); + } + + return item; + } + + /// + /// Returns an object to the pool. + /// + /// The item to return. + internal void PutObject(T item) + { + if (inuse.TryRemove(item, out bool b)) + { +#if HAZEL_BAG + pool.Add(item); +#else + lock (this.pool) + { + pool.Add(item); + } +#endif + } + else + { +#if DEBUG + throw new Exception("Duplicate add " + typeof(T).Name); +#endif + } + } + } +} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs deleted file mode 100644 index 3bd5db8..0000000 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs +++ /dev/null @@ -1,456 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; - -namespace MultiplayerToolkit -{ - public class MessageReader : IRecyclable - { - public static readonly ObjectPool ReaderPool = new ObjectPool(() => new MessageReader()); - - public byte[] Buffer; - public byte Tag; - - public int Length; - - public int BytesRemaining => this.Length - this.Position; - - private MessageReader Parent; - - public int Position - { - get { return this._position; } - set - { - this._position = value; - this.readHead = value ; - } - } - - private int _position; - private int readHead; - - public static MessageReader GetSized(int minSize) - { - var output = ReaderPool.GetObject(); - - if (output.Buffer == null || output.Buffer.Length < minSize) - { - output.Buffer = new byte[minSize]; - } - else - { - Array.Clear(output.Buffer, 0, output.Buffer.Length); - } - - output.Offset = 0; - output.Position = 0; - output.Tag = byte.MaxValue; - return output; - } - - public static MessageReader Get(byte[] buffer) - { - var output = ReaderPool.GetObject(); - - output.Buffer = buffer; - output.Offset = 0; - output.Position = 0; - output.Length = buffer.Length; - output.Tag = byte.MaxValue; - - return output; - } - - public static MessageReader CopyMessageIntoParent(MessageReader source) - { - var output = MessageReader.GetSized(source.Length + 3); - System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); - - output.Offset = 0; - output.Position = 0; - output.Length = source.Length + 3; - - return output; - } - - /// - /// 复制一份 - /// - /// - /// - public static MessageReader Get(MessageReader source) - { - var output = MessageReader.GetSized(source.Buffer.Length); - System.Buffer.BlockCopy(source.Buffer, 0, output.Buffer, 0, source.Buffer.Length); - - output.Offset = source.Offset; - - output._position = source._position; - output.readHead = source.readHead; - - output.Length = source.Length; - output.Tag = source.Tag; - - return output; - } - - public static MessageReader Get(byte[] buffer, int offset) - { - // Ensure there is at least a header - if (offset + 3 > buffer.Length) return null; - - var output = ReaderPool.GetObject(); - - output.Buffer = buffer; - output.Offset = offset; - output.Position = 0; - - output.Length = output.ReadUInt16(); - output.Tag = output.ReadByte(); - - output.Offset += 3; - output.Position = 0; - - return output; - } - - /// - /// Produces a MessageReader using the parent's buffer. This MessageReader should **NOT** be recycled. - /// - public MessageReader ReadMessage() - { - // Ensure there is at least a header - if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); - - var output = new MessageReader(); - - output.Parent = this; - output.Buffer = this.Buffer; - output.Offset = this.readHead; - output.Position = 0; - - output.Length = output.ReadUInt16(); - output.Tag = output.ReadByte(); - - output.Offset += 3; - output.Position = 0; - - if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); - - this.Position += output.Length + 3; - return output; - } - - /// - /// Produces a MessageReader with a new buffer. This MessageReader should be recycled. - /// - public MessageReader ReadMessageAsNewBuffer() - { - if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); - - var len = this.ReadUInt16(); - var tag = this.ReadByte(); - - if (this.BytesRemaining < len) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {len} of {this.BytesRemaining}"); - - var output = MessageReader.GetSized(len); - - Array.Copy(this.Buffer, this.readHead, output.Buffer, 0, len); - - output.Length = len; - output.Tag = tag; - - this.Position += output.Length; - return output; - } - - public MessageWriter StartWriter() - { - var output = new MessageWriter(this.Buffer); - output.Position = this.readHead; - return output; - } - - public MessageReader Duplicate() - { - var output = GetSized(this.Length); - Array.Copy(this.Buffer, this.Offset, output.Buffer, 0, this.Length); - output.Length = this.Length; - output.Offset = 0; - output.Position = 0; - - return output; - } - - public void RemoveMessage(MessageReader reader) - { - var temp = MessageReader.GetSized(reader.Buffer.Length); - try - { - var headerOffset = reader.Offset - 3; - var endOfMessage = reader.Offset + reader.Length; - var len = reader.Buffer.Length - endOfMessage; - - Array.Copy(reader.Buffer, endOfMessage, temp.Buffer, 0, len); - Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset, len); - - this.AdjustLength(reader.Offset, reader.Length + 3); - } - finally - { - temp.Recycle(); - } - } - - public void InsertMessage(MessageReader reader, MessageWriter writer) - { - var temp = MessageReader.GetSized(reader.Buffer.Length); - try - { - var headerOffset = reader.Offset - 3; - var startOfMessage = reader.Offset; - var len = reader.Buffer.Length - startOfMessage; - int writerOffset = 0; - //switch (writer.SendOption) - //{ - // case SendOption.Reliable: - // writerOffset = 3; - // break; - // case SendOption.None: - // writerOffset = 1; - // break; - //} - - //store the original buffer in temp - Array.Copy(reader.Buffer, headerOffset, temp.Buffer, 0, len); - - //put the contents of writer in at headerOffset - Array.Copy(writer.Buffer, writerOffset, this.Buffer, headerOffset, writer.Length-writerOffset); - - //put the original buffer in after that - Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset + (writer.Length-writerOffset), len - writer.Length); - - this.AdjustLength(-1 * reader.Offset , -1 * (writer.Length - writerOffset)); - } - finally - { - temp.Recycle(); - } - } - - private void AdjustLength(int offset, int amount) - { - if (this.readHead > offset) - { - this.Position -= amount; - } - - if (Parent != null) - { - var lengthOffset = this.Offset - 3; - var curLen = this.Buffer[lengthOffset] - | (this.Buffer[lengthOffset + 1] << 8); - - curLen -= amount; - this.Length -= amount; - - this.Buffer[lengthOffset] = (byte)curLen; - this.Buffer[lengthOffset + 1] = (byte)(this.Buffer[lengthOffset + 1] >> 8); - - Parent.AdjustLength(offset, amount); - } - } - - public void Recycle() - { - this.Parent = null; - ReaderPool.PutObject(this); - } - - #region Read Methods - public bool ReadBoolean() - { - byte val = this.FastByte(); - return val != 0; - } - - public sbyte ReadSByte() - { - return (sbyte)this.FastByte(); - } - - public byte ReadByte() - { - return this.FastByte(); - } - - public ushort ReadUInt16() - { - ushort output = - (ushort)(this.FastByte() - | this.FastByte() << 8); - return output; - } - - public short ReadInt16() - { - short output = - (short)(this.FastByte() - | this.FastByte() << 8); - return output; - } - - public uint ReadUInt32() - { - uint output = this.FastByte() - | (uint)this.FastByte() << 8 - | (uint)this.FastByte() << 16 - | (uint)this.FastByte() << 24; - - return output; - } - - public int ReadInt32() - { - int output = this.FastByte() - | this.FastByte() << 8 - | this.FastByte() << 16 - | this.FastByte() << 24; - - return output; - } - - public ulong ReadUInt64() - { - ulong output = (ulong)this.FastByte() - | (ulong)this.FastByte() << 8 - | (ulong)this.FastByte() << 16 - | (ulong)this.FastByte() << 24 - | (ulong)this.FastByte() << 32 - | (ulong)this.FastByte() << 40 - | (ulong)this.FastByte() << 48 - | (ulong)this.FastByte() << 56; - - return output; - } - - public long ReadInt64() - { - long output = (long)this.FastByte() - | (long)this.FastByte() << 8 - | (long)this.FastByte() << 16 - | (long)this.FastByte() << 24 - | (long)this.FastByte() << 32 - | (long)this.FastByte() << 40 - | (long)this.FastByte() << 48 - | (long)this.FastByte() << 56; - - return output; - } - - public unsafe float ReadSingle() - { - float output = 0; - fixed (byte* bufPtr = &this.Buffer[this.readHead]) - { - byte* outPtr = (byte*)&output; - - *outPtr = *bufPtr; - *(outPtr + 1) = *(bufPtr + 1); - *(outPtr + 2) = *(bufPtr + 2); - *(outPtr + 3) = *(bufPtr + 3); - } - - this.Position += 4; - return output; - } - - public string ReadString() - { - int len = this.ReadPackedInt32(); - if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); - - string output = UTF8Encoding.UTF8.GetString(this.Buffer, this.readHead, len); - - this.Position += len; - return output; - } - - public byte[] ReadBytesAndSize() - { - int len = this.ReadPackedInt32(); - if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); - - return this.ReadBytes(len); - } - - public byte[] ReadBytes(int length) - { - if (this.BytesRemaining < length) throw new InvalidDataException($"Read length is longer than message length: {length} of {this.BytesRemaining}"); - - byte[] output = new byte[length]; - Array.Copy(this.Buffer, this.readHead, output, 0, output.Length); - this.Position += output.Length; - return output; - } - - /// - public int ReadPackedInt32() - { - return (int)this.ReadPackedUInt32(); - } - - /// - public uint ReadPackedUInt32() - { - bool readMore = true; - int shift = 0; - uint output = 0; - - while (readMore) - { - if (this.BytesRemaining < 1) throw new InvalidDataException($"Read length is longer than message length."); - - byte b = this.ReadByte(); - if (b >= 0x80) - { - readMore = true; - b ^= 0x80; - } - else - { - readMore = false; - } - - output |= (uint)(b << shift); - shift += 7; - } - - return output; - } - #endregion - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte FastByte() - { - this._position++; - return this.Buffer[this.readHead++]; - } - - public unsafe static bool IsLittleEndian() - { - byte b; - unsafe - { - int i = 1; - byte* bp = (byte*)&i; - b = *bp; - } - - return b == 1; - } - } -} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs.meta deleted file mode 100644 index 2f470c6..0000000 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 35714cf36eee0fc458f427f0a6cea532 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs deleted file mode 100644 index 226a2d4..0000000 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace MultiplayerToolkit -{ - - /// - /// AmongUsùȥ˰ͷش - /// ṹ - /// ------------------------------------- - /// 2bytes(ushort) Эij - /// Э - /// ------------------------------------- - /// - public class MessageWriter : IRecyclable - { - public static int BufferSize = 64000; - public static readonly ObjectPool WriterPool = new ObjectPool(() => new MessageWriter(BufferSize)); - - public byte[] Buffer; - public int Length; // Эܳ - public int Position; // дλ - - private Stack messageStarts = new Stack(); // ¼ЭbufferеʼλãǶ׽ṹ - - public MessageWriter(byte[] buffer) - { - this.Buffer = buffer; - this.Length = this.Buffer.Length; - } - - /// - public MessageWriter(int bufferSize) - { - this.Buffer = new byte[bufferSize]; - } - - public byte[] ToByteArray() - { - byte[] output = new byte[this.Length]; - System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); - return output; - } - - /// The option specifying how the message should be sent. - public static MessageWriter Get() - { - MessageWriter output = WriterPool.GetObject(); - output.Clear(); - - return output; - } - - public bool HasBytes(int expected) - { - return this.Length >= 3 + expected; - } - - /// - public void StartMessage(/*byte typeFlag*/) - { - var messageStart = this.Position; - messageStarts.Push(messageStart); - this.Buffer[messageStart] = 0; // length - this.Buffer[messageStart + 1] = 0; // length - this.Position += 2; // ushort length - //this.Write(typeFlag); - } - - /// - public void EndMessage() - { - var lastMessageStart = messageStarts.Pop(); - ushort length = (ushort)(this.Position - lastMessageStart - /*3*/2); // Minus length and type byte - this.Buffer[lastMessageStart] = (byte)length; - this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); - } - - /// - /// ȡǰ༭messageصһ - /// - public void CancelMessage() - { - this.Position = this.messageStarts.Pop(); - this.Length = this.Position; - } - - public void Clear() - { - Array.Clear(this.Buffer, 0, this.Buffer.Length); - this.messageStarts.Clear(); - this.Length = 0; - this.Position = 0; - } - - /// - /// message - /// - public void Recycle() - { - this.Position = this.Length = 0; - WriterPool.PutObject(this); - } - -#region WriteMethods - - public void CopyFrom(MessageReader target) - { - int offset, length; - if (target.Tag == byte.MaxValue) - { - offset = target.Offset; - length = target.Length; - } - else - { - offset = target.Offset - 3; - length = target.Length + 3; - } - - System.Buffer.BlockCopy(target.Buffer, offset, this.Buffer, this.Position, length); - this.Position += length; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(bool value) - { - this.Buffer[this.Position++] = (byte)(value ? 1 : 0); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(sbyte value) - { - this.Buffer[this.Position++] = (byte)value; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte value) - { - this.Buffer[this.Position++] = value; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(short value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(ushort value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(uint value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - this.Buffer[this.Position++] = (byte)(value >> 16); - this.Buffer[this.Position++] = (byte)(value >> 24); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(int value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - this.Buffer[this.Position++] = (byte)(value >> 16); - this.Buffer[this.Position++] = (byte)(value >> 24); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(ulong value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - this.Buffer[this.Position++] = (byte)(value >> 16); - this.Buffer[this.Position++] = (byte)(value >> 24); - this.Buffer[this.Position++] = (byte)(value >> 32); - this.Buffer[this.Position++] = (byte)(value >> 40); - this.Buffer[this.Position++] = (byte)(value >> 48); - this.Buffer[this.Position++] = (byte)(value >> 56); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(long value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - this.Buffer[this.Position++] = (byte)(value >> 16); - this.Buffer[this.Position++] = (byte)(value >> 24); - this.Buffer[this.Position++] = (byte)(value >> 32); - this.Buffer[this.Position++] = (byte)(value >> 40); - this.Buffer[this.Position++] = (byte)(value >> 48); - this.Buffer[this.Position++] = (byte)(value >> 56); - if (this.Position > this.Length) this.Length = this.Position; - } - - public unsafe void Write(float value) - { - fixed (byte* ptr = &this.Buffer[this.Position]) - { - byte* valuePtr = (byte*)&value; - - *ptr = *valuePtr; - *(ptr + 1) = *(valuePtr + 1); - *(ptr + 2) = *(valuePtr + 2); - *(ptr + 3) = *(valuePtr + 3); - } - - this.Position += 4; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(string value) - { - var bytes = UTF8Encoding.UTF8.GetBytes(value); - this.WritePacked(bytes.Length); - this.Write(bytes); - } - - public void WriteBytesAndSize(byte[] bytes) - { - this.WritePacked((uint)bytes.Length); - this.Write(bytes); - } - - public void WriteBytesAndSize(byte[] bytes, int length) - { - this.WritePacked((uint)length); - this.Write(bytes, length); - } - - public void WriteBytesAndSize(byte[] bytes, int offset, int length) - { - this.WritePacked((uint)length); - this.Write(bytes, offset, length); - } - - public void Write(byte[] bytes) - { - Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length); - this.Position += bytes.Length; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte[] bytes, int offset, int length) - { - Array.Copy(bytes, offset, this.Buffer, this.Position, length); - this.Position += length; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte[] bytes, int length) - { - Array.Copy(bytes, 0, this.Buffer, this.Position, length); - this.Position += length; - if (this.Position > this.Length) this.Length = this.Position; - } - - /// - public void WritePacked(int value) - { - this.WritePacked((uint)value); - } - - /// - public void WritePacked(uint value) - { - do - { - byte b = (byte)(value & 0xFF); - if (value >= 0x80) - { - b |= 0x80; - } - - this.Write(b); - value >>= 7; - } while (value > 0); - } -#endregion - - public void Write(MessageWriter msg) - { - this.Write(msg.Buffer, 0, msg.Length); - } - - public unsafe static bool IsLittleEndian() - { - byte b; - unsafe - { - int i = 1; - byte* bp = (byte*)&i; - b = *bp; - } - - return b == 1; - } - } -} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs.meta deleted file mode 100644 index ec9023e..0000000 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8d9ee77993c8eff4ba8b0a9026505b40 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/ObjectPool.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/ObjectPool.cs deleted file mode 100644 index e42cdd6..0000000 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/ObjectPool.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; - -namespace MultiplayerToolkit -{ - /// - /// A fairly simple object pool for items that will be created a lot. - /// - /// The type that is pooled. - /// - public sealed class ObjectPool where T : IRecyclable - { - private int numberCreated; - public int NumberCreated { get { return numberCreated; } } - - public int NumberInUse { get { return this.inuse.Count; } } - public int NumberNotInUse { get { return this.pool.Count; } } - public int Size { get { return this.NumberInUse + this.NumberNotInUse; } } - -#if HAZEL_BAG - private readonly ConcurrentBag pool = new ConcurrentBag(); -#else - private readonly List pool = new List(); -#endif - - // Unavailable objects - private readonly ConcurrentDictionary inuse = new ConcurrentDictionary(); - - /// - /// The generator for creating new objects. - /// - /// - private readonly Func objectFactory; - - /// - /// Internal constructor for our ObjectPool. - /// - internal ObjectPool(Func objectFactory) - { - this.objectFactory = objectFactory; - } - - /// - /// Returns a pooled object of type T, if none are available another is created. - /// - /// An instance of T. - internal T GetObject() - { -#if HAZEL_BAG - if (!pool.TryTake(out T item)) - { - Interlocked.Increment(ref numberCreated); - item = objectFactory.Invoke(); - } -#else - T item; - lock (this.pool) - { - if (this.pool.Count > 0) - { - var idx = this.pool.Count - 1; - item = this.pool[idx]; - this.pool.RemoveAt(idx); - } - else - { - Interlocked.Increment(ref numberCreated); - item = objectFactory.Invoke(); - } - } -#endif - - if (!inuse.TryAdd(item, true)) - { - throw new Exception("Duplicate pull " + typeof(T).Name); - } - - return item; - } - - /// - /// Returns an object to the pool. - /// - /// The item to return. - internal void PutObject(T item) - { - if (inuse.TryRemove(item, out bool b)) - { -#if HAZEL_BAG - pool.Add(item); -#else - lock (this.pool) - { - pool.Add(item); - } -#endif - } - else - { -#if DEBUG - throw new Exception("Duplicate add " + typeof(T).Name); -#endif - } - } - } -} diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta new file mode 100644 index 0000000..e1988f5 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1bb97683c50825f4499202eb0d241254 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs new file mode 100644 index 0000000..ca028cf --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs @@ -0,0 +1,11 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + + +public class MessageUnitTests +{ + + + +} \ No newline at end of file diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta new file mode 100644 index 0000000..a5ad157 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c70f76fcb12ced489b49c7ac64eda2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Projects/Message/Message/.gitignore b/Projects/Message/Message/.gitignore new file mode 100644 index 0000000..9bf6cfb --- /dev/null +++ b/Projects/Message/Message/.gitignore @@ -0,0 +1,194 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs/ + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +#Sandcastle generated documentation +[Hh]elp/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml +*.ide diff --git a/Projects/Message/Message/IRecyclable.cs b/Projects/Message/Message/IRecyclable.cs new file mode 100644 index 0000000..17d3104 --- /dev/null +++ b/Projects/Message/Message/IRecyclable.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MultiplayerToolkit +{ + /// + /// Interface for all items that can be returned to an object pool. + /// + /// + public interface IRecyclable + { + /// + /// Returns this object back to the object pool. + /// + /// + /// + /// Calling this when you are done with the object returns the object back to a pool in order to be reused. + /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this. + /// + /// + /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are + /// completely finished with the object as the contents can be overwritten at any point after. + /// + /// + void Recycle(); + } +} diff --git a/Projects/Message/Message/Message.csproj b/Projects/Message/Message/Message.csproj new file mode 100644 index 0000000..f2f13d5 --- /dev/null +++ b/Projects/Message/Message/Message.csproj @@ -0,0 +1,74 @@ + + + + + + Debug + AnyCPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC} + Library + Properties + Message + Message + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + + + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + + \ No newline at end of file diff --git a/Projects/Message/Message/Message.lutconfig b/Projects/Message/Message/Message.lutconfig new file mode 100644 index 0000000..596a860 --- /dev/null +++ b/Projects/Message/Message/Message.lutconfig @@ -0,0 +1,6 @@ + + + true + true + 180000 + \ No newline at end of file diff --git a/Projects/Message/Message/Message.sln b/Projects/Message/Message/Message.sln new file mode 100644 index 0000000..3381718 --- /dev/null +++ b/Projects/Message/Message/Message.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Message", "Message.csproj", "{AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E601FAC4-CE34-4006-A518-979962273BFB} + EndGlobalSection +EndGlobal diff --git a/Projects/Message/Message/MessageReader.cs b/Projects/Message/Message/MessageReader.cs new file mode 100644 index 0000000..7ec134b --- /dev/null +++ b/Projects/Message/Message/MessageReader.cs @@ -0,0 +1,469 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace MultiplayerToolkit +{ + /// + /// + /// + public class MessageReader : IRecyclable + { + public static readonly ObjectPool ReaderPool = new ObjectPool(() => new MessageReader()); + + public byte[] Buffer; // √ 缓冲区,会被子协议共享这个缓冲区 + public byte Tag; // √ 协议名 + + public int Length; // √ 协议长度,对应的是MessageWriter里写入的2bytes包长度(不含length和tag) + public int Offset; // √ length和tag后面的位置(在Buffer中的位置),不是游标,不会随读取发生改变。会影响readHead的值 + + //数据位于Buffer中Offset索引开始Length长度,协议位于Buffer中Offset-3开始的Length+3长度 + + public int BytesRemaining => this.Length - this.Position; + + private MessageReader Parent; // 保存父协议索引,和父协议共享父协议的Buffer + + public int Position + { + get { return this._position; } + set + { + this._position = value; // value是 + this.readHead = value + Offset; + } + } + + private int _position; // √ 读取游标,相对于协议内容部分的偏移,从0开始,子协议也是从0开始,不是在Buffer中的索引 + private int readHead; // √ 读取游标,协议头部在Buffer中的位置,参考ReadMessage() + + public static MessageReader GetSized(int minSize) + { + var output = ReaderPool.GetObject(); + + if (output.Buffer == null || output.Buffer.Length < minSize) + { + output.Buffer = new byte[minSize]; + } + else + { + Array.Clear(output.Buffer, 0, output.Buffer.Length); + } + + output.Offset = 0; + output.Position = 0; + output.Tag = byte.MaxValue; + return output; + } + + public static MessageReader Get(byte[] buffer) + { + var output = ReaderPool.GetObject(); + + output.Buffer = buffer; + output.Offset = 0; + output.Position = 0; + output.Length = buffer.Length; + output.Tag = byte.MaxValue; + + return output; + } + + /// + /// 创建一个新的父message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// + /// + /// + public static MessageReader CopyMessageIntoParent(MessageReader source) + { + var output = MessageReader.GetSized(source.Length + 3); //3=2bytes(length) + 1byte(tag) + System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); + + output.Offset = 0; + output.Position = 0; + output.Length = source.Length + 3; + + return output; + } + + /// + /// 完全复制 一份 + /// + /// + /// + public static MessageReader Get(MessageReader source) + { + var output = MessageReader.GetSized(source.Buffer.Length); + System.Buffer.BlockCopy(source.Buffer, 0, output.Buffer, 0, source.Buffer.Length); + + output.Offset = source.Offset; + + output._position = source._position; + output.readHead = source.readHead; + + output.Length = source.Length; + output.Tag = source.Tag; + + return output; + } + + public static MessageReader Get(byte[] buffer, int offset) + { + // Ensure there is at least a header + if (offset + 3 > buffer.Length) return null; + + var output = ReaderPool.GetObject(); + + output.Buffer = buffer; + output.Offset = offset; + output.Position = 0; + + output.Length = output.ReadUInt16(); // 读到的值为length + output.Tag = output.ReadByte(); + + output.Offset += 3; + output.Position = 0; + + return output; + } + + /// + /// Produces a MessageReader using the parent's buffer. This MessageReader should **NOT** be recycled. + /// + public MessageReader ReadMessage() + { + // Ensure there is at least a header + if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); + + MessageReader output = new MessageReader(); + + output.Parent = this; + output.Buffer = this.Buffer; + output.Offset = this.readHead; // 下面会读取 + output.Position = 0; + + // 读length和tag的值并保存在字段里 + output.Length = output.ReadUInt16(); + output.Tag = output.ReadByte(); + + // Offset, readHead齐步走,移到Length和Tag后面,_position=0 + output.Offset += 3; // 3=length+tag + output.Position = 0; + + if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); + + this.Position += output.Length + 3; //跳过整个子协议 + return output; + } + + /// + /// Produces a MessageReader with a new buffer. This MessageReader should be recycled. + /// + public MessageReader ReadMessageAsNewBuffer() + { + if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); + + var len = this.ReadUInt16(); + var tag = this.ReadByte(); + + if (this.BytesRemaining < len) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {len} of {this.BytesRemaining}"); + + var output = MessageReader.GetSized(len); + + Array.Copy(this.Buffer, this.readHead, output.Buffer, 0, len); + + output.Length = len; + output.Tag = tag; + + this.Position += output.Length; + return output; + } + + public MessageWriter StartWriter() + { + var output = new MessageWriter(this.Buffer); + output.Position = this.readHead; + return output; + } + + public MessageReader Duplicate() + { + var output = GetSized(this.Length); + Array.Copy(this.Buffer, this.Offset, output.Buffer, 0, this.Length); + output.Length = this.Length; + output.Offset = 0; + output.Position = 0; + + return output; + } + + public void RemoveMessage(MessageReader reader) + { + var temp = MessageReader.GetSized(reader.Buffer.Length); + try + { + var headerOffset = reader.Offset - 3; + var endOfMessage = reader.Offset + reader.Length; + var len = reader.Buffer.Length - endOfMessage; + + Array.Copy(reader.Buffer, endOfMessage, temp.Buffer, 0, len); + Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset, len); + + this.AdjustLength(reader.Offset, reader.Length + 3); + } + finally + { + temp.Recycle(); + } + } + + //public void InsertMessage(MessageReader reader, MessageWriter writer) + //{ + // var temp = MessageReader.GetSized(reader.Buffer.Length); + // try + // { + // var headerOffset = reader.Offset - 3; + // var startOfMessage = reader.Offset; + // var len = reader.Buffer.Length - startOfMessage; + // int writerOffset = 3; + // switch (writer.SendOption) + // { + // case SendOption.Reliable: + // writerOffset = 3; + // break; + // case SendOption.None: + // writerOffset = 1; + // break; + // } + + // //store the original buffer in temp + // Array.Copy(reader.Buffer, headerOffset, temp.Buffer, 0, len); + + // //put the contents of writer in at headerOffset + // Array.Copy(writer.Buffer, writerOffset, this.Buffer, headerOffset, writer.Length - writerOffset); + + // //put the original buffer in after that + // Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset + (writer.Length - writerOffset), len - writer.Length); + + // this.AdjustLength(-1 * reader.Offset, -1 * (writer.Length - writerOffset)); + // } + // finally + // { + // temp.Recycle(); + // } + //} + + private void AdjustLength(int offset, int amount) + { + if (this.readHead > offset) + { + this.Position -= amount; + } + + if (Parent != null) + { + var lengthOffset = this.Offset - 3; + var curLen = this.Buffer[lengthOffset] + | (this.Buffer[lengthOffset + 1] << 8); + + curLen -= amount; + this.Length -= amount; + + this.Buffer[lengthOffset] = (byte)curLen; + this.Buffer[lengthOffset + 1] = (byte)(this.Buffer[lengthOffset + 1] >> 8); + + Parent.AdjustLength(offset, amount); + } + } + + public void Recycle() + { + this.Parent = null; + ReaderPool.PutObject(this); + } + + #region Read Methods + public bool ReadBoolean() + { + byte val = this.FastByte(); + return val != 0; + } + + public sbyte ReadSByte() + { + return (sbyte)this.FastByte(); + } + + public byte ReadByte() + { + return this.FastByte(); + } + + public ushort ReadUInt16() + { + ushort output = + (ushort)(this.FastByte() + | this.FastByte() << 8); + return output; + } + + public short ReadInt16() + { + short output = + (short)(this.FastByte() + | this.FastByte() << 8); + return output; + } + + public uint ReadUInt32() + { + uint output = this.FastByte() + | (uint)this.FastByte() << 8 + | (uint)this.FastByte() << 16 + | (uint)this.FastByte() << 24; + + return output; + } + + public int ReadInt32() + { + int output = this.FastByte() + | this.FastByte() << 8 + | this.FastByte() << 16 + | this.FastByte() << 24; + + return output; + } + + public ulong ReadUInt64() + { + ulong output = (ulong)this.FastByte() + | (ulong)this.FastByte() << 8 + | (ulong)this.FastByte() << 16 + | (ulong)this.FastByte() << 24 + | (ulong)this.FastByte() << 32 + | (ulong)this.FastByte() << 40 + | (ulong)this.FastByte() << 48 + | (ulong)this.FastByte() << 56; + + return output; + } + + public long ReadInt64() + { + long output = (long)this.FastByte() + | (long)this.FastByte() << 8 + | (long)this.FastByte() << 16 + | (long)this.FastByte() << 24 + | (long)this.FastByte() << 32 + | (long)this.FastByte() << 40 + | (long)this.FastByte() << 48 + | (long)this.FastByte() << 56; + + return output; + } + + public unsafe float ReadSingle() + { + float output = 0; + fixed (byte* bufPtr = &this.Buffer[this.readHead]) + { + byte* outPtr = (byte*)&output; + + *outPtr = *bufPtr; + *(outPtr + 1) = *(bufPtr + 1); + *(outPtr + 2) = *(bufPtr + 2); + *(outPtr + 3) = *(bufPtr + 3); + } + + this.Position += 4; + return output; + } + + public string ReadString() + { + int len = this.ReadPackedInt32(); + if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); + + string output = UTF8Encoding.UTF8.GetString(this.Buffer, this.readHead, len); + + this.Position += len; + return output; + } + + public byte[] ReadBytesAndSize() + { + int len = this.ReadPackedInt32(); + if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); + + return this.ReadBytes(len); + } + + public byte[] ReadBytes(int length) + { + if (this.BytesRemaining < length) throw new InvalidDataException($"Read length is longer than message length: {length} of {this.BytesRemaining}"); + + byte[] output = new byte[length]; + Array.Copy(this.Buffer, this.readHead, output, 0, output.Length); + this.Position += output.Length; + return output; + } + + /// + public int ReadPackedInt32() + { + return (int)this.ReadPackedUInt32(); + } + + /// + public uint ReadPackedUInt32() + { + bool readMore = true; + int shift = 0; + uint output = 0; + + while (readMore) + { + if (this.BytesRemaining < 1) throw new InvalidDataException($"Read length is longer than message length."); + + byte b = this.ReadByte(); + if (b >= 0x80) + { + readMore = true; + b ^= 0x80; + } + else + { + readMore = false; + } + + output |= (uint)(b << shift); + shift += 7; + } + + return output; + } + #endregion + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte FastByte() + { + this._position++; + return this.Buffer[this.readHead++]; + } + + public unsafe static bool IsLittleEndian() + { + byte b; + unsafe + { + int i = 1; + byte* bp = (byte*)&i; + b = *bp; + } + + return b == 1; + } + } +} diff --git a/Projects/Message/Message/MessageWriter.cs b/Projects/Message/Message/MessageWriter.cs new file mode 100644 index 0000000..7646135 --- /dev/null +++ b/Projects/Message/Message/MessageWriter.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace MultiplayerToolkit +{ + + /// + /// AmongUsùȥ˰ͷش + /// ṹ + /// ------------------------------------- + /// 2bytes (ushort)Эijȣ2bytes + /// ------------------------------------- + /// 1byte ЭͣЭ0ԶЭ1 + /// (2byte) (ԶЭ飬ṩһmodţSteam.UGC.Itemidulong 8bytesе󣬻ţ֧65535mod) + /// 2byte (ushort)ЭID 0-65535 + /// + /// ------------------------------------- + /// + public class MessageWriter : IRecyclable + { + public const int BYTE_COUNT_OF_LENGTH = 2; // ռ2 bytes + public static int BufferSize = 64000; // Э֧64000ֽڣ62.5KB + public static readonly ObjectPool WriterPool = new ObjectPool(() => new MessageWriter(BufferSize)); + + public byte[] Buffer; + public int Length; // ЧbufferеijȣܰǶЭ + public int Position; // дλ + + // lengthһڻĴСLengthһÿЭЭݲֵֽ + + private Stack messageStarts = new Stack(); // ¼ЭbufferеʼλãǶ׽ṹ + + public MessageWriter(byte[] buffer) + { + this.Buffer = buffer; + this.Length = this.Buffer.Length; + } + + /// + public MessageWriter(int bufferSize) + { + this.Buffer = new byte[bufferSize]; + } + + public byte[] ToByteArray() + { + byte[] output = new byte[this.Length]; + System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); + return output; + } + + /// The option specifying how the message should be sent. + public static MessageWriter Get() + { + MessageWriter output = WriterPool.GetObject(); + output.Clear(); + + return output; + } + + /// + /// Ƿexpectedȵ + /// + /// + /// + public bool HasBytes(int expected) + { + return this.Length >= expected; + } + + /// + public void StartMessage() + { + var messageStart = this.Position; + messageStarts.Push(messageStart); + this.Buffer[messageStart] = 0; // length + this.Buffer[messageStart + 1] = 0; // length + this.Position += 2; // ushort length + } + + /// + public void EndMessage() + { + var lastMessageStart = messageStarts.Pop(); + ushort length = (ushort)(this.Position - lastMessageStart - 2); // Minus length + this.Buffer[lastMessageStart] = (byte)length; + this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); + } + + /// + /// ȡǰ༭messageصһ + /// + public void CancelMessage() + { + this.Position = this.messageStarts.Pop(); + this.Length = this.Position; + } + + public void Clear() + { + Array.Clear(this.Buffer, 0, this.Buffer.Length); + this.messageStarts.Clear(); + this.Length = 0; + this.Position = 0; + } + + /// + /// message + /// + public void Recycle() + { + this.Position = this.Length = 0; + WriterPool.PutObject(this); + } + +#region WriteMethods + + public void CopyFrom(MessageReader target) + { + int offset, length; + if (target.Tag == byte.MaxValue) + { + offset = target.Offset; + length = target.Length; + } + else + { + offset = target.Offset - 3; + length = target.Length + 3; + } + + System.Buffer.BlockCopy(target.Buffer, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(bool value) + { + this.Buffer[this.Position++] = (byte)(value ? 1 : 0); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(sbyte value) + { + this.Buffer[this.Position++] = (byte)value; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte value) + { + this.Buffer[this.Position++] = value; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(short value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(ushort value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(uint value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(int value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(ulong value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + this.Buffer[this.Position++] = (byte)(value >> 32); + this.Buffer[this.Position++] = (byte)(value >> 40); + this.Buffer[this.Position++] = (byte)(value >> 48); + this.Buffer[this.Position++] = (byte)(value >> 56); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(long value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + this.Buffer[this.Position++] = (byte)(value >> 32); + this.Buffer[this.Position++] = (byte)(value >> 40); + this.Buffer[this.Position++] = (byte)(value >> 48); + this.Buffer[this.Position++] = (byte)(value >> 56); + if (this.Position > this.Length) this.Length = this.Position; + } + + public unsafe void Write(float value) + { + fixed (byte* ptr = &this.Buffer[this.Position]) + { + byte* valuePtr = (byte*)&value; + + *ptr = *valuePtr; + *(ptr + 1) = *(valuePtr + 1); + *(ptr + 2) = *(valuePtr + 2); + *(ptr + 3) = *(valuePtr + 3); + } + + this.Position += 4; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(string value) + { + var bytes = UTF8Encoding.UTF8.GetBytes(value); + this.WritePacked(bytes.Length); + this.Write(bytes); + } + + public void WriteBytesAndSize(byte[] bytes) + { + this.WritePacked((uint)bytes.Length); + this.Write(bytes); + } + + public void WriteBytesAndSize(byte[] bytes, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, length); + } + + public void WriteBytesAndSize(byte[] bytes, int offset, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, offset, length); + } + + public void Write(byte[] bytes) + { + Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length); + this.Position += bytes.Length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte[] bytes, int offset, int length) + { + Array.Copy(bytes, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte[] bytes, int length) + { + Array.Copy(bytes, 0, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + /// + public void WritePacked(int value) + { + this.WritePacked((uint)value); + } + + /// + public void WritePacked(uint value) + { + do + { + byte b = (byte)(value & 0xFF); + if (value >= 0x80) + { + b |= 0x80; + } + + this.Write(b); + value >>= 7; + } while (value > 0); + } +#endregion + + public void Write(MessageWriter msg) + { + this.Write(msg.Buffer, 0, msg.Length); + } + + public unsafe static bool IsLittleEndian() + { + byte b; + unsafe + { + int i = 1; + byte* bp = (byte*)&i; + b = *bp; + } + + return b == 1; + } + } +} diff --git a/Projects/Message/Message/ObjectPool.cs b/Projects/Message/Message/ObjectPool.cs new file mode 100644 index 0000000..e42cdd6 --- /dev/null +++ b/Projects/Message/Message/ObjectPool.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace MultiplayerToolkit +{ + /// + /// A fairly simple object pool for items that will be created a lot. + /// + /// The type that is pooled. + /// + public sealed class ObjectPool where T : IRecyclable + { + private int numberCreated; + public int NumberCreated { get { return numberCreated; } } + + public int NumberInUse { get { return this.inuse.Count; } } + public int NumberNotInUse { get { return this.pool.Count; } } + public int Size { get { return this.NumberInUse + this.NumberNotInUse; } } + +#if HAZEL_BAG + private readonly ConcurrentBag pool = new ConcurrentBag(); +#else + private readonly List pool = new List(); +#endif + + // Unavailable objects + private readonly ConcurrentDictionary inuse = new ConcurrentDictionary(); + + /// + /// The generator for creating new objects. + /// + /// + private readonly Func objectFactory; + + /// + /// Internal constructor for our ObjectPool. + /// + internal ObjectPool(Func objectFactory) + { + this.objectFactory = objectFactory; + } + + /// + /// Returns a pooled object of type T, if none are available another is created. + /// + /// An instance of T. + internal T GetObject() + { +#if HAZEL_BAG + if (!pool.TryTake(out T item)) + { + Interlocked.Increment(ref numberCreated); + item = objectFactory.Invoke(); + } +#else + T item; + lock (this.pool) + { + if (this.pool.Count > 0) + { + var idx = this.pool.Count - 1; + item = this.pool[idx]; + this.pool.RemoveAt(idx); + } + else + { + Interlocked.Increment(ref numberCreated); + item = objectFactory.Invoke(); + } + } +#endif + + if (!inuse.TryAdd(item, true)) + { + throw new Exception("Duplicate pull " + typeof(T).Name); + } + + return item; + } + + /// + /// Returns an object to the pool. + /// + /// The item to return. + internal void PutObject(T item) + { + if (inuse.TryRemove(item, out bool b)) + { +#if HAZEL_BAG + pool.Add(item); +#else + lock (this.pool) + { + pool.Add(item); + } +#endif + } + else + { +#if DEBUG + throw new Exception("Duplicate add " + typeof(T).Name); +#endif + } + } + } +} diff --git a/Projects/Message/Message/Properties/AssemblyInfo.cs b/Projects/Message/Message/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9f03f92 --- /dev/null +++ b/Projects/Message/Message/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Message")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Message")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("aadabfad-53fb-43da-bac8-83d11ca36dec")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Projects/Message/Message/UnitTest1.cs b/Projects/Message/Message/UnitTest1.cs new file mode 100644 index 0000000..2e02c6e --- /dev/null +++ b/Projects/Message/Message/UnitTest1.cs @@ -0,0 +1,16 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Message +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + + Assert.AreEqual(1, 1); + } + } +} diff --git a/Projects/Message/Message/packages.config b/Projects/Message/Message/packages.config new file mode 100644 index 0000000..54eb31a --- /dev/null +++ b/Projects/Message/Message/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs b/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs index 6cf4ba8..7a073ae 100644 --- a/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs +++ b/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs @@ -125,7 +125,7 @@ namespace Hazel.UnitTests reader.Length = msg.Length; var zero = reader.ReadMessage(); - + var one = reader.ReadMessage(); var two = one.ReadMessage(); var three = two.ReadMessage(); @@ -218,6 +218,27 @@ namespace Hazel.UnitTests const byte Test5 = 55; const byte TestInsert = 66; +/* +2 length=1 +1 tag=0 +1 Test0=11 + +2 length=11 +1 tag=12 + 2 length=8 + 1 tag=23 + 2 length=1 + 1 tag=34 + 1 Test3=33 + + 2 length=1 + 1 tag=45 + 1 Test4=44 +2 length=1 +1 tag=56 +1 Test5=55 +*/ + var msg = new MessageWriter(2048); msg.StartMessage(0); msg.Write(Test0); @@ -247,6 +268,12 @@ namespace Hazel.UnitTests writer.StartMessage(5); writer.Write(TestInsert); writer.EndMessage(); +/* +1 SendOption.None +2 length=2 +1 tag=5 +1 TestInsert=66 + */ reader.ReadMessage(); var one = reader.ReadMessage(); diff --git a/Tools/Hazel-Networking/Hazel.lutconfig b/Tools/Hazel-Networking/Hazel.lutconfig new file mode 100644 index 0000000..87625ea --- /dev/null +++ b/Tools/Hazel-Networking/Hazel.lutconfig @@ -0,0 +1,6 @@ + + ..\..\ + true + true + 180000 + \ No newline at end of file diff --git a/Tools/Hazel-Networking/Hazel/MessageReader.cs b/Tools/Hazel-Networking/Hazel/MessageReader.cs index bd3b0d8..b6e35de 100644 --- a/Tools/Hazel-Networking/Hazel/MessageReader.cs +++ b/Tools/Hazel-Networking/Hazel/MessageReader.cs @@ -1,4 +1,5 @@ -using System; +#define UNIT_TEST +using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -6,32 +7,37 @@ using System.Text; namespace Hazel { + /// + /// + /// public class MessageReader : IRecyclable { public static readonly ObjectPool ReaderPool = new ObjectPool(() => new MessageReader()); - public byte[] Buffer; - public byte Tag; + public byte[] Buffer; // √ 缓冲区,会被子协议共享这个缓冲区 + public byte Tag; // √ 协议名 - public int Length; // 总长度 - public int Offset; // length和tag后面 + public int Length; // √ 协议长度,对应的是MessageWriter里写入的2bytes包长度(不含length和tag) + public int Offset; // √ length和tag后面的位置(在Buffer中的位置),不是游标,不会随读取发生改变。会影响readHead的值 + + //数据位于Buffer中Offset索引开始Length长度,协议位于Buffer中Offset-3开始的Length+3长度 public int BytesRemaining => this.Length - this.Position; - private MessageReader Parent; + private MessageReader Parent; // 保存父协议索引,和父协议共享父协议的Buffer public int Position { get { return this._position; } set { - this._position = value; + this._position = value; // value是 this.readHead = value + Offset; } } - private int _position; - private int readHead; + private int _position; // √ 读取游标,相对于协议内容部分的偏移,从0开始,子协议也是从0开始,不是在Buffer中的索引 + private int readHead; // √ 读取游标,协议头部在Buffer中的位置,参考ReadMessage() public static MessageReader GetSized(int minSize) { @@ -65,10 +71,15 @@ namespace Hazel return output; } + /// + /// 创建一个新的message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// + /// + /// public static MessageReader CopyMessageIntoParent(MessageReader source) { - var output = MessageReader.GetSized(source.Length + 3); - System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); + var output = MessageReader.GetSized(source.Length + 3); //3=2bytes(length) + 1byte(tag) + System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); output.Offset = 0; output.Position = 0; @@ -77,6 +88,11 @@ namespace Hazel return output; } + /// + /// 完全复制 一份 + /// + /// + /// public static MessageReader Get(MessageReader source) { var output = MessageReader.GetSized(source.Buffer.Length); @@ -96,7 +112,7 @@ namespace Hazel public static MessageReader Get(byte[] buffer, int offset) { // Ensure there is at least a header - if (offset + 3 > buffer.Length) return null; + if (offset + 3 > buffer.Length) return null; //3=length+tag var output = ReaderPool.GetObject(); @@ -104,7 +120,7 @@ namespace Hazel output.Offset = offset; output.Position = 0; - output.Length = output.ReadUInt16(); + output.Length = output.ReadUInt16(); // 读到的值为length output.Tag = output.ReadByte(); output.Offset += 3; @@ -121,22 +137,24 @@ namespace Hazel // Ensure there is at least a header if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); - var output = new MessageReader(); + MessageReader output = new MessageReader(); output.Parent = this; output.Buffer = this.Buffer; - output.Offset = this.readHead; + output.Offset = this.readHead; // 下面会读取 output.Position = 0; + // 读length和tag的值并保存在字段里 output.Length = output.ReadUInt16(); output.Tag = output.ReadByte(); - output.Offset += 3; + // Offset, readHead齐步走,移到Length和Tag后面,_position=0 + output.Offset += 3; // 3=length+tag output.Position = 0; if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); - this.Position += output.Length + 3; + this.Position += output.Length + 3; //跳过整个子协议 return output; } @@ -201,22 +219,29 @@ namespace Hazel } } + +#if UNIT_TEST // 我自己加的,作为备注 + /// + /// 仅用于单元测试的方法 + /// + /// 父reader + /// public void InsertMessage(MessageReader reader, MessageWriter writer) { var temp = MessageReader.GetSized(reader.Buffer.Length); try { - var headerOffset = reader.Offset - 3; - var startOfMessage = reader.Offset; - var len = reader.Buffer.Length - startOfMessage; - int writerOffset = 3; + var headerOffset = reader.Offset - 3; // headerOffset是length+tag,这个方法仅仅接受reader不含sendoption的情况 + var startOfMessage = reader.Offset; // 头部后面的数据开始的索引 + var len = reader.Buffer.Length - startOfMessage; // 疑似写错了,应该是headerOffset + int writerOffset = 3;// √ 跳过header switch (writer.SendOption) { case SendOption.Reliable: writerOffset = 3; break; case SendOption.None: - writerOffset = 1; + writerOffset = 1; break; } @@ -236,6 +261,7 @@ namespace Hazel temp.Recycle(); } } +#endif private void AdjustLength(int offset, int amount) { @@ -266,7 +292,7 @@ namespace Hazel ReaderPool.PutObject(this); } - #region Read Methods +#region Read Methods public bool ReadBoolean() { byte val = this.FastByte(); @@ -427,7 +453,7 @@ namespace Hazel return output; } - #endregion +#endregion [MethodImpl(MethodImplOptions.AggressiveInlining)] private byte FastByte() diff --git a/Tools/Hazel-Networking/Hazel/MessageWriter.cs b/Tools/Hazel-Networking/Hazel/MessageWriter.cs index cbae11c..68280cd 100644 --- a/Tools/Hazel-Networking/Hazel/MessageWriter.cs +++ b/Tools/Hazel-Networking/Hazel/MessageWriter.cs @@ -6,12 +6,15 @@ using System.Text; namespace Hazel { /// - /// 嵌套结构的Message + /// 单向写入的Message,嵌套结构的Message,如果有嵌套,也只有一个header,子协议没有header + /// 整个结构都会被socket发送,参见UdpConnection.cs /// 结构: - /// ------------------------------------ - /// 2bytes (ushort) 包长度 - /// 1bytes (tag) 协议ID,在AmongUS里是tags.cs里定义的tag和subtag - /// ------------------------------------ + /// -------------header----------------- + /// 1bytes SendOption 是否是可靠传输 + /// (2bytes) (可靠传输用到的) + /// --------------数据------------------ + /// 2bytes 包长度(不含这2bytes和下面的tag 1byte) + /// 1bytes 协议ID,在AmongUS里是tags.cs里定义的tag和subtag /// 数据 包括嵌套的子协议 /// ------------------------------------ /// @@ -20,8 +23,8 @@ namespace Hazel public static int BufferSize = 64000; public static readonly ObjectPool WriterPool = new ObjectPool(() => new MessageWriter(BufferSize)); - public byte[] Buffer; - public int Length; // 总长度 + public byte[] Buffer; // 缓冲区,保存了整个包体,包括头部和内容 + public int Length; // 有效数据在buffer中的长度,可能包含多个嵌套子协议。Length>=Position public int Position; // 写入游标 public SendOption SendOption { get; private set; } @@ -86,6 +89,11 @@ namespace Hazel return output; } + /// + /// expected没有header + /// + /// + /// public bool HasBytes(int expected) { if (this.SendOption == SendOption.None) diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs index f6da329..90aeb0d 100644 --- a/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs @@ -205,9 +205,10 @@ namespace Hazel.Udp } #endif - var msg = MessageReader.GetSized(this.ReceiveBufferSize); + var msg = MessageReader.GetSized(this.ReceiveBufferSize);//һmessage try { + // BufferMessageWriterݣheader socket.BeginReceive(msg.Buffer, 0, msg.Buffer.Length, SocketFlags.None, ReadCallback, msg); } catch @@ -282,7 +283,7 @@ namespace Hazel.Udp //Begin receiving again try { - StartListeningForData(); + StartListeningForData(); //Ϣûasync awaitһwhileѯҪǶ׵ } catch (SocketException e) { @@ -305,6 +306,7 @@ namespace Hazel.Udp DataReceivedRaw?.Invoke(msg.Buffer, msg.Length); #endif + //c //! ص㿴泤ʲô HandleReceive(msg, msg.Length); } diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs index bed4738..cff403b 100644 --- a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs @@ -280,7 +280,18 @@ namespace Hazel.Udp ushort id; if (ProcessReliableReceive(message.Buffer, 1, out id)) { - InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived); + //c +/* + void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, int bytesReceived) + { + buffer.Offset = dataOffset; + buffer.Length = bytesReceived - dataOffset; + buffer.Position = 0; + + InvokeDataReceived(buffer, sendOption); + } +*/ + InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived); // √ 3是header,不是length+tag } else { @@ -302,7 +313,7 @@ namespace Hazel.Udp byte b2 = bytes[offset + 1]; //Get the ID form the packet - id = (ushort)((b1 << 8) + b2); + id = (ushort)((b1 << 8) + b2); // 用于可靠传输的id /* * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...) diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs index e64576a..78f1788 100644 --- a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs @@ -77,7 +77,7 @@ namespace Hazel.Udp case SendOption.Reliable: ResetKeepAliveTimer(); - AttachReliableID(buffer, 1); + AttachReliableID(buffer, 1); // д֮ǰյڿɿID WriteBytesToConnection(buffer, buffer.Length); Statistics.LogReliableSend(buffer.Length - 3); break; @@ -132,6 +132,7 @@ namespace Hazel.Udp { //Handle reliable receives case (byte)SendOption.Reliable: + //c //! ReliableMessageReceive(message, bytesReceived); break; @@ -161,6 +162,7 @@ namespace Hazel.Udp break; case (byte)SendOption.None: + //c //! InvokeDataReceived(SendOption.None, message, 1, bytesReceived); Statistics.LogUnreliableReceive(bytesReceived - 1, bytesReceived); break; -- cgit v1.1-26-g67d0