序列化一直是我比较头疼的问题,无论是系统自带的BinaryFormatter还是Json与Xml都有他们不能支持的数据类型,我们还得手动转换一下才能序列化与反序列化。
如果使用BitConverter类手动写一套序列化系统,序列化对象需要继承基类才能够序列化,数据结构将变得臃肿不方便。
直到我使用了Mirror插件,它对于自定义类的序列化,只需要对其中的写入读写类进行方法扩展,完全与原数据结构脱离,也可以随意更改序列化方式。
public class AccountData
{
public string account;
public string password;
}
public static class AccountDataReadWrite
{
public static void WriteAccountData(this NetworkWriter writer, AccountData value)
{
writer.Write(value.account);
writer.Write(value.password);
}
public static AccountData ReadAccountData(this NetworkReader reader)
{
AccountData value = new AccountData();
value.account = reader.ReadString();
value.password = reader.ReadString();
return value;
}
}
但Mirror是联网插件,仅仅只用来序列化就有一点大材小用了,所以就需要将其中的序列化部分摘出来单独使用。
通过源码可知,其通过代码织入完成对Write类的填充,作为萌新当然不会这么高级的操作,而且其是在unity编译时触发,离开unity又要重写一套,所以我想另辟蹊径,绕过这一流程,直到我拿到了初见老师fantasy框架,发现其对程序集的加载处理
public void Load(Assembly assembly)
{
Assembly = assembly;
var assemblyTypes = assembly.GetTypes().ToList();
foreach (var type in assemblyTypes)
{
if (type.IsAbstract || type.IsInterface)
{
continue;
}
var interfaces = type.GetInterfaces();
foreach (var interfaceType in interfaces)
{
AssemblyTypeGroupList.Add(interfaceType, type);
}
}
AssemblyTypeList.AddRange(assemblyTypes);
}
它通过在运行时对程序集所有类型的处理与储存,所以我就想编译时不行就运行时处理就行了,所以就有了下面的处理过程
一、序列化方法容器
在Mirror中是静态泛型类中的静态委托储存,外部声明调用
public static class Writer<T>
{
public static Action<NetworkWriter, T> write;
}
public class NetworkWriter
{
···
public void Write<T>(T value)
{
Action<NetworkWriter, T> writeDelegate = Writer<T>.write;
if (writeDelegate == null)
{
Debug.LogError($"No writer found for {typeof(T)});
}
else
{
writeDelegate(this, value);
}
}
···
}
所以我也使用了相同的容器,并进行了简单的测试
void Start()
{
//赋值
TextClass<Vector3>.action = (o) => { Debug.Log(o); };
TextClass<float>.action = (o) => { Debug.Log(o); };
//调用
TextClass<Vector3>.action(Vector3.one);
TextClass<float>.action(1);
}
public static class TextClass<T>
{
public static Action<T> action;
}
//结果
//(1.00, 1.00, 1.00)
//1
二、委托方法获取
我们可以获取程序集内的所有Type,然后逐一遍历其中的方法,通过其方法的参数与返回值,来判断是不是我们要的方法
//获取写入类的Type
Type writerType = typeof(Writer);
//获取其所在程序集
Assembly assembly = writerType.Assembly;
//获取程序集其中所有的Type然后遍历
var assemblyTypes = assembly.GetTypes().ToList();
foreach (var type in assemblyTypes)
{
//获取Type中的所有方法然后遍历
var methods = type.GetMethods();
foreach (MethodInfo info in methods)
{
//判断方法是不是泛型方法,是就跳过
if (info.IsGenericMethod) continue;
//获取方法所有参数
ParameterInfo[] pInfos = info.GetParameters();
//判断参数是不是2个,第一个是不是写入类(Writer),第2个参数是不是泛型参数(筛选所有泛型,不支持泛型)
if (pInfos.Length == 2 && pInfos[0].ParameterType == writerType && !pInfos[1].ParameterType.IsGenericType)
{
//确认方法是委托方法
Debug.Log(info);
}
}
}
三、注册委托方法
委托方法有了,但每次反射调用肯定不行,我们要用MethInfo.CreateDelegate(Type delegateType)创建一个委托,然后用Action<Writer,T>来存储直接调用,其delegateType就是我们要创建的委托类型,但不能用Action<Writer,T>直接存,我们要获取准确的Action类型,比如
TextClass<Vector3>.action = (o) => { Debug.Log(o); }
这时我们要用Type.MakeGenericType(params Type[] typeArguments)来创建我们想要的泛型委托类型,其中变长参数就是我们要替换的泛型类型,例如
Type typeOld = typeof(Action<,>);
Debug.Log(typeOld);
Type typeNew = typeOld.MakeGenericType(typeof(int), typeof(byte));
Debug.Log(typeNew);
//输出
//System.Action`2[T1,T2]
//System.Action`2[System.Int32,System.Byte]
然后我们就能对写入读取的委托进行储存
public void InitReaderWriter()
{
//写入类调用写入委托,用于判断是否为写入方法 和 用于创建写入委托的第一个参数
Type writerType = typeof(Writer);
//写入存储类,储存写入委托
Type writerGenericType = typeof(Writer<>);
//写入委托
Type actionGenericType = typeof(Action<,>);
//读取类调用读取委托,用于判断是否为读取方法 和 用于创建读取委托的第一个参数
Type readerType = typeof(Reader);
//读取存储类,存储读取委托
Type readerGenericType = typeof(Reader<>);
//读取委托
Type funcGenericType = typeof(Func<,>);
//获取委托方法所在程序集,多个就依次处理
Assembly assembly = writerType.Assembly;
//获取其中所有Type,遍历所有方法
var assemblyTypes = assembly.GetTypes().ToList();
foreach (var type in assemblyTypes)
{
var methods = type.GetMethods();
foreach (MethodInfo info in methods)
{
//泛型跳过
if (info.IsGenericMethod) continue;
ParameterInfo[] pInfos = info.GetParameters();
//写入判断条件为
//1.有且仅有2个参数
//2.第一个参数为写入类
//3.第二个参数不是泛型参数
if (pInfos.Length == 2 && pInfos[0].ParameterType == writerType && !pInfos[1].ParameterType.IsGenericType)
{
//创建精确的写入存储类,用于后续的委托存储
Type wgType = writerGenericType.MakeGenericType(pInfos[1].ParameterType);
//创建精确的写入委托,用于委托的创建
Type agType = actionGenericType.MakeGenericType(writerType, pInfos[1].ParameterType);
//创建委托
var d = info.CreateDelegate(agType);
//获得字段,并设置值
wgType.GetField("write").SetValue(null, d);
}
//读取判断条件为
//1.有且仅有1个参数
//2.第一个参数为读取类
//3.返回类型不是泛型参数
else if (pInfos.Length == 1 && pInfos[0].ParameterType == readerType && !info.ReturnType.IsGenericType)
{
//创建精确的读取存储类,用于后续的委托存储
Type rgType = readerGenericType.MakeGenericType(info.ReturnType);
//创建精确的读取委托,用于委托的创建
Type fgType = funcGenericType.MakeGenericType(readerType, info.ReturnType);
//创建委托
var d = info.CreateDelegate(fgType);
//获得字段,并设置值
rgType.GetField("read").SetValue(null, d);
}
}
}
}
这时我们的写入读取委托初始化就完成了,接下来就是把Mirror中的写入读取类稍作修改就行了
1.剔除与引擎强联系内容
2.更换与Mirror其他模块交互的部分
Writer
using System;
using System.Runtime.CompilerServices;
using System.Text;
public class Writer
{
public const ushort MaxStringLength = ushort.MaxValue - 1;
public const int DefaultCapacity = 1500;
internal byte[] buffer = new byte[DefaultCapacity];
/// <summary>Next position to write to the buffer</summary>
public int Position;
/// <summary>Current capacity. Automatically resized if necessary.</summary>
public int Capacity => buffer.Length;
internal readonly UTF8Encoding encoding = new UTF8Encoding(false, true);
/// <summary>Reset both the position and length of the stream</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
Position = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void EnsureCapacity(int value)
{
if (buffer.Length < value)
{
int capacity = Math.Max(value, buffer.Length * 2);
Array.Resize(ref buffer, capacity);
}
}
/// <summary>Copies buffer until 'Position' to a new array.</summary>
public byte[] ToArray()
{
byte[] data = new byte[Position];
Array.ConstrainedCopy(buffer, 0, data, 0, Position);
return data;
}
/// <summary>Returns allocation-free ArraySegment until 'Position'.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArraySegment<byte> ToArraySegment() =>
new ArraySegment<byte>(buffer, 0, Position);
// implicit conversion for convenience
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ArraySegment<byte>(Writer w) =>
w.ToArraySegment();
internal unsafe void WriteBlittable<T>(T value) where T : unmanaged
{
int size = sizeof(T);
EnsureCapacity(Position + size);
// write blittable
fixed (byte* ptr = &buffer[Position])
{
#if UNITY_ANDROID
T* valueBuffer = stackalloc T[1]{value};
//UnsafeUtility.MemCpy(ptr, valueBuffer, size);
Utility.MemCpy(ptr, valueBuffer, size);
#else
*(T*)ptr = value;
#endif
}
Position += size;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteBlittableNullable<T>(T? value) where T : unmanaged
{
WriteByte((byte)(value.HasValue ? 0x01 : 0x00));
if (value.HasValue)
WriteBlittable(value.Value);
}
public void WriteByte(byte value) => WriteBlittable(value);
public void WriteBytes(byte[] array, int offset, int count)
{
EnsureCapacity(Position + count);
Array.ConstrainedCopy(array, offset, this.buffer, Position, count);
Position += count;
}
public unsafe bool WriteBytes(byte* ptr, int offset, int size)
{
EnsureCapacity(Position + size);
fixed (byte* destination = &buffer[Position])
{
// 10 mio writes: 868ms
// Array.Copy(value.Array, value.Offset, buffer, Position, value.Count);
// 10 mio writes: 775ms
// Buffer.BlockCopy(destination, offset, buffer, Position, size);
// 10 mio writes: 637ms
// UnsafeUtility.MemCpy(destination, ptr + offset, size);
Utility.MemCpy(destination, ptr + offset, size);
}
Position += size;
return true;
}
public void Write<T>(T value)
{
Action<Writer, T> writeDelegate = Writer<T>.write;
if (writeDelegate == null)
{
//Debug.LogError($"No writer found for {typeof(T)}. This happens either if you are missing a NetworkWriter extension for your custom type, or if weaving failed. Try to reimport a script to weave again.");
}
else
{
writeDelegate(this, value);
}
}
public override string ToString() =>
$"[{ToArraySegment().ToHexString()} @ {Position}/{Capacity}]";
}
public static class Writer<T>
{
public static Action<Writer, T> write;
}
Reader
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
public class Reader
{
internal ArraySegment<byte> buffer;
public int Position;
/// <summary>Remaining bytes that can be read, for convenience.</summary>
public int Remaining => buffer.Count - Position;
/// <summary>Total buffer capacity, independent of reader position.</summary>
public int Capacity => buffer.Count;
internal readonly UTF8Encoding encoding = new UTF8Encoding(false, true);
public const int AllocationLimit = 1024 * 1024 * 16; // 16 MB * sizeof(T)
public Reader(ArraySegment<byte> segment)
{
buffer = segment;
}
#if !UNITY_2021_3_OR_NEWER
// Unity 2019 doesn't have the implicit byte[] to segment conversion yet
public Reader(byte[] bytes)
{
buffer = new ArraySegment<byte>(bytes, 0, bytes.Length);
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetBuffer(ArraySegment<byte> segment)
{
buffer = segment;
Position = 0;
}
#if !UNITY_2021_3_OR_NEWER
// Unity 2019 doesn't have the implicit byte[] to segment conversion yet
public void SetBuffer(byte[] bytes)
{
buffer = new ArraySegment<byte>(bytes, 0, bytes.Length);
Position = 0;
}
#endif
internal unsafe T ReadBlittable<T>() where T : unmanaged
{
int size = sizeof(T);
if (Remaining < size)
{
throw new EndOfStreamException($"ReadBlittable<{typeof(T)}> not enough data in buffer to read {size} bytes: {ToString()}");
}
T value;
fixed (byte* ptr = &buffer.Array[buffer.Offset + Position])
{
#if UNITY_ANDROID
T* valueBuffer = stackalloc T[1];
//UnsafeUtility.MemCpy(valueBuffer, ptr, size);
Utility.MemCpy(valueBuffer, ptr, size);
value = valueBuffer[0];
#else
value = *(T*)ptr;
#endif
}
Position += size;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal T? ReadBlittableNullable<T>()
where T : unmanaged =>
ReadByte() != 0 ? ReadBlittable<T>() : default(T?);
public byte ReadByte() => ReadBlittable<byte>();
/// <summary>Read 'count' bytes into the bytes array</summary>
public byte[] ReadBytes(byte[] bytes, int count)
{
if (count < 0) throw new ArgumentOutOfRangeException("ReadBytes requires count >= 0");
if (count > bytes.Length)
{
throw new EndOfStreamException($"ReadBytes can't read {count} + bytes because the passed byte[] only has length {bytes.Length}");
}
if (Remaining < count)
{
throw new EndOfStreamException($"ReadBytesSegment can't read {count} bytes because it would read past the end of the stream. {ToString()}");
}
Array.Copy(buffer.Array, buffer.Offset + Position, bytes, 0, count);
Position += count;
return bytes;
}
/// <summary>Read 'count' bytes allocation-free as ArraySegment that points to the internal array.</summary>
public ArraySegment<byte> ReadBytesSegment(int count)
{
if (count < 0) throw new ArgumentOutOfRangeException("ReadBytesSegment requires count >= 0");
if (Remaining < count)
{
throw new EndOfStreamException($"ReadBytesSegment can't read {count} bytes because it would read past the end of the stream. {ToString()}");
}
ArraySegment<byte> result = new ArraySegment<byte>(buffer.Array, buffer.Offset + Position, count);
Position += count;
return result;
}
/// <summary>Reads any data type that mirror supports. Uses weaver populated Reader(T).read</summary>
public T Read<T>()
{
Func<Reader, T> readerDelegate = Reader<T>.read;
if (readerDelegate == null)
{
//Debug.LogError($"No reader found for {typeof(T)}. Use a type supported by Mirror or define a custom reader extension for {typeof(T)}.");
return default;
}
return readerDelegate(this);
}
public override string ToString() =>
$"[{buffer.ToHexString()} @ {Position}/{Capacity}]";
}
public static class Reader<T>
{
public static Func<Reader, T> read;
}
Utility
using System;
public static class Utility
{
public unsafe static void MemCpy(byte* destination, byte* source, long size)
{
for (int i = 0; i < size; i++)
{
*destination = *source;
destination++;
source++;
}
}
public static string ToHexString(this ArraySegment<byte> segment) =>
BitConverter.ToString(segment.Array, segment.Offset, segment.Count);
}
此时核心内容就结束了,我们只需要和Mirror一样编写序列化脚本就行
public class AccountData
{
public string account;
public string password;
public PlayerData playerData;
}
public static class AccountDataReadWrite
{
public static void WriteAccountData(this Writer writer, AccountData value)
{
writer.Write(value.account);
writer.Write(value.password);
writer.WritePlayerData(value.playerData);
}
public static AccountData ReadAccountData(this Reader reader)
{
AccountData value = new AccountData();
value.account = reader.ReadString();
value.password = reader.ReadString();
value.playerData = reader.ReadPlayerData();
return value;
}
}
数据结构可以随意声明编辑处理,只要序列化类在我们处理的程序集中就行,因此单独开一个程序集储存这类脚本最佳,每次都去手动创建,再手动编写序列化内容,又过于麻烦。
所以可以写一个自动化脚本创建(详情请看唐老狮的网络协议)
四、生成工具
核心
using System;
using System.IO;
using System.Xml;
public class GenerateCSharp
{
//生成数据结构类
public void GenerateData(XmlNodeList nodes,string SAVE_PATH)
{
string namespaceStr;
string classNameStr;
string fieldStr;
string writingStr;
string readingStr;
foreach (XmlNode dataNode in nodes)
{
//命名空间
namespaceStr = dataNode.Attributes["namespace"].Value;
//类名
classNameStr = dataNode.Attributes["name"].Value;
//读取所有字段节点
XmlNodeList fields = dataNode.SelectNodes("field");
//通过这个方法进行成员变量声明的拼接 返回拼接结果
fieldStr = GetFieldStr(fields);
//通过某个方法 对Writing函数中的字符串内容进行拼接 返回结果
writingStr = GetWritingStr(fields);
//通过某个方法 对Reading函数中的字符串内容进行拼接 返回结果
readingStr = GetReadingStr(fields);
string dataStr = "using System;\r\n" +
"using System.Collections.Generic;\r\n" +
"using System.Text;\r\n" +
"using BinaryReadWrite;\r\n" +
$"namespace {namespaceStr}\r\n" +
"{\r\n";
if(dataNode.Attributes["type"].Value == "Inside")
{
dataStr += $"\tpublic class {classNameStr}\r\n" +
"\t{\r\n" +
$"{fieldStr}" +
"\t}\r\n" +
"\r\n";
}
dataStr += $"\tpublic static class {classNameStr}ReadWrite\r\n" +
"\t{\r\n" +
$"\t\tpublic static void Write{classNameStr}(this Writer writer, {classNameStr} value)\r\n" +
"\t\t{\r\n" +
$"{writingStr}" +
"\t\t}\r\n" +
$"\t\tpublic static {classNameStr} Read{classNameStr}(this Reader reader)\r\n" +
"\t\t{\r\n" +
$"\t\t\t{classNameStr} value = new {classNameStr}();\r\n" +
$"{readingStr}" +
"\t\t\treturn value;\r\n" +
"\t\t}\r\n" +
"\t}\r\n" +
"}";
//保存为 脚本文件
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/";
//如果不存在这个文件夹 则创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
//字符串保存 存储为枚举脚本文件
File.WriteAllText(path + classNameStr + "ReadWrite.cs", dataStr);
Console.WriteLine(classNameStr + "ReadWrite已生成");
}
}
/// <summary>
/// 获取成员变量声明内容
/// </summary>
/// <param name="fields"></param>
/// <returns></returns>
private string GetFieldStr(XmlNodeList fields)
{
string fieldStr = "";
foreach (XmlNode field in fields)
{
//变量类型
string type = field.Attributes["type"].Value;
//变量名
string fieldName = field.Attributes["name"].Value;
if(type == "list")
{
string T = field.Attributes["T"].Value;
fieldStr += "\t\tpublic List<" + T + "> ";
}
else if(type == "array")
{
string data = field.Attributes["data"].Value;
fieldStr += "\t\tpublic " + data + "[] ";
}
else if(type == "dic")
{
string Tkey = field.Attributes["Tkey"].Value;
string Tvalue = field.Attributes["Tvalue"].Value;
fieldStr += "\t\tpublic Dictionary<" + Tkey + ", " + Tvalue + "> ";
}
else if(type == "enum")
{
string data = field.Attributes["data"].Value;
fieldStr += "\t\tpublic " + data + " ";
}
else
{
fieldStr += "\t\tpublic " + type + " ";
}
fieldStr += fieldName + ";\r\n";
}
return fieldStr;
}
//拼接 Writing函数的方法
private string GetWritingStr(XmlNodeList fields)
{
string writingStr = "";
string type = "";
string name = "";
foreach (XmlNode field in fields)
{
type = field.Attributes["type"].Value;
name = field.Attributes["name"].Value;
if (type == "list")
{
string T = field.Attributes["T"].Value;
writingStr += "\t\t\twriter.Write((short)value." + name + ".Count);\r\n";
writingStr += "\t\t\tfor (int i = 0; i < value." + name + ".Count; ++i)\r\n";
writingStr += "\t\t\t\t" + GetFieldWritingStr(T, name + "[i]") + "\r\n";
}
else if (type == "array")
{
string data = field.Attributes["data"].Value;
writingStr += "\t\t\twriter.Write((short)value." + name + ".Length);\r\n";
writingStr += "\t\t\tfor (int i = 0; i < value." + name + ".Length; ++i)\r\n";
writingStr += "\t\t\t\t" + GetFieldWritingStr(data, name + "[i]") + "\r\n";
}
else if (type == "dic")
{
string Tkey = field.Attributes["Tkey"].Value;
string Tvalue = field.Attributes["Tvalue"].Value;
writingStr += "\t\t\twriter.Write((short)value." + name + ".Count);\r\n";
writingStr += "\t\t\tforeach (" + Tkey + " key in value." + name + ".Keys)\r\n";
writingStr += "\t\t\t{\r\n";
writingStr += "\t\t\t\t" + GetFieldWritingStrSingle(Tkey, "key") + "\r\n";
writingStr += "\t\t\t\t" + GetFieldWritingStr(Tvalue, name + "[key]") + "\r\n";
writingStr += "\t\t\t}\r\n";
}
else
{
writingStr += "\t\t\t" + GetFieldWritingStr(type, name) + "\r\n";
}
}
return writingStr;
}
private string GetFieldWritingStr(string type, string name)
{
switch (type)
{
case "byte":
return "writer.Write(value." + name +");";
case "int":
return "writer.Write(value." + name + ");";
case "short":
return "writer.Write(value." + name + ");";
case "long":
return "writer.Write(value." + name + ");";
case "float":
return "writer.Write(value." + name + ");";
case "bool":
return "writer.Write(value." + name + ");";
case "string":
return "writer.Write(value." + name + ");";
case "enum":
return "writer.Write((int)value." + name + ");";
default:
return "writer.Write" + type + "(value." + name + ");";
}
}
private string GetFieldWritingStrSingle(string type, string name)
{
switch (type)
{
case "byte":
return "writer.Write(" + name + ");";
case "int":
return "writer.Write(" + name + ");";
case "short":
return "writer.Write(" + name + ");";
case "long":
return "writer.Write(" + name + ");";
case "float":
return "writer.Write(" + name + ");";
case "bool":
return "writer.Write(" + name + ");";
case "string":
return "writer.Write(" + name + ");";
case "enum":
return "writer.Write((int)" + name + ");";
default:
return "writer.Write" + type + "(" + name + ");";
}
}
private string GetReadingStr(XmlNodeList fields)
{
string readingStr = "";
string type = "";
string name = "";
foreach (XmlNode field in fields)
{
type = field.Attributes["type"].Value;
name = field.Attributes["name"].Value;
if (type == "list")
{
string T = field.Attributes["T"].Value;
readingStr += "\t\t\tvalue." + name + " = new List<" + T + ">();\r\n";
readingStr += "\t\t\tshort " + name + "Count = reader.ReadShort();\r\n";
readingStr += "\t\t\tfor (int i = 0; i < " + name + "Count; ++i)\r\n";
readingStr += "\t\t\t\tvalue." + name + ".Add(" + GetFieldReadingStr(T) + ");\r\n";
}
else if (type == "array")
{
string data = field.Attributes["data"].Value;
readingStr += "\t\t\tshort " + name + "Length = reader.ReadShort();\r\n";
readingStr += "\t\t\tvalue." + name + " = new " + data + "[" + name + "Length];\r\n";
readingStr += "\t\t\tfor (int i = 0; i < value." + name + ".Length; ++i)\r\n";
readingStr += "\t\t\t\tvalue." + name + "[i] = " + GetFieldReadingStr(data) + ";\r\n";
}
else if (type == "dic")
{
string Tkey = field.Attributes["Tkey"].Value;
string Tvalue = field.Attributes["Tvalue"].Value;
readingStr += "\t\t\tvalue." + name + " = new Dictionary<" + Tkey + ", " + Tvalue + ">();\r\n";
readingStr += "\t\t\tshort " + name + "Count = reader.ReadShort();\r\n";
readingStr += "\t\t\tfor (int i = 0; i < " + name + "Count; ++i)\r\n";
readingStr += "\t\t\t\tvalue." + name + ".Add(" + GetFieldReadingStr(Tkey) + ", " +
GetFieldReadingStr(Tvalue) + ");\r\n";
}
else if (type == "enum")
{
string data = field.Attributes["data"].Value;
readingStr += "\t\t\tvalue." + name + " = (" + data + ")reader.ReadInt();\r\n";
}
else
readingStr += "\t\t\tvalue." + name + " = " + GetFieldReadingStr(type) + ";\r\n";
}
return readingStr;
}
private string GetFieldReadingStr(string type)
{
switch (type)
{
case "byte":
return "reader.ReadByte()";
case "int":
return "reader.ReadInt()";
case "short":
return "reader.ReadShort()";
case "long":
return "reader.ReadLong()";
case "float":
return "reader.ReadFloat()";
case "bool":
return "reader.ReadBool()";
case "string":
return "reader.ReadString()";
default:
return "reader.Read" + type + "()";
}
}
}
启动,这里为了方便就用unity的菜单触发,其他可以修改一下单独编译出来执行
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using UnityEditor;
using UnityEngine;
public class ProtocolTool
{
//配置文件所在路径
private static string XML_INFO_PATH = Application.dataPath + "/Scripts/Writer/Tool/Editor/ProtocolInfo.xml";
private static string SAVE_PATH = Application.dataPath + "/Scripts/Writer/ReadWrite/";
private static GenerateCSharp generateCSharp = new GenerateCSharp();
[MenuItem("Tools/GenerateDataReadWrite")]
private static void GenerateCSharp()
{
//生成对应的数据类读写脚本
generateCSharp.GenerateData(GetNodes("data"), SAVE_PATH);
}
/// <summary>
/// 获取指定名字的所有子节点 的 List
/// </summary>
/// <param name="nodeName"></param>
/// <returns></returns>
private static XmlNodeList GetNodes(string nodeName)
{
XmlDocument xml = new XmlDocument();
xml.Load(XML_INFO_PATH);
XmlNode root = xml.SelectSingleNode("messages");
return root.SelectNodes(nodeName);
}
}
xml,配置表
<?xml version="1.0" encoding="UTF-8"?>
<messages>
<!--数据结构类配置规则
name: 数据结构名字
namespace: 命名空间
type: 消息类型
Outside 外部声明的自由数据类,需要无参构造,(反)序列化只应用于注册成员变量,未注册则无视
Inside 内部数据类,自动生成,无法修改类参数-->
<data name="ATKData" namespace="BinaryReadWrite" type="Inside">
<field type="int" name="id"/>
<field type="float" name="pre"/>
<field type="float" name="value"/>
<field type="float" name="valueAdd"/>
<field type="float" name="valuePre"/>
<field type="bool" name="canAtk"/>
</data>
<data name="PlayerData" namespace="BinaryReadWrite" type="Inside">
<field type="int" name="id"/>
<field type="float" name="atk"/>
<field type="bool" name="sex"/>
<field type="long" name="lev"/>
<field type="array" data="int" name="arrays"/>
<field type="list" T="int" name="list"/>
<field type="dic" Tkey="int" Tvalue="string" name="dic"/>
</data>
<data name="AccountData" namespace="BinaryReadWrite" type="Inside">
<field type="string" name="account"/>
<field type="string" name="password"/>
<field type="PlayerData" name="playerData"/>
</data>
</messages>
五、常用类型内置拓展
对于int、float等常用类型,不能都在xml中配置,所以直接内置常态扩展就行
写入
#define OnUnity
using System;
using System.Collections.Generic;
#if OnUnity
using UnityEngine;
#endif
namespace BinaryReadWrite
{
public static class BinaryWriterExtensions
{
public static void WriteByte(this Writer writer, byte value) => writer.WriteBlittable(value);
public static void WriteByteNullable(this Writer writer, byte? value) => writer.WriteBlittableNullable(value);
public static void WriteSByte(this Writer writer, sbyte value) => writer.WriteBlittable(value);
public static void WriteSByteNullable(this Writer writer, sbyte? value) => writer.WriteBlittableNullable(value);
// char is not blittable. convert to ushort.
public static void WriteChar(this Writer writer, char value) => writer.WriteBlittable((ushort)value);
public static void WriteCharNullable(this Writer writer, char? value) => writer.WriteBlittableNullable((ushort?)value);
// bool is not blittable. convert to byte.
public static void WriteBool(this Writer writer, bool value) => writer.WriteBlittable((byte)(value ? 1 : 0));
public static void WriteBoolNullable(this Writer writer, bool? value) => writer.WriteBlittableNullable(value.HasValue ? ((byte)(value.Value ? 1 : 0)) : new byte?());
public static void WriteShort(this Writer writer, short value) => writer.WriteBlittable(value);
public static void WriteShortNullable(this Writer writer, short? value) => writer.WriteBlittableNullable(value);
public static void WriteUShort(this Writer writer, ushort value) => writer.WriteBlittable(value);
public static void WriteUShortNullable(this Writer writer, ushort? value) => writer.WriteBlittableNullable(value);
public static void WriteInt(this Writer writer, int value) => writer.WriteBlittable(value);
public static void WriteIntNullable(this Writer writer, int? value) => writer.WriteBlittableNullable(value);
public static void WriteUInt(this Writer writer, uint value) => writer.WriteBlittable(value);
public static void WriteUIntNullable(this Writer writer, uint? value) => writer.WriteBlittableNullable(value);
public static void WriteLong(this Writer writer, long value) => writer.WriteBlittable(value);
public static void WriteLongNullable(this Writer writer, long? value) => writer.WriteBlittableNullable(value);
public static void WriteULong(this Writer writer, ulong value) => writer.WriteBlittable(value);
public static void WriteULongNullable(this Writer writer, ulong? value) => writer.WriteBlittableNullable(value);
public static void WriteFloat(this Writer writer, float value) => writer.WriteBlittable(value);
public static void WriteFloatNullable(this Writer writer, float? value) => writer.WriteBlittableNullable(value);
public static void WriteDouble(this Writer writer, double value) => writer.WriteBlittable(value);
public static void WriteDoubleNullable(this Writer writer, double? value) => writer.WriteBlittableNullable(value);
public static void WriteDecimal(this Writer writer, decimal value) => writer.WriteBlittable(value);
public static void WriteDecimalNullable(this Writer writer, decimal? value) => writer.WriteBlittableNullable(value);
public static void WriteString(this Writer writer, string value)
{
// write 0 for null support, increment real size by 1
// (note: original HLAPI would write "" for null strings, but if a
// string is null on the server then it should also be null
// on the client)
if (value == null)
{
writer.WriteUShort(0);
return;
}
// WriteString copies into the buffer manually.
// need to ensure capacity here first, manually.
int maxSize = writer.encoding.GetMaxByteCount(value.Length);
writer.EnsureCapacity(writer.Position + 2 + maxSize); // 2 bytes position + N bytes encoding
// encode it into the buffer first.
// reserve 2 bytes for header after we know how much was written.
int written = writer.encoding.GetBytes(value, 0, value.Length, writer.buffer, writer.Position + 2);
// check if within max size, otherwise Reader can't read it.
if (written > Writer.MaxStringLength)
throw new IndexOutOfRangeException($"NetworkWriter.WriteString - Value too long: {written} bytes. Limit: {Writer.MaxStringLength} bytes");
// .Position is unchanged, so fill in the size header now.
// we already ensured that max size fits into ushort.max-1.
writer.WriteUShort(checked((ushort)(written + 1))); // Position += 2
// now update position by what was written above
writer.Position += written;
}
public static void WriteBytesAndSizeSegment(this Writer writer, ArraySegment<byte> buffer)
{
writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count);
}
// Weaver needs a write function with just one byte[] parameter
// (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too)
public static void WriteBytesAndSize(this Writer writer, byte[] buffer)
{
// buffer might be null, so we can't use .Length in that case
writer.WriteBytesAndSize(buffer, 0, buffer != null ? buffer.Length : 0);
}
// for byte arrays with dynamic size, where the reader doesn't know how many will come
// (like an inventory with different items etc.)
public static void WriteBytesAndSize(this Writer writer, byte[] buffer, int offset, int count)
{
// null is supported because [SyncVar]s might be structs with null byte[] arrays
// write 0 for null array, increment normal size by 1 to save bandwidth
// (using size=-1 for null would limit max size to 32kb instead of 64kb)
if (buffer == null)
{
writer.WriteUInt(0u);
return;
}
writer.WriteUInt(checked((uint)count) + 1u);
writer.WriteBytes(buffer, offset, count);
}
public static void WriteArraySegment<T>(this Writer writer, ArraySegment<T> segment)
{
int length = segment.Count;
writer.WriteInt(length);
for (int i = 0; i < length; i++)
{
writer.Write(segment.Array[segment.Offset + i]);
}
}
// while SyncList<T> is recommended for NetworkBehaviours,
// structs may have .List<T> members which weaver needs to be able to
// fully serialize for NetworkMessages etc.
// note that Weaver/Writers/GenerateWriter() handles this manually.
public static void WriteList<T>(this Writer writer, List<T> list)
{
// 'null' is encoded as '-1'
if (list is null)
{
writer.WriteInt(-1);
return;
}
// check if within max size, otherwise Reader can't read it.
if (list.Count > Reader.AllocationLimit)
throw new IndexOutOfRangeException($"Writer.WriteList - List<{typeof(T)}> too big: {list.Count} elements. Limit: {Reader.AllocationLimit}");
writer.WriteInt(list.Count);
for (int i = 0; i < list.Count; i++)
writer.Write(list[i]);
}
public static void WriteArray<T>(this Writer writer, T[] array)
{
// 'null' is encoded as '-1'
if (array is null)
{
writer.WriteInt(-1);
return;
}
// check if within max size, otherwise Reader can't read it.
if (array.Length > Reader.AllocationLimit)
throw new IndexOutOfRangeException($"Writer.WriteArray - Array<{typeof(T)}> too big: {array.Length} elements. Limit: {Reader.AllocationLimit}");
writer.WriteInt(array.Length);
for (int i = 0; i < array.Length; i++)
writer.Write(array[i]);
}
public static void WriteUri(this Writer writer, Uri uri)
{
writer.WriteString(uri?.ToString());
}
public static void WriteDateTime(this Writer writer, DateTime dateTime)
{
writer.WriteDouble(dateTime.ToOADate());
}
public static void WriteDateTimeNullable(this Writer writer, DateTime? dateTime)
{
writer.WriteBool(dateTime.HasValue);
if (dateTime.HasValue)
writer.WriteDouble(dateTime.Value.ToOADate());
}
#if OnUnity
public static void WriteVector2(this Writer writer, Vector2 value) => writer.WriteBlittable(value);
public static void WriteVector2Nullable(this Writer writer, Vector2? value) => writer.WriteBlittableNullable(value);
public static void WriteVector3(this Writer writer, Vector3 value) => writer.WriteBlittable(value);
public static void WriteVector3Nullable(this Writer writer, Vector3? value) => writer.WriteBlittableNullable(value);
public static void WriteVector4(this Writer writer, Vector4 value) => writer.WriteBlittable(value);
public static void WriteVector4Nullable(this Writer writer, Vector4? value) => writer.WriteBlittableNullable(value);
public static void WriteVector2Int(this Writer writer, Vector2Int value) => writer.WriteBlittable(value);
public static void WriteVector2IntNullable(this Writer writer, Vector2Int? value) => writer.WriteBlittableNullable(value);
public static void WriteVector3Int(this Writer writer, Vector3Int value) => writer.WriteBlittable(value);
public static void WriteVector3IntNullable(this Writer writer, Vector3Int? value) => writer.WriteBlittableNullable(value);
public static void WriteColor(this Writer writer, Color value) => writer.WriteBlittable(value);
public static void WriteColorNullable(this Writer writer, Color? value) => writer.WriteBlittableNullable(value);
public static void WriteColor32(this Writer writer, Color32 value) => writer.WriteBlittable(value);
public static void WriteColor32Nullable(this Writer writer, Color32? value) => writer.WriteBlittableNullable(value);
public static void WriteQuaternion(this Writer writer, Quaternion value) => writer.WriteBlittable(value);
public static void WriteQuaternionNullable(this Writer writer, Quaternion? value) => writer.WriteBlittableNullable(value);
// Rect is a struct with properties instead of fields
public static void WriteRect(this Writer writer, Rect value)
{
writer.WriteVector2(value.position);
writer.WriteVector2(value.size);
}
public static void WriteRectNullable(this Writer writer, Rect? value)
{
writer.WriteBool(value.HasValue);
if (value.HasValue)
writer.WriteRect(value.Value);
}
// Plane is a struct with properties instead of fields
public static void WritePlane(this Writer writer, Plane value)
{
writer.WriteVector3(value.normal);
writer.WriteFloat(value.distance);
}
public static void WritePlaneNullable(this Writer writer, Plane? value)
{
writer.WriteBool(value.HasValue);
if (value.HasValue)
writer.WritePlane(value.Value);
}
// Ray is a struct with properties instead of fields
public static void WriteRay(this Writer writer, Ray value)
{
writer.WriteVector3(value.origin);
writer.WriteVector3(value.direction);
}
public static void WriteRayNullable(this Writer writer, Ray? value)
{
writer.WriteBool(value.HasValue);
if (value.HasValue)
writer.WriteRay(value.Value);
}
public static void WriteMatrix4x4(this Writer writer, Matrix4x4 value) => writer.WriteBlittable(value);
public static void WriteMatrix4x4Nullable(this Writer writer, Matrix4x4? value) => writer.WriteBlittableNullable(value);
public static void WriteGuid(this Writer writer, Guid value)
{
#if !UNITY_2021_3_OR_NEWER
// Unity 2019 doesn't have Span yet
byte[] data = value.ToByteArray();
writer.WriteBytes(data, 0, data.Length);
#else
// WriteBlittable(Guid) isn't safe. see WriteBlittable comments.
// Guid is Sequential, but we can't guarantee packing.
// TryWriteBytes is safe and allocation free.
writer.EnsureCapacity(writer.Position + 16);
value.TryWriteBytes(new Span<byte>(writer.buffer, writer.Position, 16));
writer.Position += 16;
#endif
}
public static void WriteGuidNullable(this Writer writer, Guid? value)
{
writer.WriteBool(value.HasValue);
if (value.HasValue)
writer.WriteGuid(value.Value);
}
// while SyncSet<T> is recommended for NetworkBehaviours,
// structs may have .Set<T> members which weaver needs to be able to
// fully serialize for NetworkMessages etc.
// note that Weaver/Writers/GenerateWriter() handles this manually.
// TODO writer not found. need to adjust weaver first. see tests.
/*
public static void WriteHashSet<T>(this NetworkWriter writer, HashSet<T> hashSet)
{
if (hashSet is null)
{
writer.WriteInt(-1);
return;
}
writer.WriteInt(hashSet.Count);
foreach (T item in hashSet)
writer.Write(item);
}
*/
public static void WriteTexture2D(this Writer writer, Texture2D texture2D)
{
// TODO allocation protection when sending textures to server.
// currently can allocate 32k x 32k x 4 byte = 3.8 GB
// support 'null' textures for [SyncVar]s etc.
// https://github.com/vis2k/Mirror/issues/3144
// simply send -1 for width.
if (texture2D == null)
{
writer.WriteShort(-1);
return;
}
// check if within max size, otherwise Reader can't read it.
int totalSize = texture2D.width * texture2D.height;
if (totalSize > Reader.AllocationLimit)
throw new IndexOutOfRangeException($"Writer.WriteTexture2D - Texture2D total size (width*height) too big: {totalSize}. Limit: {Reader.AllocationLimit}");
// write dimensions first so reader can create the texture with size
// 32k x 32k short is more than enough
writer.WriteShort((short)texture2D.width);
writer.WriteShort((short)texture2D.height);
writer.WriteArray(texture2D.GetPixels32());
}
public static void WriteSprite(this Writer writer, Sprite sprite)
{
// support 'null' textures for [SyncVar]s etc.
// https://github.com/vis2k/Mirror/issues/3144
// simply send a 'null' for texture content.
if (sprite == null)
{
writer.WriteTexture2D(null);
return;
}
writer.WriteTexture2D(sprite.texture);
writer.WriteRect(sprite.rect);
writer.WriteVector2(sprite.pivot);
}
#endif
}
}
读取
#define OnUnity
using System;
using System.Collections.Generic;
using System.IO;
#if OnUnity
using UnityEngine;
#endif
namespace BinaryReadWrite
{
public static class BinaryReaderExtensions
{
public static byte ReadByte(this Reader reader) => reader.ReadBlittable<byte>();
public static byte? ReadByteNullable(this Reader reader) => reader.ReadBlittableNullable<byte>();
public static sbyte ReadSByte(this Reader reader) => reader.ReadBlittable<sbyte>();
public static sbyte? ReadSByteNullable(this Reader reader) => reader.ReadBlittableNullable<sbyte>();
// bool is not blittable. read as ushort.
public static char ReadChar(this Reader reader) => (char)reader.ReadBlittable<ushort>();
public static char? ReadCharNullable(this Reader reader) => (char?)reader.ReadBlittableNullable<ushort>();
// bool is not blittable. read as byte.
public static bool ReadBool(this Reader reader) => reader.ReadBlittable<byte>() != 0;
public static bool? ReadBoolNullable(this Reader reader)
{
byte? value = reader.ReadBlittableNullable<byte>();
return value.HasValue ? (value.Value != 0) : default(bool?);
}
public static short ReadShort(this Reader reader) => (short)reader.ReadUShort();
public static short? ReadShortNullable(this Reader reader) => reader.ReadBlittableNullable<short>();
public static ushort ReadUShort(this Reader reader) => reader.ReadBlittable<ushort>();
public static ushort? ReadUShortNullable(this Reader reader) => reader.ReadBlittableNullable<ushort>();
public static int ReadInt(this Reader reader) => reader.ReadBlittable<int>();
public static int? ReadIntNullable(this Reader reader) => reader.ReadBlittableNullable<int>();
public static uint ReadUInt(this Reader reader) => reader.ReadBlittable<uint>();
public static uint? ReadUIntNullable(this Reader reader) => reader.ReadBlittableNullable<uint>();
public static long ReadLong(this Reader reader) => reader.ReadBlittable<long>();
public static long? ReadLongNullable(this Reader reader) => reader.ReadBlittableNullable<long>();
public static ulong ReadULong(this Reader reader) => reader.ReadBlittable<ulong>();
public static ulong? ReadULongNullable(this Reader reader) => reader.ReadBlittableNullable<ulong>();
public static float ReadFloat(this Reader reader) => reader.ReadBlittable<float>();
public static float? ReadFloatNullable(this Reader reader) => reader.ReadBlittableNullable<float>();
public static double ReadDouble(this Reader reader) => reader.ReadBlittable<double>();
public static double? ReadDoubleNullable(this Reader reader) => reader.ReadBlittableNullable<double>();
public static decimal ReadDecimal(this Reader reader) => reader.ReadBlittable<decimal>();
public static decimal? ReadDecimalNullable(this Reader reader) => reader.ReadBlittableNullable<decimal>();
/// <exception cref="T:System.ArgumentException">if an invalid utf8 string is sent</exception>
public static string ReadString(this Reader reader)
{
// read number of bytes
ushort size = reader.ReadUShort();
// null support, see NetworkWriter
if (size == 0)
return null;
ushort realSize = (ushort)(size - 1);
// make sure it's within limits to avoid allocation attacks etc.
if (realSize > Writer.MaxStringLength)
throw new EndOfStreamException($"Reader.ReadString - Value too long: {realSize} bytes. Limit is: {Writer.MaxStringLength} bytes");
ArraySegment<byte> data = reader.ReadBytesSegment(realSize);
// convert directly from buffer to string via encoding
// throws in case of invalid utf8.
// see test: ReadString_InvalidUTF8()
return reader.encoding.GetString(data.Array, data.Offset, data.Count);
}
/// <exception cref="T:OverflowException">if count is invalid</exception>
public static byte[] ReadBytesAndSize(this Reader reader)
{
// count = 0 means the array was null
// otherwise count -1 is the length of the array
uint count = reader.ReadUInt();
// Use checked() to force it to throw OverflowException if data is invalid
return count == 0 ? null : reader.ReadBytes(checked((int)(count - 1u)));
}
public static byte[] ReadBytes(this Reader reader, int count)
{
if (count > Reader.AllocationLimit)
{
// throw EndOfStream for consistency with ReadBlittable when out of data
throw new EndOfStreamException($"Reader attempted to allocate {count} bytes, which is larger than the allowed limit of {Reader.AllocationLimit} bytes.");
}
byte[] bytes = new byte[count];
reader.ReadBytes(bytes, count);
return bytes;
}
/// <exception cref="T:OverflowException">if count is invalid</exception>
public static ArraySegment<byte> ReadBytesAndSizeSegment(this Reader reader)
{
// count = 0 means the array was null
// otherwise count - 1 is the length of the array
uint count = reader.ReadUInt();
// Use checked() to force it to throw OverflowException if data is invalid
return count == 0 ? default : reader.ReadBytesSegment(checked((int)(count - 1u)));
}
public static List<T> ReadList<T>(this Reader reader)
{
int length = reader.ReadInt();
// 'null' is encoded as '-1'
if (length < 0) return null;
if (length > Reader.AllocationLimit)
{
// throw EndOfStream for consistency with ReadBlittable when out of data
throw new EndOfStreamException($"Reader attempted to allocate a List<{typeof(T)}> {length} elements, which is larger than the allowed limit of {Reader.AllocationLimit}.");
}
List<T> result = new List<T>(length);
for (int i = 0; i < length; i++)
{
result.Add(reader.Read<T>());
}
return result;
}
public static T[] ReadArray<T>(this Reader reader)
{
int length = reader.ReadInt();
// 'null' is encoded as '-1'
if (length < 0) return null;
// prevent allocation attacks with a reasonable limit.
// server shouldn't allocate too much on client devices.
// client shouldn't allocate too much on server in ClientToServer [SyncVar]s.
if (length > Reader.AllocationLimit)
{
// throw EndOfStream for consistency with ReadBlittable when out of data
throw new EndOfStreamException($"Reader attempted to allocate an Array<{typeof(T)}> with {length} elements, which is larger than the allowed limit of {Reader.AllocationLimit}.");
}
// we can't check if reader.Remaining < length,
// because we don't know sizeof(T) since it's a managed type.
// if (length > reader.Remaining) throw new EndOfStreamException($"Received array that is too large: {length}");
T[] result = new T[length];
for (int i = 0; i < length; i++)
{
result[i] = reader.Read<T>();
}
return result;
}
public static Uri ReadUri(this Reader reader)
{
string uriString = reader.ReadString();
return (string.IsNullOrWhiteSpace(uriString) ? null : new Uri(uriString));
}
public static DateTime ReadDateTime(this Reader reader) => DateTime.FromOADate(reader.ReadDouble());
public static DateTime? ReadDateTimeNullable(this Reader reader) => reader.ReadBool() ? ReadDateTime(reader) : default(DateTime?);
#if OnUnity
public static Vector2 ReadVector2(this Reader reader) => reader.ReadBlittable<Vector2>();
public static Vector2? ReadVector2Nullable(this Reader reader) => reader.ReadBlittableNullable<Vector2>();
public static Vector3 ReadVector3(this Reader reader) => reader.ReadBlittable<Vector3>();
public static Vector3? ReadVector3Nullable(this Reader reader) => reader.ReadBlittableNullable<Vector3>();
public static Vector4 ReadVector4(this Reader reader) => reader.ReadBlittable<Vector4>();
public static Vector4? ReadVector4Nullable(this Reader reader) => reader.ReadBlittableNullable<Vector4>();
public static Vector2Int ReadVector2Int(this Reader reader) => reader.ReadBlittable<Vector2Int>();
public static Vector2Int? ReadVector2IntNullable(this Reader reader) => reader.ReadBlittableNullable<Vector2Int>();
public static Vector3Int ReadVector3Int(this Reader reader) => reader.ReadBlittable<Vector3Int>();
public static Vector3Int? ReadVector3IntNullable(this Reader reader) => reader.ReadBlittableNullable<Vector3Int>();
public static Color ReadColor(this Reader reader) => reader.ReadBlittable<Color>();
public static Color? ReadColorNullable(this Reader reader) => reader.ReadBlittableNullable<Color>();
public static Color32 ReadColor32(this Reader reader) => reader.ReadBlittable<Color32>();
public static Color32? ReadColor32Nullable(this Reader reader) => reader.ReadBlittableNullable<Color32>();
public static Quaternion ReadQuaternion(this Reader reader) => reader.ReadBlittable<Quaternion>();
public static Quaternion? ReadQuaternionNullable(this Reader reader) => reader.ReadBlittableNullable<Quaternion>();
// Rect is a struct with properties instead of fields
public static Rect ReadRect(this Reader reader) => new Rect(reader.ReadVector2(), reader.ReadVector2());
public static Rect? ReadRectNullable(this Reader reader) => reader.ReadBool() ? ReadRect(reader) : default(Rect?);
// Plane is a struct with properties instead of fields
public static Plane ReadPlane(this Reader reader) => new Plane(reader.ReadVector3(), reader.ReadFloat());
public static Plane? ReadPlaneNullable(this Reader reader) => reader.ReadBool() ? ReadPlane(reader) : default(Plane?);
// Ray is a struct with properties instead of fields
public static Ray ReadRay(this Reader reader) => new Ray(reader.ReadVector3(), reader.ReadVector3());
public static Ray? ReadRayNullable(this Reader reader) => reader.ReadBool() ? ReadRay(reader) : default(Ray?);
public static Matrix4x4 ReadMatrix4x4(this Reader reader) => reader.ReadBlittable<Matrix4x4>();
public static Matrix4x4? ReadMatrix4x4Nullable(this Reader reader) => reader.ReadBlittableNullable<Matrix4x4>();
public static Guid ReadGuid(this Reader reader)
{
#if !UNITY_2021_3_OR_NEWER
// Unity 2019 doesn't have Span yet
return new Guid(reader.ReadBytes(16));
#else
// ReadBlittable(Guid) isn't safe. see ReadBlittable comments.
// Guid is Sequential, but we can't guarantee packing.
if (reader.Remaining >= 16)
{
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(reader.buffer.Array, reader.buffer.Offset + reader.Position, 16);
reader.Position += 16;
return new Guid(span);
}
throw new EndOfStreamException($"ReadGuid out of range: {reader}");
#endif
}
public static Guid? ReadGuidNullable(this Reader reader) => reader.ReadBool() ? ReadGuid(reader) : default(Guid?);
// while SyncSet<T> is recommended for NetworkBehaviours,
// structs may have .Set<T> members which weaver needs to be able to
// fully serialize for NetworkMessages etc.
// note that Weaver/Readers/GenerateReader() handles this manually.
// TODO writer not found. need to adjust weaver first. see tests.
/*
public static HashSet<T> ReadHashSet<T>(this NetworkReader reader)
{
int length = reader.ReadInt();
if (length < 0)
return null;
HashSet<T> result = new HashSet<T>();
for (int i = 0; i < length; i++)
{
result.Add(reader.Read<T>());
}
return result;
}
*/
public static Texture2D ReadTexture2D(this Reader reader)
{
// support 'null' textures for [SyncVar]s etc.
// https://github.com/vis2k/Mirror/issues/3144
short width = reader.ReadShort();
if (width == -1) return null;
// read height
short height = reader.ReadShort();
// prevent allocation attacks with a reasonable limit.
// server shouldn't allocate too much on client devices.
// client shouldn't allocate too much on server in ClientToServer [SyncVar]s.
// log an error and return default.
// we don't want attackers to be able to trigger exceptions.
int totalSize = width * height;
if (totalSize > Reader.AllocationLimit)
{
Debug.LogWarning($"Reader attempted to allocate a Texture2D with total size (width * height) of {totalSize}, which is larger than the allowed limit of {Reader.AllocationLimit}.");
return null;
}
Texture2D texture2D = new Texture2D(width, height);
// read pixel content
Color32[] pixels = reader.ReadArray<Color32>();
texture2D.SetPixels32(pixels);
texture2D.Apply();
return texture2D;
}
public static Sprite ReadSprite(this Reader reader)
{
// support 'null' textures for [SyncVar]s etc.
// https://github.com/vis2k/Mirror/issues/3144
Texture2D texture = reader.ReadTexture2D();
if (texture == null) return null;
// otherwise create a valid sprite
return Sprite.Create(texture, reader.ReadRect(), reader.ReadVector2());
}
#endif
}
}