diff options
Diffstat (limited to 'Projects')
-rw-r--r-- | Projects/Message/Message/.gitignore | 194 | ||||
-rw-r--r-- | Projects/Message/Message/IRecyclable.cs | 29 | ||||
-rw-r--r-- | Projects/Message/Message/Message.csproj | 74 | ||||
-rw-r--r-- | Projects/Message/Message/Message.lutconfig | 6 | ||||
-rw-r--r-- | Projects/Message/Message/Message.sln | 25 | ||||
-rw-r--r-- | Projects/Message/Message/MessageReader.cs | 469 | ||||
-rw-r--r-- | Projects/Message/Message/MessageWriter.cs | 318 | ||||
-rw-r--r-- | Projects/Message/Message/ObjectPool.cs | 108 | ||||
-rw-r--r-- | Projects/Message/Message/Properties/AssemblyInfo.cs | 20 | ||||
-rw-r--r-- | Projects/Message/Message/UnitTest1.cs | 16 | ||||
-rw-r--r-- | Projects/Message/Message/packages.config | 5 |
11 files changed, 1264 insertions, 0 deletions
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 +{ + /// <summary> + /// Interface for all items that can be returned to an object pool. + /// </summary> + /// <threadsafety static="true" instance="true"/> + public interface IRecyclable + { + /// <summary> + /// Returns this object back to the object pool. + /// </summary> + /// <remarks> + /// <para> + /// 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. + /// </para> + /// <para> + /// 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. + /// </para> + /// </remarks> + 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props" Condition="Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props')" /> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Message</RootNamespace> + <AssemblyName>Message</AssemblyName> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + </ItemGroup> + <ItemGroup> + <Compile Include="IRecyclable.cs" /> + <Compile Include="MessageReader.cs" /> + <Compile Include="MessageWriter.cs" /> + <Compile Include="ObjectPool.cs" /> + <Compile Include="UnitTest1.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props'))" /> + <Error Condition="!Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets'))" /> + </Target> + <Import Project="packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets" Condition="Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets')" /> +</Project>
\ 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 @@ +<LUTConfig Version="1.0"> + <Repository /> + <ParallelBuilds>true</ParallelBuilds> + <ParallelTestRuns>true</ParallelTestRuns> + <TestCaseTimeout>180000</TestCaseTimeout> +</LUTConfig>
\ 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 +{ + /// <summary> + /// + /// </summary> + public class MessageReader : IRecyclable + { + public static readonly ObjectPool<MessageReader> ReaderPool = new ObjectPool<MessageReader>(() => 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; + } + + /// <summary> + /// 创建一个新的父message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> + 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; + } + + /// <summary> + /// 完全复制 一份 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> + 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; + } + + /// <summary> + /// Produces a MessageReader using the parent's buffer. This MessageReader should **NOT** be recycled. + /// </summary> + 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; + } + + /// <summary> + /// Produces a MessageReader with a new buffer. This MessageReader should be recycled. + /// </summary> + 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 +{ + + /// <summary> + /// AmongUsùȥ˰ͷش + /// ṹ + /// ------------------------------------- + /// 2bytes (ushort)Эijȣ2bytes + /// ------------------------------------- + /// 1byte ЭͣЭ0ԶЭ1 + /// (2byte) (ԶЭ飬ṩһmodţSteam.UGC.Itemidulong 8bytesеţ֧65535mod) + /// 2byte (ushort)ЭID 0-65535 + /// + /// ------------------------------------- + /// </summary> + 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<MessageWriter> WriterPool = new ObjectPool<MessageWriter>(() => new MessageWriter(BufferSize)); + + public byte[] Buffer; + public int Length; // ЧbufferеijȣܰǶЭ + public int Position; // дλ + + // lengthһڻĴСLengthһÿЭЭݲֵֽ + + private Stack<int> messageStarts = new Stack<int>(); // ¼Э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; + } + + /// <param name="sendOption">The option specifying how the message should be sent.</param> + public static MessageWriter Get() + { + MessageWriter output = WriterPool.GetObject(); + output.Clear(); + + return output; + } + + /// <summary> + /// Ƿexpectedȵ + /// </summary> + /// <param name="expected"></param> + /// <returns></returns> + 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); + } + + /// <summary> + /// ȡǰ༭messageصһ + /// </summary> + 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; + } + + /// <summary> + /// message + /// </summary> + 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 +{ + /// <summary> + /// A fairly simple object pool for items that will be created a lot. + /// </summary> + /// <typeparam name="T">The type that is pooled.</typeparam> + /// <threadsafety static="true" instance="true"/> + public sealed class ObjectPool<T> 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<T> pool = new ConcurrentBag<T>(); +#else + private readonly List<T> pool = new List<T>(); +#endif + + // Unavailable objects + private readonly ConcurrentDictionary<T, bool> inuse = new ConcurrentDictionary<T, bool>(); + + /// <summary> + /// The generator for creating new objects. + /// </summary> + /// <returns></returns> + private readonly Func<T> objectFactory; + + /// <summary> + /// Internal constructor for our ObjectPool. + /// </summary> + internal ObjectPool(Func<T> objectFactory) + { + this.objectFactory = objectFactory; + } + + /// <summary> + /// Returns a pooled object of type T, if none are available another is created. + /// </summary> + /// <returns>An instance of T.</returns> + 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; + } + + /// <summary> + /// Returns an object to the pool. + /// </summary> + /// <param name="item">The item to return.</param> + 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="MSTest.TestAdapter" version="2.2.7" targetFramework="net472" /> + <package id="MSTest.TestFramework" version="2.2.7" targetFramework="net472" /> +</packages>
\ No newline at end of file |