From 53a7cdd337b43ce7af2a8e7be4ce581ad692ea76 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Wed, 30 Aug 2023 13:50:21 +0800 Subject: [PATCH] First checkin with code --- .gitignore | 1 + Assets/GuruKCP/Editor/__PLACEHOLDER__ | 0 Assets/GuruKCP/Editor/__PLACEHOLDER__.meta | 3 - Assets/GuruKCP/Runtime/__PLACEHOLDER__ | 0 Assets/GuruKCP/Runtime/__PLACEHOLDER__.meta | 3 - Assets/GuruKCP/Editor.meta => Editor.meta | 0 Editor/GuruKCP.Editor.asmdef | 18 + Editor/GuruKCP.Editor.asmdef.meta | 7 + Editor/Proto2CSEditor.cs | 248 ++ Editor/Proto2CSEditor.cs.meta | 11 + Editor/protobuf-net.Reflection.meta | 8 + .../CSharpCodeGenerator.cs | 787 +++++ .../CSharpCodeGenerator.cs.meta | 11 + .../CodeGenerator.OneOfStub.cs | 158 + .../CodeGenerator.OneOfStub.cs.meta | 11 + .../protobuf-net.Reflection/CodeGenerator.cs | 476 +++ .../CodeGenerator.cs.meta | 11 + .../protobuf-net.Reflection/CustomOptions.cs | 141 + .../CustomOptions.cs.meta | 11 + Editor/protobuf-net.Reflection/Descriptor.cs | 1149 +++++++ .../Descriptor.cs.meta | 11 + .../protobuf-net.Reflection/NameNormalizer.cs | 236 ++ .../NameNormalizer.cs.meta | 11 + Editor/protobuf-net.Reflection/Parsers.cs | 2841 +++++++++++++++++ .../protobuf-net.Reflection/Parsers.cs.meta | 11 + Editor/protobuf-net.Reflection/Peekable.cs | 55 + .../protobuf-net.Reflection/Peekable.cs.meta | 11 + Editor/protobuf-net.Reflection/Token.cs | 83 + Editor/protobuf-net.Reflection/Token.cs.meta | 11 + .../TokenExtensions.cs | 642 ++++ .../TokenExtensions.cs.meta | 11 + Editor/protobuf-net.Reflection/TokenType.cs | 11 + .../protobuf-net.Reflection/TokenType.cs.meta | 11 + Assets/GuruKCP/README.md => README.md | 4 + .../GuruKCP/README.md.meta => README.md.meta | 0 Assets/GuruKCP/Runtime.meta => Runtime.meta | 0 .../GuruKCP.Runtime.asmdef | 6 +- .../GuruKCP.Runtime.asmdef.meta | 0 .../GuruKCP/Runtime => Runtime}/GuruKCP.cs | 0 .../Runtime => Runtime}/GuruKCP.cs.meta | 0 Runtime/Protobuf-net.meta | 8 + Runtime/Protobuf-net/BclHelpers.cs | 712 +++++ Runtime/Protobuf-net/BclHelpers.cs.meta | 11 + Runtime/Protobuf-net/BufferExtension.cs | 78 + Runtime/Protobuf-net/BufferExtension.cs.meta | 11 + Runtime/Protobuf-net/BufferPool.cs | 149 + Runtime/Protobuf-net/BufferPool.cs.meta | 11 + Runtime/Protobuf-net/CallbackAttributes.cs | 33 + .../Protobuf-net/CallbackAttributes.cs.meta | 11 + Runtime/Protobuf-net/Compiler.meta | 8 + .../Protobuf-net/Compiler/CompilerContext.cs | 1435 +++++++++ .../Compiler/CompilerContext.cs.meta | 11 + .../Compiler/CompilerDelegates.cs | 7 + .../Compiler/CompilerDelegates.cs.meta | 11 + Runtime/Protobuf-net/Compiler/Local.cs | 58 + Runtime/Protobuf-net/Compiler/Local.cs.meta | 11 + Runtime/Protobuf-net/DataFormat.cs | 49 + Runtime/Protobuf-net/DataFormat.cs.meta | 11 + .../DiscriminatedUnion.Serializable.cs | 176 + .../DiscriminatedUnion.Serializable.cs.meta | 11 + Runtime/Protobuf-net/DiscriminatedUnion.cs | 416 +++ .../Protobuf-net/DiscriminatedUnion.cs.meta | 11 + Runtime/Protobuf-net/Extensible.cs | 284 ++ Runtime/Protobuf-net/Extensible.cs.meta | 11 + Runtime/Protobuf-net/ExtensibleUtil.cs | 118 + Runtime/Protobuf-net/ExtensibleUtil.cs.meta | 11 + Runtime/Protobuf-net/GlobalSuppressions.cs | Bin 0 -> 2750 bytes .../Protobuf-net/GlobalSuppressions.cs.meta | 11 + Runtime/Protobuf-net/Helpers.cs | 638 ++++ Runtime/Protobuf-net/Helpers.cs.meta | 11 + Runtime/Protobuf-net/IExtensible.cs | 23 + Runtime/Protobuf-net/IExtensible.cs.meta | 11 + Runtime/Protobuf-net/IExtension.cs | 58 + Runtime/Protobuf-net/IExtension.cs.meta | 11 + Runtime/Protobuf-net/IProtoInputT.cs | 13 + Runtime/Protobuf-net/IProtoInputT.cs.meta | 11 + Runtime/Protobuf-net/IProtoOutputT.cs | 55 + Runtime/Protobuf-net/IProtoOutputT.cs.meta | 11 + Runtime/Protobuf-net/ImplicitFields.cs | 29 + Runtime/Protobuf-net/ImplicitFields.cs.meta | 11 + Runtime/Protobuf-net/KeyValuePairProxy.cs | 44 + .../Protobuf-net/KeyValuePairProxy.cs.meta | 11 + Runtime/Protobuf-net/Meta.meta | 8 + Runtime/Protobuf-net/Meta/AttributeMap.cs | 108 + .../Protobuf-net/Meta/AttributeMap.cs.meta | 11 + Runtime/Protobuf-net/Meta/BasicList.cs | 267 ++ Runtime/Protobuf-net/Meta/BasicList.cs.meta | 11 + Runtime/Protobuf-net/Meta/CallbackSet.cs | 110 + Runtime/Protobuf-net/Meta/CallbackSet.cs.meta | 11 + Runtime/Protobuf-net/Meta/MetaType.cs | 2171 +++++++++++++ Runtime/Protobuf-net/Meta/MetaType.cs.meta | 11 + Runtime/Protobuf-net/Meta/ProtoSyntax.cs | 17 + Runtime/Protobuf-net/Meta/ProtoSyntax.cs.meta | 11 + Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs | 2036 ++++++++++++ .../Meta/RuntimeTypeModel.cs.meta | 11 + Runtime/Protobuf-net/Meta/SubType.cs | 97 + Runtime/Protobuf-net/Meta/SubType.cs.meta | 11 + .../Protobuf-net/Meta/TypeAddedEventArgs.cs | 33 + .../Meta/TypeAddedEventArgs.cs.meta | 11 + .../Protobuf-net/Meta/TypeFormatEventArgs.cs | 64 + .../Meta/TypeFormatEventArgs.cs.meta | 11 + .../Meta/TypeModel.InputOutput.cs | 45 + .../Meta/TypeModel.InputOutput.cs.meta | 11 + Runtime/Protobuf-net/Meta/TypeModel.cs | 1696 ++++++++++ Runtime/Protobuf-net/Meta/TypeModel.cs.meta | 11 + Runtime/Protobuf-net/Meta/ValueMember.cs | 855 +++++ Runtime/Protobuf-net/Meta/ValueMember.cs.meta | 11 + Runtime/Protobuf-net/NetObjectCache.cs | 190 ++ Runtime/Protobuf-net/NetObjectCache.cs.meta | 11 + Runtime/Protobuf-net/PrefixStyle.cs | 26 + Runtime/Protobuf-net/PrefixStyle.cs.meta | 11 + .../Protobuf-net/ProtoContractAttribute.cs | 175 + .../ProtoContractAttribute.cs.meta | 11 + .../Protobuf-net/ProtoConverterAttribute.cs | 13 + .../ProtoConverterAttribute.cs.meta | 11 + Runtime/Protobuf-net/ProtoEnumAttribute.cs | 36 + .../Protobuf-net/ProtoEnumAttribute.cs.meta | 11 + Runtime/Protobuf-net/ProtoException.cs | 30 + Runtime/Protobuf-net/ProtoException.cs.meta | 11 + Runtime/Protobuf-net/ProtoIgnoreAttribute.cs | 40 + .../Protobuf-net/ProtoIgnoreAttribute.cs.meta | 11 + Runtime/Protobuf-net/ProtoIncludeAttribute.cs | 60 + .../ProtoIncludeAttribute.cs.meta | 11 + Runtime/Protobuf-net/ProtoMapAttribute.cs | 29 + .../Protobuf-net/ProtoMapAttribute.cs.meta | 11 + Runtime/Protobuf-net/ProtoMemberAttribute.cs | 228 ++ .../Protobuf-net/ProtoMemberAttribute.cs.meta | 11 + Runtime/Protobuf-net/ProtoReader.cs | 1444 +++++++++ Runtime/Protobuf-net/ProtoReader.cs.meta | 11 + Runtime/Protobuf-net/ProtoWriter.cs | 1003 ++++++ Runtime/Protobuf-net/ProtoWriter.cs.meta | 11 + Runtime/Protobuf-net/SerializationContext.cs | 76 + .../Protobuf-net/SerializationContext.cs.meta | 11 + Runtime/Protobuf-net/Serializer.cs | 514 +++ Runtime/Protobuf-net/Serializer.cs.meta | 11 + Runtime/Protobuf-net/Serializers.meta | 8 + .../Serializers/ArrayDecorator.cs | 310 ++ .../Serializers/ArrayDecorator.cs.meta | 11 + .../Serializers/BlobSerializer.cs | 59 + .../Serializers/BlobSerializer.cs.meta | 11 + .../Serializers/BooleanSerializer.cs | 41 + .../Serializers/BooleanSerializer.cs.meta | 11 + .../Serializers/ByteSerializer.cs | 42 + .../Serializers/ByteSerializer.cs.meta | 11 + .../Serializers/CharSerializer.cs | 32 + .../Serializers/CharSerializer.cs.meta | 11 + .../Serializers/CompiledSerializer.cs | 88 + .../Serializers/CompiledSerializer.cs.meta | 11 + .../Serializers/DateTimeSerializer.cs | 65 + .../Serializers/DateTimeSerializer.cs.meta | 11 + .../Serializers/DecimalSerializer.cs | 42 + .../Serializers/DecimalSerializer.cs.meta | 11 + .../Serializers/DefaultValueDecorator.cs | 259 ++ .../Serializers/DefaultValueDecorator.cs.meta | 11 + .../Serializers/DoubleSerializer.cs | 42 + .../Serializers/DoubleSerializer.cs.meta | 11 + .../Serializers/EnumSerializer.cs | 267 ++ .../Serializers/EnumSerializer.cs.meta | 11 + .../Serializers/FieldDecorator.cs | 104 + .../Serializers/FieldDecorator.cs.meta | 11 + .../Serializers/GuidSerializer.cs | 43 + .../Serializers/GuidSerializer.cs.meta | 11 + .../Serializers/IProtoSerializer.cs | 64 + .../Serializers/IProtoSerializer.cs.meta | 11 + .../Serializers/IProtoTypeSerializer.cs | 20 + .../Serializers/IProtoTypeSerializer.cs.meta | 11 + .../Serializers/ISerializerProxy.cs | 10 + .../Serializers/ISerializerProxy.cs.meta | 11 + .../ImmutableCollectionDecorator.cs | 304 ++ .../ImmutableCollectionDecorator.cs.meta | 11 + .../Serializers/Int16Serializer.cs | 42 + .../Serializers/Int16Serializer.cs.meta | 11 + .../Serializers/Int32Serializer.cs | 42 + .../Serializers/Int32Serializer.cs.meta | 11 + .../Serializers/Int64Serializer.cs | 41 + .../Serializers/Int64Serializer.cs.meta | 11 + .../Protobuf-net/Serializers/ListDecorator.cs | 579 ++++ .../Serializers/ListDecorator.cs.meta | 11 + .../Protobuf-net/Serializers/MapDecorator.cs | 298 ++ .../Serializers/MapDecorator.cs.meta | 11 + .../Serializers/MemberSpecifiedDecorator.cs | 76 + .../MemberSpecifiedDecorator.cs.meta | 11 + .../Serializers/NetObjectSerializer.cs | 64 + .../Serializers/NetObjectSerializer.cs.meta | 11 + .../Protobuf-net/Serializers/NullDecorator.cs | 167 + .../Serializers/NullDecorator.cs.meta | 11 + .../Serializers/ParseableSerializer.cs | 111 + .../Serializers/ParseableSerializer.cs.meta | 11 + .../Serializers/PropertyDecorator.cs | 167 + .../Serializers/PropertyDecorator.cs.meta | 11 + .../Serializers/ProtoDecoratorBase.cs | 24 + .../Serializers/ProtoDecoratorBase.cs.meta | 11 + .../Serializers/ReflectedUriDecorator.cs | 90 + .../Serializers/ReflectedUriDecorator.cs.meta | 11 + .../Serializers/SByteSerializer.cs | 45 + .../Serializers/SByteSerializer.cs.meta | 11 + .../Serializers/SingleSerializer.cs | 45 + .../Serializers/SingleSerializer.cs.meta | 11 + .../Serializers/StringSerializer.cs | 41 + .../Serializers/StringSerializer.cs.meta | 11 + .../Serializers/SubItemSerializer.cs | 138 + .../Serializers/SubItemSerializer.cs.meta | 11 + .../Serializers/SurrogateSerializer.cs | 157 + .../Serializers/SurrogateSerializer.cs.meta | 11 + .../Serializers/SystemTypeSerializer.cs | 46 + .../Serializers/SystemTypeSerializer.cs.meta | 11 + .../Protobuf-net/Serializers/TagDecorator.cs | 108 + .../Serializers/TagDecorator.cs.meta | 11 + .../Serializers/TimeSpanSerializer.cs | 63 + .../Serializers/TimeSpanSerializer.cs.meta | 11 + .../Serializers/TupleSerializer.cs | 339 ++ .../Serializers/TupleSerializer.cs.meta | 11 + .../Serializers/TypeSerializer.cs | 798 +++++ .../Serializers/TypeSerializer.cs.meta | 11 + .../Serializers/UInt16Serializer.cs | 43 + .../Serializers/UInt16Serializer.cs.meta | 11 + .../Serializers/UInt32Serializer.cs | 43 + .../Serializers/UInt32Serializer.cs.meta | 11 + .../Serializers/UInt64Serializer.cs | 43 + .../Serializers/UInt64Serializer.cs.meta | 11 + .../Protobuf-net/Serializers/UriDecorator.cs | 62 + .../Serializers/UriDecorator.cs.meta | 11 + Runtime/Protobuf-net/ServiceModel.meta | 8 + .../ServiceModel/ProtoBehaviorAttribute.cs | 35 + .../ProtoBehaviorAttribute.cs.meta | 11 + .../ProtoBehaviorExtensionElement.cs | 29 + .../ProtoBehaviorExtensionElement.cs.meta | 11 + .../ServiceModel/ProtoEndpointBehavior.cs | 82 + .../ProtoEndpointBehavior.cs.meta | 11 + .../ServiceModel/ProtoOperationBehavior.cs | 52 + .../ProtoOperationBehavior.cs.meta | 11 + .../ServiceModel/XmlProtoSerializer.cs | 208 ++ .../ServiceModel/XmlProtoSerializer.cs.meta | 11 + Runtime/Protobuf-net/SubItemToken.cs | 16 + Runtime/Protobuf-net/SubItemToken.cs.meta | 11 + Runtime/Protobuf-net/WireType.cs | 50 + Runtime/Protobuf-net/WireType.cs.meta | 11 + Runtime/csharp-kcp.meta | 8 + Runtime/csharp-kcp/Plugins.meta | 8 + .../csharp-kcp/Plugins/DotNetty.Buffers.dll | Bin 0 -> 168448 bytes .../Plugins/DotNetty.Buffers.dll.mdb | Bin 0 -> 119604 bytes .../Plugins/DotNetty.Buffers.dll.mdb.meta | 7 + .../Plugins/DotNetty.Buffers.dll.meta | 33 + .../csharp-kcp/Plugins/DotNetty.Buffers.pdb | Bin 0 -> 900608 bytes .../Plugins/DotNetty.Buffers.pdb.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Buffers.xml | 1978 ++++++++++++ .../Plugins/DotNetty.Buffers.xml.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Codecs.dll | Bin 0 -> 131584 bytes .../Plugins/DotNetty.Codecs.dll.mdb | Bin 0 -> 64387 bytes .../Plugins/DotNetty.Codecs.dll.mdb.meta | 7 + .../Plugins/DotNetty.Codecs.dll.meta | 33 + .../csharp-kcp/Plugins/DotNetty.Codecs.pdb | Bin 0 -> 464384 bytes .../Plugins/DotNetty.Codecs.pdb.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Codecs.xml | 646 ++++ .../Plugins/DotNetty.Codecs.xml.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Common.dll | Bin 0 -> 141824 bytes .../Plugins/DotNetty.Common.dll.mdb | Bin 0 -> 83912 bytes .../Plugins/DotNetty.Common.dll.mdb.meta | 7 + .../Plugins/DotNetty.Common.dll.meta | 33 + .../csharp-kcp/Plugins/DotNetty.Common.pdb | Bin 0 -> 734720 bytes .../Plugins/DotNetty.Common.pdb.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Common.xml | 2149 +++++++++++++ .../Plugins/DotNetty.Common.xml.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Handlers.dll | Bin 0 -> 44032 bytes .../Plugins/DotNetty.Handlers.dll.mdb | Bin 0 -> 20275 bytes .../Plugins/DotNetty.Handlers.dll.mdb.meta | 7 + .../Plugins/DotNetty.Handlers.dll.meta | 33 + .../csharp-kcp/Plugins/DotNetty.Handlers.pdb | Bin 0 -> 196096 bytes .../Plugins/DotNetty.Handlers.pdb.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Handlers.xml | 591 ++++ .../Plugins/DotNetty.Handlers.xml.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Transport.dll | Bin 0 -> 159744 bytes .../Plugins/DotNetty.Transport.dll.mdb | Bin 0 -> 90744 bytes .../Plugins/DotNetty.Transport.dll.mdb.meta | 7 + .../Plugins/DotNetty.Transport.dll.meta | 33 + .../csharp-kcp/Plugins/DotNetty.Transport.pdb | Bin 0 -> 796160 bytes .../Plugins/DotNetty.Transport.pdb.meta | 7 + .../csharp-kcp/Plugins/DotNetty.Transport.xml | 2464 ++++++++++++++ .../Plugins/DotNetty.Transport.xml.meta | 7 + Runtime/csharp-kcp/Plugins/DotNetty.Unity.dll | Bin 0 -> 7680 bytes .../csharp-kcp/Plugins/DotNetty.Unity.dll.mdb | Bin 0 -> 2079 bytes .../Plugins/DotNetty.Unity.dll.mdb.meta | 7 + .../Plugins/DotNetty.Unity.dll.meta | 33 + Runtime/csharp-kcp/Plugins/DotNetty.Unity.pdb | Bin 0 -> 30208 bytes .../Plugins/DotNetty.Unity.pdb.meta | 7 + Runtime/csharp-kcp/Plugins/DotNetty.Unity.xml | 8 + .../Plugins/DotNetty.Unity.xml.meta | 7 + Runtime/csharp-kcp/Plugins/LICENSE.txt | 45 + Runtime/csharp-kcp/Plugins/LICENSE.txt.meta | 7 + ...System.Runtime.CompilerServices.Unsafe.dll | Bin 0 -> 16776 bytes ...m.Runtime.CompilerServices.Unsafe.dll.meta | 33 + ...System.Runtime.CompilerServices.Unsafe.xml | 200 ++ ...m.Runtime.CompilerServices.Unsafe.xml.meta | 7 + Runtime/csharp-kcp/base-kcp.meta | 8 + Runtime/csharp-kcp/base-kcp/DelayPacket.cs | 42 + .../csharp-kcp/base-kcp/DelayPacket.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/Kcp.cs | 1841 +++++++++++ Runtime/csharp-kcp/base-kcp/Kcp.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/KcpOutput.cs | 9 + Runtime/csharp-kcp/base-kcp/KcpOutput.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/KcpUntils.cs | 13 + Runtime/csharp-kcp/base-kcp/KcpUntils.cs.meta | 11 + .../csharp-kcp/base-kcp/LatencySimulator.cs | 390 +++ .../base-kcp/LatencySimulator.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/Program.cs | 25 + Runtime/csharp-kcp/base-kcp/Program.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/Segment.cs | 177 + Runtime/csharp-kcp/base-kcp/Segment.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/fec.meta | 8 + .../base-kcp/fec/ByteBufCodingLoop.cs | 68 + .../base-kcp/fec/ByteBufCodingLoop.cs.meta | 11 + .../base-kcp/fec/ByteBufCodingLoopBase.cs | 39 + .../fec/ByteBufCodingLoopBase.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/fec/Fec.cs | 11 + Runtime/csharp-kcp/base-kcp/fec/Fec.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/fec/FecDecode.cs | 307 ++ .../csharp-kcp/base-kcp/fec/FecDecode.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/fec/FecEncode.cs | 178 ++ .../csharp-kcp/base-kcp/fec/FecEncode.cs.meta | 11 + .../csharp-kcp/base-kcp/fec/FecExpansion.cs | 188 ++ .../base-kcp/fec/FecExpansion.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/fec/FecPacket.cs | 59 + .../csharp-kcp/base-kcp/fec/FecPacket.cs.meta | 11 + .../fec/InputOutputByteBufTableCodingLoop.cs | 101 + .../InputOutputByteBufTableCodingLoop.cs.meta | 11 + Runtime/csharp-kcp/base-kcp/fec/Snmp.cs | 116 + Runtime/csharp-kcp/base-kcp/fec/Snmp.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp.meta | 8 + .../csharp-kcp/dotNetty-kcp/ChannelConfig.cs | 171 + .../dotNetty-kcp/ChannelConfig.cs.meta | 11 + .../dotNetty-kcp/ClientChannelHandler.cs | 33 + .../dotNetty-kcp/ClientChannelHandler.cs.meta | 11 + .../dotNetty-kcp/ClientConvChannelManager.cs | 66 + .../ClientConvChannelManager.cs.meta | 11 + .../ClientEndPointChannelManager.cs | 39 + .../ClientEndPointChannelManager.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/CloseTask.cs | 19 + .../csharp-kcp/dotNetty-kcp/CloseTask.cs.meta | 11 + .../dotNetty-kcp/CodecOutputList.cs | 39 + .../dotNetty-kcp/CodecOutputList.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/ConnectTask.cs | 31 + .../dotNetty-kcp/ConnectTask.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/Crc32.cs | 104 + Runtime/csharp-kcp/dotNetty-kcp/Crc32.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/Crc32OutPut.cs | 25 + .../dotNetty-kcp/Crc32OutPut.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/FecOutPut.cs | 32 + .../csharp-kcp/dotNetty-kcp/FecOutPut.cs.meta | 11 + .../dotNetty-kcp/IChannelManager.cs | 19 + .../dotNetty-kcp/IChannelManager.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/IScheduleTask.cs | 11 + .../dotNetty-kcp/IScheduleTask.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/KcpClient.cs | 192 ++ .../csharp-kcp/dotNetty-kcp/KcpClient.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/KcpListener.cs | 39 + .../dotNetty-kcp/KcpListener.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/KcpOutPutImp.cs | 20 + .../dotNetty-kcp/KcpOutPutImp.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/KcpServer.cs | 103 + .../csharp-kcp/dotNetty-kcp/KcpServer.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/ReadTask.cs | 112 + .../csharp-kcp/dotNetty-kcp/ReadTask.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/ScheduleTask.cs | 77 + .../dotNetty-kcp/ScheduleTask.cs.meta | 11 + .../dotNetty-kcp/ServerChannelHandler.cs | 102 + .../dotNetty-kcp/ServerChannelHandler.cs.meta | 11 + .../dotNetty-kcp/ServerConvChannelManager.cs | 65 + .../ServerConvChannelManager.cs.meta | 11 + .../ServerEndPointChannelManager.cs | 38 + .../ServerEndPointChannelManager.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/Ukcp.cs | 634 ++++ Runtime/csharp-kcp/dotNetty-kcp/Ukcp.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/User.cs | 50 + Runtime/csharp-kcp/dotNetty-kcp/User.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/WriteTask.cs | 91 + .../csharp-kcp/dotNetty-kcp/WriteTask.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/queue.meta | 8 + .../queue/ConcurrentCircularArrayQueue.cs | 73 + .../ConcurrentCircularArrayQueue.cs.meta | 11 + .../dotNetty-kcp/queue/MpscArrayQueue.cs | 319 ++ .../dotNetty-kcp/queue/MpscArrayQueue.cs.meta | 11 + .../dotNetty-kcp/queue/RefArrayAccessUtil.cs | 54 + .../queue/RefArrayAccessUtil.cs.meta | 11 + Runtime/csharp-kcp/dotNetty-kcp/thread.meta | 8 + .../thread/AbstratcMessageExecutor.cs | 96 + .../thread/AbstratcMessageExecutor.cs.meta | 11 + .../dotNetty-kcp/thread/AtomicBoolean.cs | 78 + .../dotNetty-kcp/thread/AtomicBoolean.cs.meta | 11 + .../thread/DistuptorMessageExecutor.cs | 44 + .../thread/DistuptorMessageExecutor.cs.meta | 11 + .../thread/EventLoopScheduleThread.cs | 25 + .../thread/EventLoopScheduleThread.cs.meta | 11 + .../dotNetty-kcp/thread/ExecutorPool.cs | 40 + .../dotNetty-kcp/thread/ExecutorPool.cs.meta | 11 + .../thread/HashedWheelScheduleThread.cs | 25 + .../thread/HashedWheelScheduleThread.cs.meta | 11 + .../dotNetty-kcp/thread/IExecutorPool.cs | 17 + .../dotNetty-kcp/thread/IExecutorPool.cs.meta | 11 + .../dotNetty-kcp/thread/IMessageExecutor.cs | 34 + .../thread/IMessageExecutor.cs.meta | 11 + .../dotNetty-kcp/thread/IScheduleThread.cs | 13 + .../thread/IScheduleThread.cs.meta | 11 + .../csharp-kcp/dotNetty-kcp/thread/ITask.cs | 7 + .../dotNetty-kcp/thread/ITask.cs.meta | 11 + .../thread/MessageExecutorTest.cs | 55 + .../thread/MessageExecutorTest.cs.meta | 11 + .../dotNetty-kcp/thread/RingBuffer.cs | 316 ++ .../dotNetty-kcp/thread/RingBuffer.cs.meta | 11 + .../thread/ThreadMessageExecutor.cs | 56 + .../thread/ThreadMessageExecutor.cs.meta | 11 + Runtime/csharp-kcp/reedsolomon_csharp.meta | 8 + .../reedsolomon_csharp/CodingLoop.cs | 69 + .../reedsolomon_csharp/CodingLoop.cs.meta | 11 + .../reedsolomon_csharp/CodingLoopBase.cs | 41 + .../reedsolomon_csharp/CodingLoopBase.cs.meta | 11 + .../csharp-kcp/reedsolomon_csharp/Galois.cs | 337 ++ .../reedsolomon_csharp/Galois.cs.meta | 11 + .../InputOutputByteTableCodingLoop.cs | 94 + .../InputOutputByteTableCodingLoop.cs.meta | 11 + .../csharp-kcp/reedsolomon_csharp/Matrix.cs | 336 ++ .../reedsolomon_csharp/Matrix.cs.meta | 11 + .../OutputInputByteTableCodingLoop.cs | 93 + .../OutputInputByteTableCodingLoop.cs.meta | 11 + .../csharp-kcp/reedsolomon_csharp/Program.cs | 17 + .../reedsolomon_csharp/Program.cs.meta | 11 + .../reedsolomon_csharp/ReedSolomon.cs | 414 +++ .../reedsolomon_csharp/ReedSolomon.cs.meta | 11 + .../ReedSolomonBenchmark.cs | 286 ++ .../ReedSolomonBenchmark.cs.meta | 11 + .../reedsolomon_csharp/ReedSolomonTest.cs | 202 ++ .../ReedSolomonTest.cs.meta | 11 + Assets/GuruKCP/package.json => package.json | 0 .../package.json.meta => package.json.meta | 0 433 files changed, 47828 insertions(+), 10 deletions(-) create mode 100644 .gitignore delete mode 100644 Assets/GuruKCP/Editor/__PLACEHOLDER__ delete mode 100644 Assets/GuruKCP/Editor/__PLACEHOLDER__.meta delete mode 100644 Assets/GuruKCP/Runtime/__PLACEHOLDER__ delete mode 100644 Assets/GuruKCP/Runtime/__PLACEHOLDER__.meta rename Assets/GuruKCP/Editor.meta => Editor.meta (100%) create mode 100644 Editor/GuruKCP.Editor.asmdef create mode 100644 Editor/GuruKCP.Editor.asmdef.meta create mode 100644 Editor/Proto2CSEditor.cs create mode 100644 Editor/Proto2CSEditor.cs.meta create mode 100644 Editor/protobuf-net.Reflection.meta create mode 100644 Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs create mode 100644 Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs.meta create mode 100644 Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs create mode 100644 Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs.meta create mode 100644 Editor/protobuf-net.Reflection/CodeGenerator.cs create mode 100644 Editor/protobuf-net.Reflection/CodeGenerator.cs.meta create mode 100644 Editor/protobuf-net.Reflection/CustomOptions.cs create mode 100644 Editor/protobuf-net.Reflection/CustomOptions.cs.meta create mode 100644 Editor/protobuf-net.Reflection/Descriptor.cs create mode 100644 Editor/protobuf-net.Reflection/Descriptor.cs.meta create mode 100644 Editor/protobuf-net.Reflection/NameNormalizer.cs create mode 100644 Editor/protobuf-net.Reflection/NameNormalizer.cs.meta create mode 100644 Editor/protobuf-net.Reflection/Parsers.cs create mode 100644 Editor/protobuf-net.Reflection/Parsers.cs.meta create mode 100644 Editor/protobuf-net.Reflection/Peekable.cs create mode 100644 Editor/protobuf-net.Reflection/Peekable.cs.meta create mode 100644 Editor/protobuf-net.Reflection/Token.cs create mode 100644 Editor/protobuf-net.Reflection/Token.cs.meta create mode 100644 Editor/protobuf-net.Reflection/TokenExtensions.cs create mode 100644 Editor/protobuf-net.Reflection/TokenExtensions.cs.meta create mode 100644 Editor/protobuf-net.Reflection/TokenType.cs create mode 100644 Editor/protobuf-net.Reflection/TokenType.cs.meta rename Assets/GuruKCP/README.md => README.md (81%) rename Assets/GuruKCP/README.md.meta => README.md.meta (100%) rename Assets/GuruKCP/Runtime.meta => Runtime.meta (100%) rename {Assets/GuruKCP/Runtime => Runtime}/GuruKCP.Runtime.asmdef (78%) rename {Assets/GuruKCP/Runtime => Runtime}/GuruKCP.Runtime.asmdef.meta (100%) rename {Assets/GuruKCP/Runtime => Runtime}/GuruKCP.cs (100%) rename {Assets/GuruKCP/Runtime => Runtime}/GuruKCP.cs.meta (100%) create mode 100644 Runtime/Protobuf-net.meta create mode 100644 Runtime/Protobuf-net/BclHelpers.cs create mode 100644 Runtime/Protobuf-net/BclHelpers.cs.meta create mode 100644 Runtime/Protobuf-net/BufferExtension.cs create mode 100644 Runtime/Protobuf-net/BufferExtension.cs.meta create mode 100644 Runtime/Protobuf-net/BufferPool.cs create mode 100644 Runtime/Protobuf-net/BufferPool.cs.meta create mode 100644 Runtime/Protobuf-net/CallbackAttributes.cs create mode 100644 Runtime/Protobuf-net/CallbackAttributes.cs.meta create mode 100644 Runtime/Protobuf-net/Compiler.meta create mode 100644 Runtime/Protobuf-net/Compiler/CompilerContext.cs create mode 100644 Runtime/Protobuf-net/Compiler/CompilerContext.cs.meta create mode 100644 Runtime/Protobuf-net/Compiler/CompilerDelegates.cs create mode 100644 Runtime/Protobuf-net/Compiler/CompilerDelegates.cs.meta create mode 100644 Runtime/Protobuf-net/Compiler/Local.cs create mode 100644 Runtime/Protobuf-net/Compiler/Local.cs.meta create mode 100644 Runtime/Protobuf-net/DataFormat.cs create mode 100644 Runtime/Protobuf-net/DataFormat.cs.meta create mode 100644 Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs create mode 100644 Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta create mode 100644 Runtime/Protobuf-net/DiscriminatedUnion.cs create mode 100644 Runtime/Protobuf-net/DiscriminatedUnion.cs.meta create mode 100644 Runtime/Protobuf-net/Extensible.cs create mode 100644 Runtime/Protobuf-net/Extensible.cs.meta create mode 100644 Runtime/Protobuf-net/ExtensibleUtil.cs create mode 100644 Runtime/Protobuf-net/ExtensibleUtil.cs.meta create mode 100644 Runtime/Protobuf-net/GlobalSuppressions.cs create mode 100644 Runtime/Protobuf-net/GlobalSuppressions.cs.meta create mode 100644 Runtime/Protobuf-net/Helpers.cs create mode 100644 Runtime/Protobuf-net/Helpers.cs.meta create mode 100644 Runtime/Protobuf-net/IExtensible.cs create mode 100644 Runtime/Protobuf-net/IExtensible.cs.meta create mode 100644 Runtime/Protobuf-net/IExtension.cs create mode 100644 Runtime/Protobuf-net/IExtension.cs.meta create mode 100644 Runtime/Protobuf-net/IProtoInputT.cs create mode 100644 Runtime/Protobuf-net/IProtoInputT.cs.meta create mode 100644 Runtime/Protobuf-net/IProtoOutputT.cs create mode 100644 Runtime/Protobuf-net/IProtoOutputT.cs.meta create mode 100644 Runtime/Protobuf-net/ImplicitFields.cs create mode 100644 Runtime/Protobuf-net/ImplicitFields.cs.meta create mode 100644 Runtime/Protobuf-net/KeyValuePairProxy.cs create mode 100644 Runtime/Protobuf-net/KeyValuePairProxy.cs.meta create mode 100644 Runtime/Protobuf-net/Meta.meta create mode 100644 Runtime/Protobuf-net/Meta/AttributeMap.cs create mode 100644 Runtime/Protobuf-net/Meta/AttributeMap.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/BasicList.cs create mode 100644 Runtime/Protobuf-net/Meta/BasicList.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/CallbackSet.cs create mode 100644 Runtime/Protobuf-net/Meta/CallbackSet.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/MetaType.cs create mode 100644 Runtime/Protobuf-net/Meta/MetaType.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/ProtoSyntax.cs create mode 100644 Runtime/Protobuf-net/Meta/ProtoSyntax.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs create mode 100644 Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/SubType.cs create mode 100644 Runtime/Protobuf-net/Meta/SubType.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs create mode 100644 Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs create mode 100644 Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs create mode 100644 Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/TypeModel.cs create mode 100644 Runtime/Protobuf-net/Meta/TypeModel.cs.meta create mode 100644 Runtime/Protobuf-net/Meta/ValueMember.cs create mode 100644 Runtime/Protobuf-net/Meta/ValueMember.cs.meta create mode 100644 Runtime/Protobuf-net/NetObjectCache.cs create mode 100644 Runtime/Protobuf-net/NetObjectCache.cs.meta create mode 100644 Runtime/Protobuf-net/PrefixStyle.cs create mode 100644 Runtime/Protobuf-net/PrefixStyle.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoContractAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoContractAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoConverterAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoConverterAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoEnumAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoEnumAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoException.cs create mode 100644 Runtime/Protobuf-net/ProtoException.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoIgnoreAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoIgnoreAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoIncludeAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoIncludeAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoMapAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoMapAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoMemberAttribute.cs create mode 100644 Runtime/Protobuf-net/ProtoMemberAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoReader.cs create mode 100644 Runtime/Protobuf-net/ProtoReader.cs.meta create mode 100644 Runtime/Protobuf-net/ProtoWriter.cs create mode 100644 Runtime/Protobuf-net/ProtoWriter.cs.meta create mode 100644 Runtime/Protobuf-net/SerializationContext.cs create mode 100644 Runtime/Protobuf-net/SerializationContext.cs.meta create mode 100644 Runtime/Protobuf-net/Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers.meta create mode 100644 Runtime/Protobuf-net/Serializers/ArrayDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/ArrayDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/BlobSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/BlobSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/BooleanSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/BooleanSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ByteSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/ByteSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/CharSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/CharSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/CompiledSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/CompiledSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/DecimalSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/DecimalSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/DoubleSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/DoubleSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/EnumSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/EnumSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/FieldDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/FieldDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/GuidSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/GuidSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/IProtoSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/IProtoSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ISerializerProxy.cs create mode 100644 Runtime/Protobuf-net/Serializers/ISerializerProxy.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/Int16Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/Int16Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/Int32Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/Int32Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/Int64Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/Int64Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ListDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/ListDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/MapDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/MapDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/NullDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/NullDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ParseableSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/ParseableSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/PropertyDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/PropertyDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs create mode 100644 Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/SByteSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/SByteSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/SingleSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/SingleSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/StringSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/StringSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/SubItemSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/SubItemSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/TagDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/TagDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/TupleSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/TupleSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/TypeSerializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/TypeSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/UInt16Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/UInt16Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/UInt32Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/UInt32Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/UInt64Serializer.cs create mode 100644 Runtime/Protobuf-net/Serializers/UInt64Serializer.cs.meta create mode 100644 Runtime/Protobuf-net/Serializers/UriDecorator.cs create mode 100644 Runtime/Protobuf-net/Serializers/UriDecorator.cs.meta create mode 100644 Runtime/Protobuf-net/ServiceModel.meta create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs create mode 100644 Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta create mode 100644 Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs create mode 100644 Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta create mode 100644 Runtime/Protobuf-net/SubItemToken.cs create mode 100644 Runtime/Protobuf-net/SubItemToken.cs.meta create mode 100644 Runtime/Protobuf-net/WireType.cs create mode 100644 Runtime/Protobuf-net/WireType.cs.meta create mode 100644 Runtime/csharp-kcp.meta create mode 100644 Runtime/csharp-kcp/Plugins.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll.mdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll.mdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.pdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.pdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.xml create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Buffers.xml.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.dll create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.dll.mdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.dll.mdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.pdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.pdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.xml create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Codecs.xml.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.dll create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.dll.mdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.dll.mdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.pdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.pdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.xml create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Common.xml.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.dll create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.dll.mdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.dll.mdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.pdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.pdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.xml create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Handlers.xml.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.dll create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.dll.mdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.dll.mdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.pdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.pdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.xml create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Transport.xml.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.dll create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.dll.mdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.dll.mdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.pdb create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.pdb.meta create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.xml create mode 100644 Runtime/csharp-kcp/Plugins/DotNetty.Unity.xml.meta create mode 100644 Runtime/csharp-kcp/Plugins/LICENSE.txt create mode 100644 Runtime/csharp-kcp/Plugins/LICENSE.txt.meta create mode 100644 Runtime/csharp-kcp/Plugins/System.Runtime.CompilerServices.Unsafe.dll create mode 100644 Runtime/csharp-kcp/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta create mode 100644 Runtime/csharp-kcp/Plugins/System.Runtime.CompilerServices.Unsafe.xml create mode 100644 Runtime/csharp-kcp/Plugins/System.Runtime.CompilerServices.Unsafe.xml.meta create mode 100644 Runtime/csharp-kcp/base-kcp.meta create mode 100644 Runtime/csharp-kcp/base-kcp/DelayPacket.cs create mode 100644 Runtime/csharp-kcp/base-kcp/DelayPacket.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/Kcp.cs create mode 100644 Runtime/csharp-kcp/base-kcp/Kcp.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/KcpOutput.cs create mode 100644 Runtime/csharp-kcp/base-kcp/KcpOutput.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/KcpUntils.cs create mode 100644 Runtime/csharp-kcp/base-kcp/KcpUntils.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/LatencySimulator.cs create mode 100644 Runtime/csharp-kcp/base-kcp/LatencySimulator.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/Program.cs create mode 100644 Runtime/csharp-kcp/base-kcp/Program.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/Segment.cs create mode 100644 Runtime/csharp-kcp/base-kcp/Segment.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/ByteBufCodingLoop.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/ByteBufCodingLoop.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/ByteBufCodingLoopBase.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/ByteBufCodingLoopBase.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/Fec.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/Fec.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecDecode.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecDecode.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecEncode.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecEncode.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecExpansion.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecExpansion.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecPacket.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/FecPacket.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/InputOutputByteBufTableCodingLoop.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/InputOutputByteBufTableCodingLoop.cs.meta create mode 100644 Runtime/csharp-kcp/base-kcp/fec/Snmp.cs create mode 100644 Runtime/csharp-kcp/base-kcp/fec/Snmp.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ChannelConfig.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ChannelConfig.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ClientChannelHandler.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ClientChannelHandler.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ClientConvChannelManager.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ClientConvChannelManager.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ClientEndPointChannelManager.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ClientEndPointChannelManager.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/CloseTask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/CloseTask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/CodecOutputList.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/CodecOutputList.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ConnectTask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ConnectTask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/Crc32.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/Crc32.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/Crc32OutPut.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/Crc32OutPut.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/FecOutPut.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/FecOutPut.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/IChannelManager.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/IChannelManager.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/IScheduleTask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/IScheduleTask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpClient.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpClient.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpListener.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpListener.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpOutPutImp.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpOutPutImp.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpServer.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/KcpServer.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ReadTask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ReadTask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ScheduleTask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ScheduleTask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ServerChannelHandler.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ServerChannelHandler.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ServerConvChannelManager.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ServerConvChannelManager.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ServerEndPointChannelManager.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/ServerEndPointChannelManager.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/Ukcp.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/Ukcp.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/User.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/User.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/WriteTask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/WriteTask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue/ConcurrentCircularArrayQueue.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue/ConcurrentCircularArrayQueue.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue/MpscArrayQueue.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue/MpscArrayQueue.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue/RefArrayAccessUtil.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/queue/RefArrayAccessUtil.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/AbstratcMessageExecutor.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/AbstratcMessageExecutor.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/AtomicBoolean.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/AtomicBoolean.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/DistuptorMessageExecutor.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/DistuptorMessageExecutor.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/EventLoopScheduleThread.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/EventLoopScheduleThread.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/ExecutorPool.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/ExecutorPool.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/HashedWheelScheduleThread.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/HashedWheelScheduleThread.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/IExecutorPool.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/IExecutorPool.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/IMessageExecutor.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/IMessageExecutor.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/IScheduleThread.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/IScheduleThread.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/ITask.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/ITask.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/MessageExecutorTest.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/MessageExecutorTest.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/RingBuffer.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/RingBuffer.cs.meta create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/ThreadMessageExecutor.cs create mode 100644 Runtime/csharp-kcp/dotNetty-kcp/thread/ThreadMessageExecutor.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/CodingLoop.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/CodingLoop.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/CodingLoopBase.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/CodingLoopBase.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/Galois.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/Galois.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/InputOutputByteTableCodingLoop.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/InputOutputByteTableCodingLoop.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/Matrix.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/Matrix.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/OutputInputByteTableCodingLoop.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/OutputInputByteTableCodingLoop.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/Program.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/Program.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/ReedSolomon.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/ReedSolomon.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/ReedSolomonBenchmark.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/ReedSolomonBenchmark.cs.meta create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/ReedSolomonTest.cs create mode 100644 Runtime/csharp-kcp/reedsolomon_csharp/ReedSolomonTest.cs.meta rename Assets/GuruKCP/package.json => package.json (100%) rename Assets/GuruKCP/package.json.meta => package.json.meta (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/Assets/GuruKCP/Editor/__PLACEHOLDER__ b/Assets/GuruKCP/Editor/__PLACEHOLDER__ deleted file mode 100644 index e69de29..0000000 diff --git a/Assets/GuruKCP/Editor/__PLACEHOLDER__.meta b/Assets/GuruKCP/Editor/__PLACEHOLDER__.meta deleted file mode 100644 index b5b54aa..0000000 --- a/Assets/GuruKCP/Editor/__PLACEHOLDER__.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 68f75ad8b6624e2ca86deea09d90452e -timeCreated: 1693367821 \ No newline at end of file diff --git a/Assets/GuruKCP/Runtime/__PLACEHOLDER__ b/Assets/GuruKCP/Runtime/__PLACEHOLDER__ deleted file mode 100644 index e69de29..0000000 diff --git a/Assets/GuruKCP/Runtime/__PLACEHOLDER__.meta b/Assets/GuruKCP/Runtime/__PLACEHOLDER__.meta deleted file mode 100644 index f77dba7..0000000 --- a/Assets/GuruKCP/Runtime/__PLACEHOLDER__.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0cce6bb95d7a4c62a10114242afa5176 -timeCreated: 1693367793 \ No newline at end of file diff --git a/Assets/GuruKCP/Editor.meta b/Editor.meta similarity index 100% rename from Assets/GuruKCP/Editor.meta rename to Editor.meta diff --git a/Editor/GuruKCP.Editor.asmdef b/Editor/GuruKCP.Editor.asmdef new file mode 100644 index 0000000..33d0b06 --- /dev/null +++ b/Editor/GuruKCP.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Protobuf-net.Editor", + "rootNamespace": "", + "references": [ + "GUID:832e7ae06a4304a17a11ca2f7b21373d" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/GuruKCP.Editor.asmdef.meta b/Editor/GuruKCP.Editor.asmdef.meta new file mode 100644 index 0000000..82dbfb5 --- /dev/null +++ b/Editor/GuruKCP.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b8a90c429cd0f45168d25ef7957cc732 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Proto2CSEditor.cs b/Editor/Proto2CSEditor.cs new file mode 100644 index 0000000..81b9867 --- /dev/null +++ b/Editor/Proto2CSEditor.cs @@ -0,0 +1,248 @@ +// +// Proto2CSEditor.cs +// +// Author: +// JasonXuDeveloper(傑) +// +// Copyright (c) 2020 JEngine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.IO; +using Google.Protobuf.Reflection; +using ProtoBuf.Reflection; +using UnityEditor; +using UnityEngine; + +namespace Guru +{ + internal class Proto2CSEditor : EditorWindow + { + private static Proto2CSEditor win; + + //[MenuItem("Tools/Protobuf/Show Generate Window")] + //public static void ShowCSGenerateWindow() + //{ + // int index = Application.dataPath.LastIndexOf('/'); + // var proto_dir = $"{Application.dataPath.Substring(0, index)}/ServerProtos"; + // win = GetWindow("Proto2CS Generator"); + // win.folder = EditorUtility.OpenFolderPanel("Please select proto files directory", + // proto_dir, ""); + // win.minSize = new Vector2(500, 500); + // win.Show(); + //} + + [MenuItem("Tools/Protobuf/Generate All")] + public static void GenerateAllProtos() + { + int index = Application.dataPath.LastIndexOf('/'); + var proto_dir = $"{Application.dataPath.Substring(0, index)}/ServerProtos"; + var file_list = GetAllProtoFiles(proto_dir); + var dest_folder = $"{Application.dataPath}/../Assets/Scripts/NetworkGen"; + + if (Directory.Exists(dest_folder)) + { + Directory.Delete(dest_folder, true); + + // Just in case the system has output_folder still locked for deletion + while (Directory.Exists(dest_folder)) + { + System.Threading.Thread.Sleep(10); + } + } + + Directory.CreateDirectory(dest_folder); + + Generate(proto_dir, file_list, dest_folder); + } + + [MenuItem("Tools/Protobuf/View Proto Files")] + private static void ViewDataPath() + { + int index = Application.dataPath.LastIndexOf('/'); + var proto_dir = $"{Application.dataPath.Substring(0, index)}/ServerProtos"; + + if (!Directory.Exists(proto_dir)) + { + Directory.CreateDirectory(proto_dir); + } + + EditorUtility.OpenWithDefaultApp(proto_dir); + } + + [SerializeField] protected string[] _fileList = new string[0]; + protected string folder; + protected SerializedObject _serializedObject; + protected SerializedProperty _fileListProperty; + + + protected void OnEnable() + { + //使用当前类初始化 + _serializedObject = new SerializedObject(this); + //获取当前类中可序列话的属性 + _fileListProperty = _serializedObject.FindProperty("_fileList"); + } + + protected void OnGUI() + { + //绘制标题 + GUILayout.Space(10); + GUIStyle textStyle = new GUIStyle(); + textStyle.fontSize = 24; + textStyle.normal.textColor = Color.white; + textStyle.alignment = TextAnchor.MiddleCenter; + GUILayout.Label("Proto文件转CS文件", textStyle); + textStyle.fontSize = 18; + GUILayout.Label("Proto file to CS file", textStyle); + GUILayout.Space(10); + + /* + * 路径 + */ + GUILayout.Label("Proto file folder Proto文件路径"); + GUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(true); + folder = EditorGUILayout.TextField(folder); + EditorGUI.EndDisabledGroup(); + + GUILayout.Space(10); + if (GUILayout.Button("Select Path 选择路径", GUILayout.ExpandWidth(false))) + { + int index = Application.dataPath.LastIndexOf('/'); + var proto_dir = $"{Application.dataPath.Substring(0, index)}/ServerProtos"; + + folder = EditorUtility.OpenFolderPanel("Select proto files source 请选择proto文件路径", proto_dir, ""); + } + + GUILayout.EndHorizontal(); + + /* + * 文件 + */ + GUILayout.Space(10); + GUILayout.Label("Files to convert 需转换文件"); + //更新 + _serializedObject.Update(); + //开始检查是否有修改 + EditorGUI.BeginChangeCheck(); + //显示属性 + EditorGUILayout.PropertyField(_fileListProperty, true); + + //结束检查是否有修改 + if (EditorGUI.EndChangeCheck()) + { + //提交修改 + _serializedObject.ApplyModifiedProperties(); + } + + /* + * 按钮 + */ + GUILayout.Space(50); + if (GUILayout.Button("Match all files from folder 从文件夹中匹配全部文件")) + { + _fileList = GetAllProtoFiles(folder); + _serializedObject.Update(); + } + + GUILayout.Space(10); + if (GUILayout.Button("Generate 生成")) + { + var dest_folder = $"{Application.dataPath}/Gen/Network"; + //Generate(folder, _fileList, dest_folder); + } + } + + private static string[] GetAllProtoFiles(string path) + { + if (string.IsNullOrEmpty(path)) + { + Debug.LogError($"Folder path is empty!"); + return null; + } + + var file_list = Directory.GetFiles(path, "*.proto", SearchOption.AllDirectories); + var file_name_list = new string[file_list.Length]; + + for (int i = 0; i < file_list.Length; i++) + { + file_name_list[i] = Path.GetFileName(file_list[i]); + } + + return file_name_list; + } + + private static void Generate(string inpath, string[] inprotos, string outpath) + { + if (!Directory.Exists(outpath)) + { + Directory.CreateDirectory(outpath); + } + + var set = new FileDescriptorSet(); + set.AddImportPath(inpath); + foreach (var inproto in inprotos) + { + var s = inproto; + if (!inproto.Contains(".proto")) + { + s += ".proto"; + } + + set.Add(s, true); + } + + set.Process(); + var errors = set.GetErrors(); + CSharpCodeGenerator.ClearTypeNames(); + var files = CSharpCodeGenerator.Default.Generate(set); + + foreach (var file in files) + { + CSharpCodeGenerator.ClearTypeNames(); + var full_file_name = file.Name; + int index = full_file_name.LastIndexOf('.'); + var file_name = index > 0 ? full_file_name.Substring(0, index) : full_file_name; + file_name = file_name.ToLower(); + var dest_filename = $"{NameNormalizer.AutoCapitalize(file_name)}.cs"; + //var path = Path.Combine(outpath, file.Name); + var path = Path.Combine(outpath, dest_filename); + File.WriteAllText(path, file.Text); + + Debug.Log($"Generated cs file for {full_file_name.Replace(".cs", ".proto")} successfully to: {path}"); + } + + EditorUtility.DisplayDialog("Complete", + "Proto文件已转CS,详细请看控制台输出" + + "\n" + + "Proto files has been convert into CS files, please go to console and view details", + "Close window"); + + if (win != null) + { + win.Close(); + } + + AssetDatabase.Refresh(); + } + + } + +} \ No newline at end of file diff --git a/Editor/Proto2CSEditor.cs.meta b/Editor/Proto2CSEditor.cs.meta new file mode 100644 index 0000000..9a2b4b7 --- /dev/null +++ b/Editor/Proto2CSEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0995aad32e8324ae48ae9e676c5bf38c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection.meta b/Editor/protobuf-net.Reflection.meta new file mode 100644 index 0000000..cd4629d --- /dev/null +++ b/Editor/protobuf-net.Reflection.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6def9201877ce4d4797b547c57cdeee0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs b/Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs new file mode 100644 index 0000000..9a91dc4 --- /dev/null +++ b/Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs @@ -0,0 +1,787 @@ +using Google.Protobuf.Reflection; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace ProtoBuf.Reflection +{ + /// + /// A coded generator that writes C# + /// + public class CSharpCodeGenerator : CommonCodeGenerator + { + /// + /// Reusable code-generator instance + /// + public static CSharpCodeGenerator Default { get; } = new CSharpCodeGenerator(); + /// + /// Create a new CSharpCodeGenerator instance + /// + protected CSharpCodeGenerator() { } + /// + /// Returns the language name + /// + public override string Name => "C#"; + /// + /// Returns the default file extension + /// + protected override string DefaultFileExtension => "cs"; + /// + /// Escapes language keywords + /// + protected override string Escape(string identifier) + { + switch (identifier) + { + case "abstract": + case "event": + case "new": + case "struct": + case "as": + case "explicit": + case "null": + case "switch": + case "base": + case "extern": + case "object": + case "this": + case "bool": + case "false": + case "operator": + case "throw": + case "break": + case "finally": + case "out": + case "true": + case "byte": + case "fixed": + case "override": + case "try": + case "case": + case "float": + case "params": + case "typeof": + case "catch": + case "for": + case "private": + case "uint": + case "char": + case "foreach": + case "protected": + case "ulong": + case "checked": + case "goto": + case "public": + case "unchecked": + case "class": + case "if": + case "readonly": + case "unsafe": + case "const": + case "implicit": + case "ref": + case "ushort": + case "continue": + case "in": + case "return": + case "using": + case "decimal": + case "int": + case "sbyte": + case "virtual": + case "default": + case "interface": + case "sealed": + case "volatile": + case "delegate": + case "internal": + case "short": + case "void": + case "do": + case "is": + case "sizeof": + case "while": + case "double": + case "lock": + case "stackalloc": + case "else": + case "long": + case "static": + case "enum": + case "namespace": + case "string": + return "@" + identifier; + default: + return identifier; + } + } + /// + /// Start a file + /// + protected override void WriteFileHeader(GeneratorContext ctx, FileDescriptorProto file, ref object state) + { + ctx.WriteLine("// This file was generated by a tool; you should avoid making direct changes.") + .WriteLine("// Consider using 'partial classes' to extend these types") + .WriteLine($"// Input: {Path.GetFileName(ctx.File.Name)}").WriteLine() + .WriteLine("#pragma warning disable CS1591, CS0612, CS3021").WriteLine(); + + + var @namespace = ctx.NameNormalizer.GetName(file); + + if (!string.IsNullOrWhiteSpace(@namespace)) + { + state = @namespace; + ctx.WriteLine($"namespace {@namespace}"); + ctx.WriteLine("{").Indent().WriteLine(); + } + + } + /// + /// End a file + /// + protected override void WriteFileFooter(GeneratorContext ctx, FileDescriptorProto file, ref object state) + { + var @namespace = (string)state; + if (!string.IsNullOrWhiteSpace(@namespace)) + { + ctx.Outdent().WriteLine("}").WriteLine(); + } + + ctx.WriteLine("#pragma warning restore CS1591, CS0612, CS3021"); + } + /// + /// Start an enum + /// + protected override void WriteEnumHeader(GeneratorContext ctx, EnumDescriptorProto obj, ref object state) + { + var name = ctx.NameNormalizer.GetName(obj); + var tw = ctx.Write($@"[global::ProtoBuf.ProtoContract("); + if (name != obj.Name) tw.Write($@"Name = @""{obj.Name}"""); + tw.WriteLine(")]"); + WriteOptions(ctx, obj.Options); + ctx.WriteLine($"{GetAccess(GetAccess(obj))} enum {Escape(name)}").WriteLine("{").Indent(); + } + /// + /// End an enum + /// + + protected override void WriteEnumFooter(GeneratorContext ctx, EnumDescriptorProto obj, ref object state) + { + ctx.Outdent().WriteLine("}").WriteLine(); + } + /// + /// Write an enum value + /// + protected override void WriteEnumValue(GeneratorContext ctx, EnumValueDescriptorProto obj, ref object state) + { + var name = ctx.NameNormalizer.GetName(obj); + if (name != obj.Name) + { + var tw = ctx.Write($@"[global::ProtoBuf.ProtoEnum("); + tw.Write($@"Name = @""{obj.Name}"""); + tw.WriteLine(")]"); + } + + WriteOptions(ctx, obj.Options); + ctx.WriteLine($"{Escape(name)} = {obj.Number},"); + } + + /// + /// End a message + /// + protected override void WriteMessageFooter(GeneratorContext ctx, DescriptorProto obj, ref object state) + { + ctx.Outdent().WriteLine("}").WriteLine(); + } + /// + /// Start a message + /// + protected override void WriteMessageHeader(GeneratorContext ctx, DescriptorProto obj, ref object state) + { + var name = ctx.NameNormalizer.GetName(obj); + GetTypeName2(obj.FullyQualifiedName); + var tw = ctx.Write($@"[global::ProtoBuf.ProtoContract("); + if (name != obj.Name) tw.Write($@"Name = @""{obj.Name}"""); + tw.WriteLine(")]"); + WriteOptions(ctx, obj.Options); + tw = ctx.Write($"{GetAccess(GetAccess(obj))} partial class {Escape(name)}"); + if (obj.ExtensionRanges.Count != 0) tw.Write(" : global::ProtoBuf.IExtensible"); + tw.WriteLine(); + ctx.WriteLine("{").Indent(); + if (obj.Options?.MessageSetWireFormat == true) + { + ctx.WriteLine("#error message_set_wire_format is not currently implemented").WriteLine(); + } + if (obj.ExtensionRanges.Count != 0) + { + ctx.WriteLine($"private global::ProtoBuf.IExtension {FieldPrefix}extensionData;") + .WriteLine($"global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)").Indent() + .WriteLine($"=> global::ProtoBuf.Extensible.GetExtensionObject(ref {FieldPrefix}extensionData, createIfMissing);").Outdent().WriteLine(); + } + } + + private static void WriteOptions(GeneratorContext ctx, T obj) where T : class, ISchemaOptions + { + if (obj == null) return; + if (obj.Deprecated) + { + ctx.WriteLine($"[global::System.Obsolete]"); + } + } + + const string FieldPrefix = "__pbn__"; + + /// + /// Get the language specific keyword representing an access level + /// + public override string GetAccess(Access access) + { + switch (access) + { + case Access.Internal: return "internal"; + case Access.Public: return "public"; + case Access.Private: return "private"; + default: return base.GetAccess(access); + } + } + static HashSet TypeNames2 = new HashSet(); + static string GetTypeName2(string type) { + if (type.StartsWith(".")) { type = type.Substring(1); } + TypeNames2.Add(type); + return type; + } + public static void ClearTypeNames(){ + TypeNames2.Clear (); + } + /// + /// Write a field + /// + protected override void WriteField(GeneratorContext ctx, FieldDescriptorProto obj, ref object state, OneOfStub[] oneOfs) + { + string dataFormat; + var typeName = GetTypeName(ctx, obj, out dataFormat, out var isMap); + if (isMap) + { + return; + } + var name = ctx.NameNormalizer.GetName(obj); + var tw = ctx.Write($@"[global::ProtoBuf.ProtoMember({obj.Number}"); + + if (!string.IsNullOrWhiteSpace(dataFormat)) + { + tw.Write($", (int)global::ProtoBuf.DataFormat.{dataFormat}"); + } + if (name != obj.Name) + { + tw.Write($@", Name = @""{obj.Name}"""); + } + var options = obj.Options?.GetOptions(); + if (options?.AsReference == true) + { + tw.Write($@", AsReference = true"); + } + if (options?.DynamicType == true) + { + tw.Write($@", DynamicType = true"); + } + + bool isOptional = obj.label == FieldDescriptorProto.Label.LabelOptional; + bool isRepeated = obj.label == FieldDescriptorProto.Label.LabelRepeated; + // Only needed by ILRuntime + /*if (isRepeated && obj.type == FieldDescriptorProto.Type.TypeMessage) + { + tw.Write($@", TypeName = ""{GetTypeName2(obj.TypeName)}"""); + }*/ + OneOfStub oneOf = obj.ShouldSerializeOneofIndex() ? oneOfs?[obj.OneofIndex] : null; + if (oneOf != null && oneOf.CountTotal == 1) + { + oneOf = null; // not really a one-of, then! + } + bool explicitValues = isOptional && oneOf == null && ctx.Syntax == FileDescriptorProto.SyntaxProto2 + && obj.type != FieldDescriptorProto.Type.TypeMessage + && obj.type != FieldDescriptorProto.Type.TypeGroup; + + + string defaultValue = null; + bool suppressDefaultAttribute = !isOptional; + if (isOptional || obj.type == FieldDescriptorProto.Type.TypeEnum) + { + //GetTypeName2(obj.TypeName); + defaultValue = obj.DefaultValue; + + if (obj.type == FieldDescriptorProto.Type.TypeString) + { + defaultValue = string.IsNullOrEmpty(defaultValue) ? "\"\"" + : ("@\"" + (defaultValue ?? "").Replace("\"", "\"\"") + "\""); + } + else if (obj.type == FieldDescriptorProto.Type.TypeDouble) + { + switch (defaultValue) + { + case "inf": defaultValue = "double.PositiveInfinity"; break; + case "-inf": defaultValue = "double.NegativeInfinity"; break; + case "nan": defaultValue = "double.NaN"; break; + } + } + else if (obj.type == FieldDescriptorProto.Type.TypeFloat) + { + switch (defaultValue) + { + case "inf": defaultValue = "float.PositiveInfinity"; break; + case "-inf": defaultValue = "float.NegativeInfinity"; break; + case "nan": defaultValue = "float.NaN"; break; + } + } + else if (obj.type == FieldDescriptorProto.Type.TypeEnum) + { + var enumType = ctx.TryFind(obj.TypeName); + if (enumType != null) + { + EnumValueDescriptorProto found = null; + if (!string.IsNullOrEmpty(defaultValue)) + { + found = enumType.Values.FirstOrDefault(x => x.Name == defaultValue); + } + else if (ctx.Syntax == FileDescriptorProto.SyntaxProto2) + { + // find the first one; if that is a zero, we don't need it after all + found = enumType.Values.FirstOrDefault(); + if(found != null && found.Number == 0) + { + if(!isOptional) found = null; // we don't need it after all + } + } + // for proto3 the default is 0, so no need to do anything - GetValueOrDefault() will do it all + + if (found != null) + { + defaultValue = ctx.NameNormalizer.GetName(found); + } + if (!string.IsNullOrWhiteSpace(defaultValue)) + { + //defaultValue = ctx.NameNormalizer.GetName(enumType) + "." + defaultValue; + + defaultValue = "global::"+enumType.FullyQualifiedName.Substring(1) + "." + defaultValue; + } + } + } + } + + if (obj.IsPacked(ctx.Syntax)) + { + tw.Write($", IsPacked = true"); + } + if (obj.label == FieldDescriptorProto.Label.LabelRequired) + { + tw.Write($", IsRequired = true"); + } + tw.WriteLine(")]"); + if (!isRepeated && !string.IsNullOrWhiteSpace(defaultValue) && !suppressDefaultAttribute) + { + ctx.WriteLine($"[global::System.ComponentModel.DefaultValue({defaultValue})]"); + } + WriteOptions(ctx, obj.Options); + if (isRepeated) + { + var mapMsgType = isMap ? ctx.TryFind(obj.TypeName) : null; + if (mapMsgType != null) + { + string keyDataFormat; + bool _; + var keyTypeName = GetTypeName(ctx, mapMsgType.Fields.Single(x => x.Number == 1), + out keyDataFormat, out _); + string valueDataFormat; + var valueTypeName = GetTypeName(ctx, mapMsgType.Fields.Single(x => x.Number == 2), + out valueDataFormat, out _); + + bool first = true; + tw = ctx.Write($"[global::ProtoBuf.ProtoMap"); + if (!string.IsNullOrWhiteSpace(keyDataFormat)) + { + tw.Write($"{(first ? "(" : ", ")}KeyFormat = global::ProtoBuf.DataFormat.{keyDataFormat}"); + first = false; + } + if (!string.IsNullOrWhiteSpace(valueDataFormat)) + { + tw.Write($"{(first ? "(" : ", ")}ValueFormat = global::ProtoBuf.DataFormat.{valueDataFormat}"); + first = false; + } + tw.WriteLine(first ? "]" : ")]"); + ctx.WriteLine($"{GetAccess(GetAccess(obj))} global::System.Collections.Generic.Dictionary<{keyTypeName}, {valueTypeName}> {Escape(name)} {{ get; }} = new global::System.Collections.Generic.Dictionary<{keyTypeName}, {valueTypeName}>();"); + } + else if (UseArray(obj)) + { + ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName}[] {Escape(name)} {{ get; set; }}"); + } + else + { + ctx.WriteLine($"{GetAccess(GetAccess(obj))} global::System.Collections.Generic.List<{typeName}> {Escape(name)} {{ get; }} = new global::System.Collections.Generic.List<{typeName}>();"); + } + } + else if (oneOf != null) + { + var defValue = string.IsNullOrWhiteSpace(defaultValue) ? $"default({typeName})" : defaultValue; + var fieldName = FieldPrefix + oneOf.OneOf.Name; + var storage = oneOf.GetStorage(obj.type, obj.TypeName); + ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)}").WriteLine("{").Indent(); + + switch (obj.type) + { + case FieldDescriptorProto.Type.TypeMessage: + case FieldDescriptorProto.Type.TypeGroup: + case FieldDescriptorProto.Type.TypeEnum: + case FieldDescriptorProto.Type.TypeBytes: + case FieldDescriptorProto.Type.TypeString: + ctx.WriteLine($"get {{ return {fieldName}.Is({obj.Number}) ? (({typeName}){fieldName}.{storage}) : {defValue}; }}"); + break; + default: + ctx.WriteLine($"get {{ return {fieldName}.Is({obj.Number}) ? {fieldName}.{storage} : {defValue}; }}"); + break; + } + var unionType = oneOf.GetUnionType(); + ctx.WriteLine($"set {{ {fieldName} = new global::ProtoBuf.{unionType}({obj.Number}, value); }}") + .Outdent().WriteLine("}") + .WriteLine($"{GetAccess(GetAccess(obj))} bool ShouldSerialize{name}() => {fieldName}.Is({obj.Number});") + .WriteLine($"{GetAccess(GetAccess(obj))} void Reset{name}() => global::ProtoBuf.{unionType}.Reset(ref {fieldName}, {obj.Number});"); + + if (oneOf.IsFirst()) + { + ctx.WriteLine().WriteLine($"private global::ProtoBuf.{unionType} {fieldName};"); + } + } + else if (explicitValues) + { + string fieldName = FieldPrefix + name, fieldType; + bool isRef = false; + switch (obj.type) + { + case FieldDescriptorProto.Type.TypeString: + case FieldDescriptorProto.Type.TypeBytes: + fieldType = typeName; + isRef = true; + break; + default: + fieldType = typeName + "?"; + break; + } + ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)}").WriteLine("{").Indent(); + tw = ctx.Write($"get {{ return {fieldName}"); + if (!string.IsNullOrWhiteSpace(defaultValue)) + { + tw.Write(" ?? "); + tw.Write(defaultValue); + } + else if (!isRef) + { + tw.Write(".GetValueOrDefault()"); + } + tw.WriteLine("; }"); + ctx.WriteLine($"set {{ {fieldName} = value; }}") + .Outdent().WriteLine("}") + .WriteLine($"{GetAccess(GetAccess(obj))} bool ShouldSerialize{name}() => {fieldName} != null;") + .WriteLine($"{GetAccess(GetAccess(obj))} void Reset{name}() => {fieldName} = null;") + .WriteLine($"private {fieldType} {fieldName};"); + } + else + { + tw = ctx.Write($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)} {{ get; set; }}"); + if (!string.IsNullOrWhiteSpace(defaultValue)) tw.Write($" = {defaultValue};"); + tw.WriteLine(); + } + ctx.WriteLine(); + } + /// + /// Starts an extgensions block + /// + protected override void WriteExtensionsHeader(GeneratorContext ctx, FileDescriptorProto obj, ref object state) + { + var name = obj?.Options?.GetOptions()?.ExtensionTypeName; + if (string.IsNullOrWhiteSpace(name)) name = "Extensions"; + ctx.WriteLine($"{GetAccess(GetAccess(obj))} static class {Escape(name)}").WriteLine("{").Indent(); + } + /// + /// Ends an extgensions block + /// + protected override void WriteExtensionsFooter(GeneratorContext ctx, FileDescriptorProto obj, ref object state) + { + ctx.Outdent().WriteLine("}"); + } + /// + /// Starts an extensions block + /// + protected override void WriteExtensionsHeader(GeneratorContext ctx, DescriptorProto obj, ref object state) + { + var name = obj?.Options?.GetOptions()?.ExtensionTypeName; + if (string.IsNullOrWhiteSpace(name)) name = "Extensions"; + ctx.WriteLine($"{GetAccess(GetAccess(obj))} static class {Escape(name)}").WriteLine("{").Indent(); + } + /// + /// Ends an extensions block + /// + protected override void WriteExtensionsFooter(GeneratorContext ctx, DescriptorProto obj, ref object state) + { + ctx.Outdent().WriteLine("}"); + } + /// + /// Write an extension + /// + protected override void WriteExtension(GeneratorContext ctx, FieldDescriptorProto field) + { + string dataFormat; + bool isMap; + var type = GetTypeName(ctx, field, out dataFormat, out isMap); + + if (isMap) + { + ctx.WriteLine("#error map extensions not yet implemented"); + } + else if (field.label == FieldDescriptorProto.Label.LabelRepeated) + { + ctx.WriteLine("#error repeated extensions not yet implemented"); + } + else + { + var msg = ctx.TryFind(field.Extendee); + var extendee = MakeRelativeName(field, msg, ctx.NameNormalizer); + + var @this = field.Parent is FileDescriptorProto ? "this " : ""; + string name = ctx.NameNormalizer.GetName(field); + var tw = ctx.WriteLine($"{GetAccess(GetAccess(field))} static {type} Get{name}({@this}{extendee} obj)") + .Write($"=> obj == null ? default({type}) : global::ProtoBuf.Extensible.GetValue<{type}>(obj, {field.Number}"); + if (!string.IsNullOrEmpty(dataFormat)) + { + tw.Write($", global::ProtoBuf.DataFormat.{dataFormat}"); + } + tw.WriteLine(");"); + ctx.WriteLine(); + // GetValue(IExtensible instance, int tag, DataFormat format) + } + } + + private static bool UseArray(FieldDescriptorProto field) + { + switch (field.type) + { + case FieldDescriptorProto.Type.TypeBool: + case FieldDescriptorProto.Type.TypeDouble: + case FieldDescriptorProto.Type.TypeFixed32: + case FieldDescriptorProto.Type.TypeFixed64: + case FieldDescriptorProto.Type.TypeFloat: + case FieldDescriptorProto.Type.TypeInt32: + case FieldDescriptorProto.Type.TypeInt64: + case FieldDescriptorProto.Type.TypeSfixed32: + case FieldDescriptorProto.Type.TypeSfixed64: + case FieldDescriptorProto.Type.TypeSint32: + case FieldDescriptorProto.Type.TypeSint64: + case FieldDescriptorProto.Type.TypeUint32: + case FieldDescriptorProto.Type.TypeUint64: + return true; + default: + return false; + } + } + + private string GetTypeName(GeneratorContext ctx, FieldDescriptorProto field, out string dataFormat, out bool isMap) + { + dataFormat = ""; + isMap = false; + switch (field.type) + { + case FieldDescriptorProto.Type.TypeDouble: + return "double"; + case FieldDescriptorProto.Type.TypeFloat: + return "float"; + case FieldDescriptorProto.Type.TypeBool: + return "bool"; + case FieldDescriptorProto.Type.TypeString: + return "string"; + case FieldDescriptorProto.Type.TypeSint32: + dataFormat = nameof(DataFormat.ZigZag); + return "int"; + case FieldDescriptorProto.Type.TypeInt32: + return "int"; + case FieldDescriptorProto.Type.TypeSfixed32: + dataFormat = nameof(DataFormat.FixedSize); + return "int"; + case FieldDescriptorProto.Type.TypeSint64: + dataFormat = nameof(DataFormat.ZigZag); + return "long"; + case FieldDescriptorProto.Type.TypeInt64: + return "long"; + case FieldDescriptorProto.Type.TypeSfixed64: + dataFormat = nameof(DataFormat.FixedSize); + return "long"; + case FieldDescriptorProto.Type.TypeFixed32: + dataFormat = nameof(DataFormat.FixedSize); + return "uint"; + case FieldDescriptorProto.Type.TypeUint32: + return "uint"; + case FieldDescriptorProto.Type.TypeFixed64: + dataFormat = nameof(DataFormat.FixedSize); + return "ulong"; + case FieldDescriptorProto.Type.TypeUint64: + return "ulong"; + case FieldDescriptorProto.Type.TypeBytes: + return "byte[]"; + case FieldDescriptorProto.Type.TypeEnum: + switch (field.TypeName) + { + case ".bcl.DateTime.DateTimeKind": + return "global::System.DateTimeKind"; + } + var enumType = ctx.TryFind(field.TypeName); + return MakeRelativeName(field, enumType, ctx.NameNormalizer); + case FieldDescriptorProto.Type.TypeGroup: + case FieldDescriptorProto.Type.TypeMessage: + switch (field.TypeName) + { + case WellKnownTypeTimestamp: + dataFormat = "WellKnown"; + return "global::System.DateTime?"; + case WellKnownTypeDuration: + dataFormat = "WellKnown"; + return "global::System.TimeSpan?"; + case ".bcl.NetObjectProxy": + return "object"; + case ".bcl.DateTime": + return "global::System.DateTime?"; + case ".bcl.TimeSpan": + return "global::System.TimeSpan?"; + case ".bcl.Decimal": + return "decimal?"; + case ".bcl.Guid": + return "global::System.Guid?"; + } + var msgType = ctx.TryFind(field.TypeName); + if (field.type == FieldDescriptorProto.Type.TypeGroup) + { + dataFormat = nameof(DataFormat.Group); + } + isMap = msgType?.Options?.MapEntry ?? false; + return MakeRelativeName(field, msgType, ctx.NameNormalizer); + default: + return field.TypeName; + } + } + + private string MakeRelativeName(FieldDescriptorProto field, IType target, NameNormalizer normalizer) + { + if (target == null) return Escape(field.TypeName); // the only thing we know + + var declaringType = field.Parent; + + if (declaringType is IType) + { + var name = FindNameFromCommonAncestor((IType)declaringType, target, normalizer); + if (!string.IsNullOrWhiteSpace(name)) return name; + } + return Escape(field.TypeName); // give up! + } + + // k, what we do is; we have two types; each knows the parent, but nothing else, so: + // for each, use a stack to build the ancestry tree - the "top" of the stack will be the + // package, the bottom of the stack will be the type itself. They will often be stacks + // of different heights. + // + // Find how many is in the smallest stack; now take that many items, in turn, until we + // get something that is different (at which point, put that one back on the stack), or + // we run out of items in one of the stacks. + // + // There are now two options: + // - we ran out of things in the "target" stack - in which case, they are common enough to not + // need any resolution - just give back the fixed name + // - we have things left in the "target" stack - in which case we have found a common ancestor, + // or the target is a descendent; either way, just concat what is left (including the package + // if the package itself was different) + + private string FindNameFromCommonAncestor(IType declaring, IType target, NameNormalizer normalizer) + { + // trivial case; asking for self, or asking for immediate child + if (ReferenceEquals(declaring, target) || ReferenceEquals(declaring, target.Parent)) + { + if (target is DescriptorProto) return Escape(normalizer.GetName((DescriptorProto)target)); + if (target is EnumDescriptorProto) return Escape(normalizer.GetName((EnumDescriptorProto)target)); + return null; + } + + var origTarget = target; + var xStack = new Stack(); + + while (declaring != null) + { + xStack.Push(declaring); + declaring = declaring.Parent; + } + var yStack = new Stack(); + + while (target != null) + { + yStack.Push(target); + target = target.Parent; + } + int lim = Math.Min(xStack.Count, yStack.Count); + for (int i = 0; i < lim; i++) + { + declaring = xStack.Peek(); + target = yStack.Pop(); + if (!ReferenceEquals(target, declaring)) + { + // special-case: if both are the package (file), and they have the same namespace: we're OK + if (target is FileDescriptorProto && declaring is FileDescriptorProto && + normalizer.GetName((FileDescriptorProto)declaring) == normalizer.GetName((FileDescriptorProto)target)) + { + // that's fine, keep going + } + else + { + // put it back + yStack.Push(target); + break; + } + } + } + // if we used everything, then the target is an ancestor-or-self + if (yStack.Count == 0) + { + target = origTarget; + if (target is DescriptorProto) return Escape(normalizer.GetName((DescriptorProto)target)); + if (target is EnumDescriptorProto) return Escape(normalizer.GetName((EnumDescriptorProto)target)); + return null; + } + + var sb = new StringBuilder(); + while (yStack.Count != 0) + { + target = yStack.Pop(); + + string nextName; + if (target is FileDescriptorProto) nextName = normalizer.GetName((FileDescriptorProto)target); + else if (target is DescriptorProto) nextName = normalizer.GetName((DescriptorProto)target); + else if (target is EnumDescriptorProto) nextName = normalizer.GetName((EnumDescriptorProto)target); + else return null; + + if (!string.IsNullOrWhiteSpace(nextName)) + { + if (sb.Length == 0 && target is FileDescriptorProto) sb.Append("global::"); + else if (sb.Length != 0) sb.Append('.'); + sb.Append(Escape(nextName)); + } + } + return sb.ToString(); + } + + static bool IsAncestorOrSelf(IType parent, IType child) + { + while (parent != null) + { + if (ReferenceEquals(parent, child)) return true; + parent = parent.Parent; + } + return false; + } + const string WellKnownTypeTimestamp = ".google.protobuf.Timestamp", + WellKnownTypeDuration = ".google.protobuf.Duration"; + } +} diff --git a/Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs.meta b/Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs.meta new file mode 100644 index 0000000..e66ae13 --- /dev/null +++ b/Editor/protobuf-net.Reflection/CSharpCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f18de68ea6964dc78de6459348acefe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs b/Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs new file mode 100644 index 0000000..cbf438d --- /dev/null +++ b/Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs @@ -0,0 +1,158 @@ +using Google.Protobuf.Reflection; + +namespace ProtoBuf.Reflection +{ + partial class CommonCodeGenerator + { + /// + /// Represents the union summary of a one-of declaration + /// + protected class OneOfStub + { + /// + /// The underlying descriptor + /// + public OneofDescriptorProto OneOf { get; } + + internal OneOfStub(OneofDescriptorProto decl) + { + OneOf = decl; + } + internal int Count32 { get; private set; } + internal int Count64 { get; private set; } + internal int Count128 { get; private set; } + internal int CountRef { get; private set; } + internal int CountTotal => CountRef + Count32 + Count64; + + private void AccountFor(FieldDescriptorProto.Type type, string typeName) + { + switch (type) + { + case FieldDescriptorProto.Type.TypeBool: + case FieldDescriptorProto.Type.TypeEnum: + case FieldDescriptorProto.Type.TypeFixed32: + case FieldDescriptorProto.Type.TypeFloat: + case FieldDescriptorProto.Type.TypeInt32: + case FieldDescriptorProto.Type.TypeSfixed32: + case FieldDescriptorProto.Type.TypeSint32: + case FieldDescriptorProto.Type.TypeUint32: + Count32++; + break; + case FieldDescriptorProto.Type.TypeDouble: + case FieldDescriptorProto.Type.TypeFixed64: + case FieldDescriptorProto.Type.TypeInt64: + case FieldDescriptorProto.Type.TypeSfixed64: + case FieldDescriptorProto.Type.TypeSint64: + case FieldDescriptorProto.Type.TypeUint64: + Count32++; + Count64++; + break; + case FieldDescriptorProto.Type.TypeMessage: + switch(typeName) + { + case ".google.protobuf.Timestamp": + case ".google.protobuf.Duration": + Count64++; + break; + case ".bcl.Guid": + Count128++; + break; + default: + CountRef++; + break; + } + break; + default: + CountRef++; + break; + } + } + internal string GetStorage(FieldDescriptorProto.Type type, string typeName) + { + switch (type) + { + case FieldDescriptorProto.Type.TypeBool: + return "Boolean"; + case FieldDescriptorProto.Type.TypeInt32: + case FieldDescriptorProto.Type.TypeSfixed32: + case FieldDescriptorProto.Type.TypeSint32: + case FieldDescriptorProto.Type.TypeFixed32: + case FieldDescriptorProto.Type.TypeEnum: + return "Int32"; + case FieldDescriptorProto.Type.TypeFloat: + return "Single"; + case FieldDescriptorProto.Type.TypeUint32: + return "UInt32"; + case FieldDescriptorProto.Type.TypeDouble: + return "Double"; + case FieldDescriptorProto.Type.TypeFixed64: + case FieldDescriptorProto.Type.TypeInt64: + case FieldDescriptorProto.Type.TypeSfixed64: + case FieldDescriptorProto.Type.TypeSint64: + return "Int64"; + case FieldDescriptorProto.Type.TypeUint64: + return "UInt64"; + case FieldDescriptorProto.Type.TypeMessage: + switch (typeName) + { + case ".google.protobuf.Timestamp": + return "DateTime"; + case ".google.protobuf.Duration": + return "TimeSpan"; + case ".bcl.Guid": + return "Guid"; + default: + return "Object"; + } + default: + return "Object"; + } + } + internal static OneOfStub[] Build(GeneratorContext context, DescriptorProto message) + { + if (message.OneofDecls.Count == 0) return null; + var stubs = new OneOfStub[message.OneofDecls.Count]; + int index = 0; + foreach (var decl in message.OneofDecls) + { + stubs[index++] = new OneOfStub(decl); + } + foreach (var field in message.Fields) + { + if (field.ShouldSerializeOneofIndex()) + { + stubs[field.OneofIndex].AccountFor(field.type, field.TypeName); + } + } + return stubs; + } + private bool isFirst = true; + internal bool IsFirst() + { + if (isFirst) + { + isFirst = false; + return true; + } + return false; + } + + internal string GetUnionType() + { + if (Count128 != 0) + { + return CountRef == 0 ? "DiscriminatedUnion128" : "DiscriminatedUnion128Object"; + } + if (Count64 != 0) + { + return CountRef == 0 ? "DiscriminatedUnion64" : "DiscriminatedUnion64Object"; + } + if (Count32 != 0) + { + return CountRef == 0 ? "DiscriminatedUnion32" : "DiscriminatedUnion32Object"; + } + return "DiscriminatedUnionObject"; + } + } + } +} diff --git a/Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs.meta b/Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs.meta new file mode 100644 index 0000000..c7bc17c --- /dev/null +++ b/Editor/protobuf-net.Reflection/CodeGenerator.OneOfStub.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff61418fb53f3488c9fe4974974b436e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/CodeGenerator.cs b/Editor/protobuf-net.Reflection/CodeGenerator.cs new file mode 100644 index 0000000..a8d848e --- /dev/null +++ b/Editor/protobuf-net.Reflection/CodeGenerator.cs @@ -0,0 +1,476 @@ +using Google.Protobuf.Reflection; +using System; +using System.Collections.Generic; +using System.IO; + +namespace ProtoBuf.Reflection +{ + /// + /// Abstract root for a general purpose code-generator + /// + public abstract class CodeGenerator + { + /// + /// The logical name of this code generator + /// + public abstract string Name { get; } + /// + /// Get a string representation of the instance + /// + public override string ToString() => Name; + + /// + /// Execute the code generator against a FileDescriptorSet, yielding a sequence of files + /// + public abstract IEnumerable Generate(FileDescriptorSet set, NameNormalizer normalizer = null); + + /// + /// Eexecute this code generator against a code file + /// + public CompilerResult Compile(CodeFile file) => Compile(new[] { file }); + /// + /// Eexecute this code generator against a set of code file + /// + public CompilerResult Compile(params CodeFile[] files) + { + var set = new FileDescriptorSet(); + foreach (var file in files) + { + using (var reader = new StringReader(file.Text)) + { + Console.WriteLine($"Parsing {file.Name}..."); + set.Add(file.Name, true, reader); + } + } + set.Process(); + var results = new List(); + var newErrors = new List(); + + try + { + results.AddRange(Generate(set)); + } + catch (Exception ex) + { + set.Errors.Add(new Error(default(Token), ex.Message, true)); + } + var errors = set.GetErrors(); + + return new CompilerResult(errors, results.ToArray()); + } + } + /// + /// Abstract base class for a code generator that uses a visitor pattern + /// + public abstract partial class CommonCodeGenerator : CodeGenerator + { + private Access? GetAccess(IType parent) + { + if (parent is DescriptorProto) + return GetAccess((DescriptorProto)parent); + if (parent is EnumDescriptorProto) + return GetAccess((EnumDescriptorProto)parent); + if (parent is FileDescriptorProto) + return GetAccess((FileDescriptorProto)parent); + return null; + } + /// + /// Obtain the access of an item, accounting for the model's hierarchy + /// + protected Access GetAccess(FileDescriptorProto obj) + => obj?.Options?.GetOptions()?.Access ?? Access.Public; + + static Access? NullIfInherit(Access? access) + => access == Access.Inherit ? null : access; + /// + /// Obtain the access of an item, accounting for the model's hierarchy + /// + protected Access GetAccess(DescriptorProto obj) + => NullIfInherit(obj?.Options?.GetOptions()?.Access) + ?? GetAccess(obj?.Parent) ?? Access.Public; + /// + /// Obtain the access of an item, accounting for the model's hierarchy + /// + protected Access GetAccess(FieldDescriptorProto obj) + => NullIfInherit(obj?.Options?.GetOptions()?.Access) + ?? GetAccess(obj?.Parent as IType) ?? Access.Public; + /// + /// Obtain the access of an item, accounting for the model's hierarchy + /// + protected Access GetAccess(EnumDescriptorProto obj) + => NullIfInherit(obj?.Options?.GetOptions()?.Access) + ?? GetAccess(obj?.Parent) ?? Access.Public; + /// + /// Get the textual name of a given access level + /// + public virtual string GetAccess(Access access) + => access.ToString(); + + /// + /// The indentation used by this code generator + /// + public virtual string Indent => " "; + /// + /// The file extension of the files generatred by this generator + /// + protected abstract string DefaultFileExtension { get; } + /// + /// Handle keyword escaping in the language of this code generator + /// + /// + /// + protected abstract string Escape(string identifier); + /// + /// Execute the code generator against a FileDescriptorSet, yielding a sequence of files + /// + public override IEnumerable Generate(FileDescriptorSet set, NameNormalizer normalizer = null) + { + foreach (var file in set.Files) + { + if (!file.IncludeInOutput) continue; + + var fileName = Path.ChangeExtension(file.Name, DefaultFileExtension); + + string generated; + using (var buffer = new StringWriter()) + { + var ctx = new GeneratorContext(file, normalizer ?? NameNormalizer.Default, buffer, Indent); + + ctx.BuildTypeIndex(); // populates for TryFind + WriteFile(ctx, file); + generated = buffer.ToString(); + } + yield return new CodeFile(fileName, generated); + + } + + } + + /// + /// Emits the code for a file in a descriptor-set + /// + protected virtual void WriteFile(GeneratorContext ctx, FileDescriptorProto obj) + { + var file = ctx.File; + object state = null; + WriteFileHeader(ctx, obj, ref state); + + foreach (var inner in file.MessageTypes) + { + WriteMessage(ctx, inner); + } + foreach (var inner in file.EnumTypes) + { + WriteEnum(ctx, inner); + } + foreach (var inner in file.Services) + { + WriteService(ctx, inner); + } + if(file.Extensions.Count != 0) + { + object extState = null; + WriteExtensionsHeader(ctx, file, ref extState); + foreach(var ext in file.Extensions) + { + WriteExtension(ctx, ext); + } + WriteExtensionsFooter(ctx, file, ref extState); + } + WriteFileFooter(ctx, obj, ref state); + } + /// + /// Emit code representing an extension field + /// + protected virtual void WriteExtension(GeneratorContext ctx, FieldDescriptorProto ext) { } + /// + /// Emit code preceeding a set of extension fields + /// + protected virtual void WriteExtensionsHeader(GeneratorContext ctx, FileDescriptorProto file, ref object state) { } + /// + /// Emit code following a set of extension fields + /// + protected virtual void WriteExtensionsFooter(GeneratorContext ctx, FileDescriptorProto file, ref object state) { } + /// + /// Emit code preceeding a set of extension fields + /// + protected virtual void WriteExtensionsHeader(GeneratorContext ctx, DescriptorProto file, ref object state) { } + /// + /// Emit code following a set of extension fields + /// + protected virtual void WriteExtensionsFooter(GeneratorContext ctx, DescriptorProto file, ref object state) { } + /// + /// Emit code representing a service + /// + protected virtual void WriteService(GeneratorContext ctx, ServiceDescriptorProto obj) + { + object state = null; + WriteServiceHeader(ctx, obj, ref state); + foreach (var inner in obj.Methods) + { + WriteServiceMethod(ctx, inner, ref state); + } + WriteServiceFooter(ctx, obj, ref state); + } + /// + /// Emit code following a set of service methods + /// + protected virtual void WriteServiceFooter(GeneratorContext ctx, ServiceDescriptorProto obj, ref object state) { } + + /// + /// Emit code representing a service method + /// + protected virtual void WriteServiceMethod(GeneratorContext ctx, MethodDescriptorProto inner, ref object state) { } + /// + /// Emit code following preceeding a set of service methods + /// + protected virtual void WriteServiceHeader(GeneratorContext ctx, ServiceDescriptorProto obj, ref object state) { } + /// + /// Check whether a particular message should be suppressed - for example because it represents a map + /// + protected virtual bool ShouldOmitMessage(GeneratorContext ctx, DescriptorProto obj, ref object state) + => obj.Options?.MapEntry ?? false; // don't write this type - use a dictionary instead + + /// + /// Emit code representing a message type + /// + protected virtual void WriteMessage(GeneratorContext ctx, DescriptorProto obj) + { + object state = null; + if (ShouldOmitMessage(ctx, obj, ref state)) return; + + WriteMessageHeader(ctx, obj, ref state); + var oneOfs = OneOfStub.Build(ctx, obj); + foreach (var inner in obj.Fields) + { + WriteField(ctx, inner, ref state, oneOfs); + } + foreach (var inner in obj.NestedTypes) + { + WriteMessage(ctx, inner); + } + foreach (var inner in obj.EnumTypes) + { + WriteEnum(ctx, inner); + } + if (obj.Extensions.Count != 0) + { + object extState = null; + WriteExtensionsHeader(ctx, obj, ref extState); + foreach (var ext in obj.Extensions) + { + WriteExtension(ctx, ext); + } + WriteExtensionsFooter(ctx, obj, ref extState); + } + WriteMessageFooter(ctx, obj, ref state); + } + /// + /// Emit code representing a message field + /// + protected abstract void WriteField(GeneratorContext ctx, FieldDescriptorProto obj, ref object state, OneOfStub[] oneOfs); + /// + /// Emit code following a set of message fields + /// + protected abstract void WriteMessageFooter(GeneratorContext ctx, DescriptorProto obj, ref object state); + /// + /// Emit code preceeding a set of message fields + /// + protected abstract void WriteMessageHeader(GeneratorContext ctx, DescriptorProto obj, ref object state); + /// + /// Emit code representing an enum type + /// + protected virtual void WriteEnum(GeneratorContext ctx, EnumDescriptorProto obj) + { + object state = null; + WriteEnumHeader(ctx, obj, ref state); + foreach (var inner in obj.Values) + { + WriteEnumValue(ctx, inner, ref state); + } + WriteEnumFooter(ctx, obj, ref state); + } + + /// + /// Emit code preceeding a set of enum values + /// + protected abstract void WriteEnumHeader(GeneratorContext ctx, EnumDescriptorProto obj, ref object state); + /// + /// Emit code representing an enum value + /// + protected abstract void WriteEnumValue(GeneratorContext ctx, EnumValueDescriptorProto obj, ref object state); + /// + /// Emit code following a set of enum values + /// + protected abstract void WriteEnumFooter(GeneratorContext ctx, EnumDescriptorProto obj, ref object state); + /// + /// Emit code at the start of a file + /// + protected virtual void WriteFileHeader(GeneratorContext ctx, FileDescriptorProto obj, ref object state) { } + /// + /// Emit code at the end of a file + /// + protected virtual void WriteFileFooter(GeneratorContext ctx, FileDescriptorProto obj, ref object state) { } + + /// + /// Represents the state of a code-generation invocation + /// + protected class GeneratorContext + { + /// + /// The file being processed + /// + public FileDescriptorProto File { get; } + /// + /// The token to use for indentation + /// + public string IndentToken { get; } + /// + /// The current indent level + /// + public int IndentLevel { get; private set; } + /// + /// The mechanism to use for name normalization + /// + public NameNormalizer NameNormalizer { get; } + /// + /// The output for this code generation + /// + public TextWriter Output { get; } + /// + /// The effective syntax of this code-generation cycle, defaulting to "proto2" if not explicity specified + /// + public string Syntax => string.IsNullOrWhiteSpace(File.Syntax) ? FileDescriptorProto.SyntaxProto2 : File.Syntax; + /// + /// Create a new GeneratorContext instance + /// + internal GeneratorContext(FileDescriptorProto file, NameNormalizer nameNormalizer, TextWriter output, string indentToken) + { + File = file; + NameNormalizer = nameNormalizer; + Output = output; + IndentToken = indentToken; + } + + /// + /// Ends the current line + /// + public GeneratorContext WriteLine() + { + Output.WriteLine(); + return this; + } + /// + /// Appends a value and ends the current line + /// + public GeneratorContext WriteLine(string line) + { + var indentLevel = IndentLevel; + var target = Output; + while (indentLevel-- > 0) + { + target.Write(IndentToken); + } + target.WriteLine(line); + return this; + } + /// + /// Appends a value to the current line + /// + public TextWriter Write(string value) + { + var indentLevel = IndentLevel; + var target = Output; + while (indentLevel-- > 0) + { + target.Write(IndentToken); + } + target.Write(value); + return target; + } + /// + /// Increases the indentation level + /// + public GeneratorContext Indent() + { + IndentLevel++; + return this; + } + /// + /// Decreases the indentation level + /// + public GeneratorContext Outdent() + { + IndentLevel--; + return this; + } + + /// + /// Try to find a descriptor of the type specified by T with the given full name + /// + public T TryFind(string typeName) where T : class + { + object obj; + if (!_knownTypes.TryGetValue(typeName, out obj) || obj == null) + { + return null; + } + return obj as T; + } + + private Dictionary _knownTypes = new Dictionary(); + void AddMessage(DescriptorProto message) + { + _knownTypes[message.FullyQualifiedName] = message; + foreach (var @enum in message.EnumTypes) + { + _knownTypes[@enum.FullyQualifiedName] = @enum; + } + foreach (var msg in message.NestedTypes) + { + AddMessage(msg); + } + } + internal void BuildTypeIndex() + { + { + var processedFiles = new HashSet(StringComparer.OrdinalIgnoreCase); + var pendingFiles = new Queue(); + + _knownTypes.Clear(); + processedFiles.Add(File.Name); + pendingFiles.Enqueue(File); + + while (pendingFiles.Count != 0) + { + var file = pendingFiles.Dequeue(); + + foreach (var @enum in file.EnumTypes) + { + _knownTypes[@enum.FullyQualifiedName] = @enum; + } + foreach (var msg in file.MessageTypes) + { + AddMessage(msg); + } + + if (file.HasImports()) + { + foreach (var import in file.GetImports()) + { + if (processedFiles.Add(import.Path)) + { + var importFile = file.Parent.GetFile(import.Path); + if (importFile != null) pendingFiles.Enqueue(importFile); + } + } + } + + } + } + } + } + } + + +} diff --git a/Editor/protobuf-net.Reflection/CodeGenerator.cs.meta b/Editor/protobuf-net.Reflection/CodeGenerator.cs.meta new file mode 100644 index 0000000..237ebc9 --- /dev/null +++ b/Editor/protobuf-net.Reflection/CodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b021246de9d37497cb1f97a9ed01772a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/CustomOptions.cs b/Editor/protobuf-net.Reflection/CustomOptions.cs new file mode 100644 index 0000000..13a1fe1 --- /dev/null +++ b/Editor/protobuf-net.Reflection/CustomOptions.cs @@ -0,0 +1,141 @@ +// This file was generated by a tool; you should avoid making direct changes. +// Consider using 'partial classes' to extend these types +// Input: protogen.proto + +#pragma warning disable CS1591, CS0612, CS3021 + +namespace ProtoBuf.Reflection +{ + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenFileOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"namespace")] + [global::System.ComponentModel.DefaultValue("")] + public string Namespace { get; set; } = ""; + + [global::ProtoBuf.ProtoMember(2, Name = @"access")] + public Access Access { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"extensions")] + [global::System.ComponentModel.DefaultValue("")] + public string ExtensionTypeName { get; set; } = ""; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenMessageOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name { get; set; } = ""; + + [global::ProtoBuf.ProtoMember(2, Name = @"access")] + public Access Access { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"extensions")] + [global::System.ComponentModel.DefaultValue("")] + public string ExtensionTypeName { get; set; } = ""; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenFieldOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name { get; set; } = ""; + + [global::ProtoBuf.ProtoMember(2, Name = @"access")] + public Access Access { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"asRef")] + public bool AsReference { get; set; } + + [global::ProtoBuf.ProtoMember(4, Name = @"dynamicType")] + public bool DynamicType { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenEnumOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name { get; set; } = ""; + + [global::ProtoBuf.ProtoMember(2, Name = @"access")] + public Access Access { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenEnumValueOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name { get; set; } = ""; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenServiceOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name { get; set; } = ""; + + [global::ProtoBuf.ProtoMember(2, Name = @"access")] + public Access Access { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ProtogenMethodOptions + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name { get; set; } = ""; + + } + + [global::ProtoBuf.ProtoContract()] + public enum Access + { + [global::ProtoBuf.ProtoEnum(Name = @"INHERIT")] + Inherit = 0, + [global::ProtoBuf.ProtoEnum(Name = @"PUBLIC")] + Public = 1, + [global::ProtoBuf.ProtoEnum(Name = @"PRIVATE")] + Private = 2, + [global::ProtoBuf.ProtoEnum(Name = @"INTERNAL")] + Internal = 3, + } + + public static class Extensions + { + public static ProtogenFileOptions GetOptions(this global::Google.Protobuf.Reflection.FileOptions obj) + => obj == null ? default(ProtogenFileOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + public static ProtogenMessageOptions GetOptions(this global::Google.Protobuf.Reflection.MessageOptions obj) + => obj == null ? default(ProtogenMessageOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + public static ProtogenFieldOptions GetOptions(this global::Google.Protobuf.Reflection.FieldOptions obj) + => obj == null ? default(ProtogenFieldOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + public static ProtogenEnumOptions GetOptions(this global::Google.Protobuf.Reflection.EnumOptions obj) + => obj == null ? default(ProtogenEnumOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + public static ProtogenEnumValueOptions GetOptions(this global::Google.Protobuf.Reflection.EnumValueOptions obj) + => obj == null ? default(ProtogenEnumValueOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + public static ProtogenServiceOptions GetOptions(this global::Google.Protobuf.Reflection.ServiceOptions obj) + => obj == null ? default(ProtogenServiceOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + public static ProtogenMethodOptions GetOptions(this global::Google.Protobuf.Reflection.MethodOptions obj) + => obj == null ? default(ProtogenMethodOptions) : global::ProtoBuf.Extensible.GetValue(obj, 1037); + + } +} + +#pragma warning restore CS1591, CS0612, CS3021 diff --git a/Editor/protobuf-net.Reflection/CustomOptions.cs.meta b/Editor/protobuf-net.Reflection/CustomOptions.cs.meta new file mode 100644 index 0000000..fd77e0b --- /dev/null +++ b/Editor/protobuf-net.Reflection/CustomOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7a900f1d35ef45bd8dd1a9708204ef2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/Descriptor.cs b/Editor/protobuf-net.Reflection/Descriptor.cs new file mode 100644 index 0000000..91efe2e --- /dev/null +++ b/Editor/protobuf-net.Reflection/Descriptor.cs @@ -0,0 +1,1149 @@ +// This file was generated by a tool; you should avoid making direct changes. +// Consider using 'partial classes' to extend these types +// Input: descriptor.proto + +#pragma warning disable CS1591, CS0612, CS3021 + +namespace Google.Protobuf.Reflection +{ + + [global::ProtoBuf.ProtoContract()] + public partial class FileDescriptorSet + { + [global::ProtoBuf.ProtoMember(1, Name = @"file")] + public global::System.Collections.Generic.List Files { get; } = new global::System.Collections.Generic.List(); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class FileDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"package")] + [global::System.ComponentModel.DefaultValue("")] + public string Package + { + get { return __pbn__Package ?? ""; } + set { __pbn__Package = value; } + } + public bool ShouldSerializePackage() => __pbn__Package != null; + public void ResetPackage() => __pbn__Package = null; + private string __pbn__Package; + + [global::ProtoBuf.ProtoMember(3, Name = @"dependency")] + public global::System.Collections.Generic.List Dependencies { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(10, Name = @"public_dependency")] + public int[] PublicDependencies { get; set; } + + [global::ProtoBuf.ProtoMember(11, Name = @"weak_dependency")] + public int[] WeakDependencies { get; set; } + + [global::ProtoBuf.ProtoMember(4, Name = @"message_type")] + public global::System.Collections.Generic.List MessageTypes { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(5, Name = @"enum_type")] + public global::System.Collections.Generic.List EnumTypes { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(6, Name = @"service")] + public global::System.Collections.Generic.List Services { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(7, Name = @"extension")] + public global::System.Collections.Generic.List Extensions { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(8, Name = @"options")] + public FileOptions Options { get; set; } + + [global::ProtoBuf.ProtoMember(9, Name = @"source_code_info")] + public SourceCodeInfo SourceCodeInfo { get; set; } + + [global::ProtoBuf.ProtoMember(12, Name = @"syntax")] + [global::System.ComponentModel.DefaultValue("")] + public string Syntax + { + get { return __pbn__Syntax ?? ""; } + set { __pbn__Syntax = value; } + } + public bool ShouldSerializeSyntax() => __pbn__Syntax != null; + public void ResetSyntax() => __pbn__Syntax = null; + private string __pbn__Syntax; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class DescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"field")] + public global::System.Collections.Generic.List Fields { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(6, Name = @"extension")] + public global::System.Collections.Generic.List Extensions { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(3, Name = @"nested_type")] + public global::System.Collections.Generic.List NestedTypes { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(4, Name = @"enum_type")] + public global::System.Collections.Generic.List EnumTypes { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(5, Name = @"extension_range")] + public global::System.Collections.Generic.List ExtensionRanges { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(8, Name = @"oneof_decl")] + public global::System.Collections.Generic.List OneofDecls { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(7, Name = @"options")] + public MessageOptions Options { get; set; } + + [global::ProtoBuf.ProtoMember(9, Name = @"reserved_range")] + public global::System.Collections.Generic.List ReservedRanges { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(10, Name = @"reserved_name")] + public global::System.Collections.Generic.List ReservedNames { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public partial class ExtensionRange + { + [global::ProtoBuf.ProtoMember(1, Name = @"start")] + public int Start + { + get { return __pbn__Start.GetValueOrDefault(); } + set { __pbn__Start = value; } + } + public bool ShouldSerializeStart() => __pbn__Start != null; + public void ResetStart() => __pbn__Start = null; + private int? __pbn__Start; + + [global::ProtoBuf.ProtoMember(2, Name = @"end")] + public int End + { + get { return __pbn__End.GetValueOrDefault(); } + set { __pbn__End = value; } + } + public bool ShouldSerializeEnd() => __pbn__End != null; + public void ResetEnd() => __pbn__End = null; + private int? __pbn__End; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ReservedRange + { + [global::ProtoBuf.ProtoMember(1, Name = @"start")] + public int Start + { + get { return __pbn__Start.GetValueOrDefault(); } + set { __pbn__Start = value; } + } + public bool ShouldSerializeStart() => __pbn__Start != null; + public void ResetStart() => __pbn__Start = null; + private int? __pbn__Start; + + [global::ProtoBuf.ProtoMember(2, Name = @"end")] + public int End + { + get { return __pbn__End.GetValueOrDefault(); } + set { __pbn__End = value; } + } + public bool ShouldSerializeEnd() => __pbn__End != null; + public void ResetEnd() => __pbn__End = null; + private int? __pbn__End; + + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class FieldDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(3, Name = @"number")] + public int Number + { + get { return __pbn__Number.GetValueOrDefault(); } + set { __pbn__Number = value; } + } + public bool ShouldSerializeNumber() => __pbn__Number != null; + public void ResetNumber() => __pbn__Number = null; + private int? __pbn__Number; + + [global::ProtoBuf.ProtoMember(4)] + public Label label + { + get { return __pbn__label.GetValueOrDefault(); } + set { __pbn__label = value; } + } + public bool ShouldSerializelabel() => __pbn__label != null; + public void Resetlabel() => __pbn__label = null; + private Label? __pbn__label; + + [global::ProtoBuf.ProtoMember(5)] + public Type type + { + get { return __pbn__type.GetValueOrDefault(); } + set { __pbn__type = value; } + } + public bool ShouldSerializetype() => __pbn__type != null; + public void Resettype() => __pbn__type = null; + private Type? __pbn__type; + + [global::ProtoBuf.ProtoMember(6, Name = @"type_name")] + [global::System.ComponentModel.DefaultValue("")] + public string TypeName + { + get { return __pbn__TypeName ?? ""; } + set { __pbn__TypeName = value; } + } + public bool ShouldSerializeTypeName() => __pbn__TypeName != null; + public void ResetTypeName() => __pbn__TypeName = null; + private string __pbn__TypeName; + + [global::ProtoBuf.ProtoMember(2, Name = @"extendee")] + [global::System.ComponentModel.DefaultValue("")] + public string Extendee + { + get { return __pbn__Extendee ?? ""; } + set { __pbn__Extendee = value; } + } + public bool ShouldSerializeExtendee() => __pbn__Extendee != null; + public void ResetExtendee() => __pbn__Extendee = null; + private string __pbn__Extendee; + + [global::ProtoBuf.ProtoMember(7, Name = @"default_value")] + [global::System.ComponentModel.DefaultValue("")] + public string DefaultValue + { + get { return __pbn__DefaultValue ?? ""; } + set { __pbn__DefaultValue = value; } + } + public bool ShouldSerializeDefaultValue() => __pbn__DefaultValue != null; + public void ResetDefaultValue() => __pbn__DefaultValue = null; + private string __pbn__DefaultValue; + + [global::ProtoBuf.ProtoMember(9, Name = @"oneof_index")] + public int OneofIndex + { + get { return __pbn__OneofIndex.GetValueOrDefault(); } + set { __pbn__OneofIndex = value; } + } + public bool ShouldSerializeOneofIndex() => __pbn__OneofIndex != null; + public void ResetOneofIndex() => __pbn__OneofIndex = null; + private int? __pbn__OneofIndex; + + [global::ProtoBuf.ProtoMember(10, Name = @"json_name")] + [global::System.ComponentModel.DefaultValue("")] + public string JsonName + { + get { return __pbn__JsonName ?? ""; } + set { __pbn__JsonName = value; } + } + public bool ShouldSerializeJsonName() => __pbn__JsonName != null; + public void ResetJsonName() => __pbn__JsonName = null; + private string __pbn__JsonName; + + [global::ProtoBuf.ProtoMember(8, Name = @"options")] + public FieldOptions Options { get; set; } + + [global::ProtoBuf.ProtoContract()] + public enum Type + { + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_DOUBLE")] + TypeDouble = 1, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_FLOAT")] + TypeFloat = 2, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_INT64")] + TypeInt64 = 3, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_UINT64")] + TypeUint64 = 4, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_INT32")] + TypeInt32 = 5, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_FIXED64")] + TypeFixed64 = 6, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_FIXED32")] + TypeFixed32 = 7, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_BOOL")] + TypeBool = 8, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_STRING")] + TypeString = 9, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_GROUP")] + TypeGroup = 10, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_MESSAGE")] + TypeMessage = 11, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_BYTES")] + TypeBytes = 12, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_UINT32")] + TypeUint32 = 13, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_ENUM")] + TypeEnum = 14, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_SFIXED32")] + TypeSfixed32 = 15, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_SFIXED64")] + TypeSfixed64 = 16, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_SINT32")] + TypeSint32 = 17, + [global::ProtoBuf.ProtoEnum(Name = @"TYPE_SINT64")] + TypeSint64 = 18, + } + + [global::ProtoBuf.ProtoContract()] + public enum Label + { + [global::ProtoBuf.ProtoEnum(Name = @"LABEL_OPTIONAL")] + LabelOptional = 1, + [global::ProtoBuf.ProtoEnum(Name = @"LABEL_REQUIRED")] + LabelRequired = 2, + [global::ProtoBuf.ProtoEnum(Name = @"LABEL_REPEATED")] + LabelRepeated = 3, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class OneofDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"options")] + public OneofOptions Options { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class EnumDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"value")] + public global::System.Collections.Generic.List Values { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(3, Name = @"options")] + public EnumOptions Options { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class EnumValueDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"number")] + public int Number + { + get { return __pbn__Number.GetValueOrDefault(); } + set { __pbn__Number = value; } + } + public bool ShouldSerializeNumber() => __pbn__Number != null; + public void ResetNumber() => __pbn__Number = null; + private int? __pbn__Number; + + [global::ProtoBuf.ProtoMember(3, Name = @"options")] + public EnumValueOptions Options { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ServiceDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"method")] + public global::System.Collections.Generic.List Methods { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(3, Name = @"options")] + public ServiceOptions Options { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class MethodDescriptorProto + { + [global::ProtoBuf.ProtoMember(1, Name = @"name")] + [global::System.ComponentModel.DefaultValue("")] + public string Name + { + get { return __pbn__Name ?? ""; } + set { __pbn__Name = value; } + } + public bool ShouldSerializeName() => __pbn__Name != null; + public void ResetName() => __pbn__Name = null; + private string __pbn__Name; + + [global::ProtoBuf.ProtoMember(2, Name = @"input_type")] + [global::System.ComponentModel.DefaultValue("")] + public string InputType + { + get { return __pbn__InputType ?? ""; } + set { __pbn__InputType = value; } + } + public bool ShouldSerializeInputType() => __pbn__InputType != null; + public void ResetInputType() => __pbn__InputType = null; + private string __pbn__InputType; + + [global::ProtoBuf.ProtoMember(3, Name = @"output_type")] + [global::System.ComponentModel.DefaultValue("")] + public string OutputType + { + get { return __pbn__OutputType ?? ""; } + set { __pbn__OutputType = value; } + } + public bool ShouldSerializeOutputType() => __pbn__OutputType != null; + public void ResetOutputType() => __pbn__OutputType = null; + private string __pbn__OutputType; + + [global::ProtoBuf.ProtoMember(4, Name = @"options")] + public MethodOptions Options { get; set; } + + [global::ProtoBuf.ProtoMember(5, Name = @"client_streaming")] + [global::System.ComponentModel.DefaultValue(false)] + public bool ClientStreaming + { + get { return __pbn__ClientStreaming ?? false; } + set { __pbn__ClientStreaming = value; } + } + public bool ShouldSerializeClientStreaming() => __pbn__ClientStreaming != null; + public void ResetClientStreaming() => __pbn__ClientStreaming = null; + private bool? __pbn__ClientStreaming; + + [global::ProtoBuf.ProtoMember(6, Name = @"server_streaming")] + [global::System.ComponentModel.DefaultValue(false)] + public bool ServerStreaming + { + get { return __pbn__ServerStreaming ?? false; } + set { __pbn__ServerStreaming = value; } + } + public bool ShouldSerializeServerStreaming() => __pbn__ServerStreaming != null; + public void ResetServerStreaming() => __pbn__ServerStreaming = null; + private bool? __pbn__ServerStreaming; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class FileOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"java_package")] + [global::System.ComponentModel.DefaultValue("")] + public string JavaPackage + { + get { return __pbn__JavaPackage ?? ""; } + set { __pbn__JavaPackage = value; } + } + public bool ShouldSerializeJavaPackage() => __pbn__JavaPackage != null; + public void ResetJavaPackage() => __pbn__JavaPackage = null; + private string __pbn__JavaPackage; + + [global::ProtoBuf.ProtoMember(8, Name = @"java_outer_classname")] + [global::System.ComponentModel.DefaultValue("")] + public string JavaOuterClassname + { + get { return __pbn__JavaOuterClassname ?? ""; } + set { __pbn__JavaOuterClassname = value; } + } + public bool ShouldSerializeJavaOuterClassname() => __pbn__JavaOuterClassname != null; + public void ResetJavaOuterClassname() => __pbn__JavaOuterClassname = null; + private string __pbn__JavaOuterClassname; + + [global::ProtoBuf.ProtoMember(10, Name = @"java_multiple_files")] + [global::System.ComponentModel.DefaultValue(false)] + public bool JavaMultipleFiles + { + get { return __pbn__JavaMultipleFiles ?? false; } + set { __pbn__JavaMultipleFiles = value; } + } + public bool ShouldSerializeJavaMultipleFiles() => __pbn__JavaMultipleFiles != null; + public void ResetJavaMultipleFiles() => __pbn__JavaMultipleFiles = null; + private bool? __pbn__JavaMultipleFiles; + + [global::ProtoBuf.ProtoMember(20, Name = @"java_generate_equals_and_hash")] + [global::System.Obsolete] + public bool JavaGenerateEqualsAndHash + { + get { return __pbn__JavaGenerateEqualsAndHash.GetValueOrDefault(); } + set { __pbn__JavaGenerateEqualsAndHash = value; } + } + public bool ShouldSerializeJavaGenerateEqualsAndHash() => __pbn__JavaGenerateEqualsAndHash != null; + public void ResetJavaGenerateEqualsAndHash() => __pbn__JavaGenerateEqualsAndHash = null; + private bool? __pbn__JavaGenerateEqualsAndHash; + + [global::ProtoBuf.ProtoMember(27, Name = @"java_string_check_utf8")] + [global::System.ComponentModel.DefaultValue(false)] + public bool JavaStringCheckUtf8 + { + get { return __pbn__JavaStringCheckUtf8 ?? false; } + set { __pbn__JavaStringCheckUtf8 = value; } + } + public bool ShouldSerializeJavaStringCheckUtf8() => __pbn__JavaStringCheckUtf8 != null; + public void ResetJavaStringCheckUtf8() => __pbn__JavaStringCheckUtf8 = null; + private bool? __pbn__JavaStringCheckUtf8; + + [global::ProtoBuf.ProtoMember(9, Name = @"optimize_for")] + [global::System.ComponentModel.DefaultValue(OptimizeMode.Speed)] + public OptimizeMode OptimizeFor + { + get { return __pbn__OptimizeFor ?? OptimizeMode.Speed; } + set { __pbn__OptimizeFor = value; } + } + public bool ShouldSerializeOptimizeFor() => __pbn__OptimizeFor != null; + public void ResetOptimizeFor() => __pbn__OptimizeFor = null; + private OptimizeMode? __pbn__OptimizeFor; + + [global::ProtoBuf.ProtoMember(11, Name = @"go_package")] + [global::System.ComponentModel.DefaultValue("")] + public string GoPackage + { + get { return __pbn__GoPackage ?? ""; } + set { __pbn__GoPackage = value; } + } + public bool ShouldSerializeGoPackage() => __pbn__GoPackage != null; + public void ResetGoPackage() => __pbn__GoPackage = null; + private string __pbn__GoPackage; + + [global::ProtoBuf.ProtoMember(16, Name = @"cc_generic_services")] + [global::System.ComponentModel.DefaultValue(false)] + public bool CcGenericServices + { + get { return __pbn__CcGenericServices ?? false; } + set { __pbn__CcGenericServices = value; } + } + public bool ShouldSerializeCcGenericServices() => __pbn__CcGenericServices != null; + public void ResetCcGenericServices() => __pbn__CcGenericServices = null; + private bool? __pbn__CcGenericServices; + + [global::ProtoBuf.ProtoMember(17, Name = @"java_generic_services")] + [global::System.ComponentModel.DefaultValue(false)] + public bool JavaGenericServices + { + get { return __pbn__JavaGenericServices ?? false; } + set { __pbn__JavaGenericServices = value; } + } + public bool ShouldSerializeJavaGenericServices() => __pbn__JavaGenericServices != null; + public void ResetJavaGenericServices() => __pbn__JavaGenericServices = null; + private bool? __pbn__JavaGenericServices; + + [global::ProtoBuf.ProtoMember(18, Name = @"py_generic_services")] + [global::System.ComponentModel.DefaultValue(false)] + public bool PyGenericServices + { + get { return __pbn__PyGenericServices ?? false; } + set { __pbn__PyGenericServices = value; } + } + public bool ShouldSerializePyGenericServices() => __pbn__PyGenericServices != null; + public void ResetPyGenericServices() => __pbn__PyGenericServices = null; + private bool? __pbn__PyGenericServices; + + [global::ProtoBuf.ProtoMember(23, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(31, Name = @"cc_enable_arenas")] + [global::System.ComponentModel.DefaultValue(false)] + public bool CcEnableArenas + { + get { return __pbn__CcEnableArenas ?? false; } + set { __pbn__CcEnableArenas = value; } + } + public bool ShouldSerializeCcEnableArenas() => __pbn__CcEnableArenas != null; + public void ResetCcEnableArenas() => __pbn__CcEnableArenas = null; + private bool? __pbn__CcEnableArenas; + + [global::ProtoBuf.ProtoMember(36, Name = @"objc_class_prefix")] + [global::System.ComponentModel.DefaultValue("")] + public string ObjcClassPrefix + { + get { return __pbn__ObjcClassPrefix ?? ""; } + set { __pbn__ObjcClassPrefix = value; } + } + public bool ShouldSerializeObjcClassPrefix() => __pbn__ObjcClassPrefix != null; + public void ResetObjcClassPrefix() => __pbn__ObjcClassPrefix = null; + private string __pbn__ObjcClassPrefix; + + [global::ProtoBuf.ProtoMember(37, Name = @"csharp_namespace")] + [global::System.ComponentModel.DefaultValue("")] + public string CsharpNamespace + { + get { return __pbn__CsharpNamespace ?? ""; } + set { __pbn__CsharpNamespace = value; } + } + public bool ShouldSerializeCsharpNamespace() => __pbn__CsharpNamespace != null; + public void ResetCsharpNamespace() => __pbn__CsharpNamespace = null; + private string __pbn__CsharpNamespace; + + [global::ProtoBuf.ProtoMember(39, Name = @"swift_prefix")] + [global::System.ComponentModel.DefaultValue("")] + public string SwiftPrefix + { + get { return __pbn__SwiftPrefix ?? ""; } + set { __pbn__SwiftPrefix = value; } + } + public bool ShouldSerializeSwiftPrefix() => __pbn__SwiftPrefix != null; + public void ResetSwiftPrefix() => __pbn__SwiftPrefix = null; + private string __pbn__SwiftPrefix; + + [global::ProtoBuf.ProtoMember(40, Name = @"php_class_prefix")] + [global::System.ComponentModel.DefaultValue("")] + public string PhpClassPrefix + { + get { return __pbn__PhpClassPrefix ?? ""; } + set { __pbn__PhpClassPrefix = value; } + } + public bool ShouldSerializePhpClassPrefix() => __pbn__PhpClassPrefix != null; + public void ResetPhpClassPrefix() => __pbn__PhpClassPrefix = null; + private string __pbn__PhpClassPrefix; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public enum OptimizeMode + { + [global::ProtoBuf.ProtoEnum(Name = @"SPEED")] + Speed = 1, + [global::ProtoBuf.ProtoEnum(Name = @"CODE_SIZE")] + CodeSize = 2, + [global::ProtoBuf.ProtoEnum(Name = @"LITE_RUNTIME")] + LiteRuntime = 3, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class MessageOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"message_set_wire_format")] + [global::System.ComponentModel.DefaultValue(false)] + public bool MessageSetWireFormat + { + get { return __pbn__MessageSetWireFormat ?? false; } + set { __pbn__MessageSetWireFormat = value; } + } + public bool ShouldSerializeMessageSetWireFormat() => __pbn__MessageSetWireFormat != null; + public void ResetMessageSetWireFormat() => __pbn__MessageSetWireFormat = null; + private bool? __pbn__MessageSetWireFormat; + + [global::ProtoBuf.ProtoMember(2, Name = @"no_standard_descriptor_accessor")] + [global::System.ComponentModel.DefaultValue(false)] + public bool NoStandardDescriptorAccessor + { + get { return __pbn__NoStandardDescriptorAccessor ?? false; } + set { __pbn__NoStandardDescriptorAccessor = value; } + } + public bool ShouldSerializeNoStandardDescriptorAccessor() => __pbn__NoStandardDescriptorAccessor != null; + public void ResetNoStandardDescriptorAccessor() => __pbn__NoStandardDescriptorAccessor = null; + private bool? __pbn__NoStandardDescriptorAccessor; + + [global::ProtoBuf.ProtoMember(3, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(7, Name = @"map_entry")] + public bool MapEntry + { + get { return __pbn__MapEntry.GetValueOrDefault(); } + set { __pbn__MapEntry = value; } + } + public bool ShouldSerializeMapEntry() => __pbn__MapEntry != null; + public void ResetMapEntry() => __pbn__MapEntry = null; + private bool? __pbn__MapEntry; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class FieldOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"ctype")] + [global::System.ComponentModel.DefaultValue(CType.String)] + public CType Ctype + { + get { return __pbn__Ctype ?? CType.String; } + set { __pbn__Ctype = value; } + } + public bool ShouldSerializeCtype() => __pbn__Ctype != null; + public void ResetCtype() => __pbn__Ctype = null; + private CType? __pbn__Ctype; + + [global::ProtoBuf.ProtoMember(2, Name = @"packed")] + public bool Packed + { + get { return __pbn__Packed.GetValueOrDefault(); } + set { __pbn__Packed = value; } + } + public bool ShouldSerializePacked() => __pbn__Packed != null; + public void ResetPacked() => __pbn__Packed = null; + private bool? __pbn__Packed; + + [global::ProtoBuf.ProtoMember(6, Name = @"jstype")] + [global::System.ComponentModel.DefaultValue(JSType.JsNormal)] + public JSType Jstype + { + get { return __pbn__Jstype ?? JSType.JsNormal; } + set { __pbn__Jstype = value; } + } + public bool ShouldSerializeJstype() => __pbn__Jstype != null; + public void ResetJstype() => __pbn__Jstype = null; + private JSType? __pbn__Jstype; + + [global::ProtoBuf.ProtoMember(5, Name = @"lazy")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Lazy + { + get { return __pbn__Lazy ?? false; } + set { __pbn__Lazy = value; } + } + public bool ShouldSerializeLazy() => __pbn__Lazy != null; + public void ResetLazy() => __pbn__Lazy = null; + private bool? __pbn__Lazy; + + [global::ProtoBuf.ProtoMember(3, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(10, Name = @"weak")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Weak + { + get { return __pbn__Weak ?? false; } + set { __pbn__Weak = value; } + } + public bool ShouldSerializeWeak() => __pbn__Weak != null; + public void ResetWeak() => __pbn__Weak = null; + private bool? __pbn__Weak; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public enum CType + { + [global::ProtoBuf.ProtoEnum(Name = @"STRING")] + String = 0, + [global::ProtoBuf.ProtoEnum(Name = @"CORD")] + Cord = 1, + [global::ProtoBuf.ProtoEnum(Name = @"STRING_PIECE")] + StringPiece = 2, + } + + [global::ProtoBuf.ProtoContract()] + public enum JSType + { + [global::ProtoBuf.ProtoEnum(Name = @"JS_NORMAL")] + JsNormal = 0, + [global::ProtoBuf.ProtoEnum(Name = @"JS_STRING")] + JsString = 1, + [global::ProtoBuf.ProtoEnum(Name = @"JS_NUMBER")] + JsNumber = 2, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class OneofOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class EnumOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(2, Name = @"allow_alias")] + public bool AllowAlias + { + get { return __pbn__AllowAlias.GetValueOrDefault(); } + set { __pbn__AllowAlias = value; } + } + public bool ShouldSerializeAllowAlias() => __pbn__AllowAlias != null; + public void ResetAllowAlias() => __pbn__AllowAlias = null; + private bool? __pbn__AllowAlias; + + [global::ProtoBuf.ProtoMember(3, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class EnumValueOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ServiceOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(33, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class MethodOptions : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(33, Name = @"deprecated")] + [global::System.ComponentModel.DefaultValue(false)] + public bool Deprecated + { + get { return __pbn__Deprecated ?? false; } + set { __pbn__Deprecated = value; } + } + public bool ShouldSerializeDeprecated() => __pbn__Deprecated != null; + public void ResetDeprecated() => __pbn__Deprecated = null; + private bool? __pbn__Deprecated; + + [global::ProtoBuf.ProtoMember(34)] + [global::System.ComponentModel.DefaultValue(IdempotencyLevel.IdempotencyUnknown)] + public IdempotencyLevel idempotency_level + { + get { return __pbn__idempotency_level ?? IdempotencyLevel.IdempotencyUnknown; } + set { __pbn__idempotency_level = value; } + } + public bool ShouldSerializeidempotency_level() => __pbn__idempotency_level != null; + public void Resetidempotency_level() => __pbn__idempotency_level = null; + private IdempotencyLevel? __pbn__idempotency_level; + + [global::ProtoBuf.ProtoMember(999, Name = @"uninterpreted_option")] + public global::System.Collections.Generic.List UninterpretedOptions { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public enum IdempotencyLevel + { + [global::ProtoBuf.ProtoEnum(Name = @"IDEMPOTENCY_UNKNOWN")] + IdempotencyUnknown = 0, + [global::ProtoBuf.ProtoEnum(Name = @"NO_SIDE_EFFECTS")] + NoSideEffects = 1, + [global::ProtoBuf.ProtoEnum(Name = @"IDEMPOTENT")] + Idempotent = 2, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class UninterpretedOption + { + [global::ProtoBuf.ProtoMember(2, Name = @"name")] + public global::System.Collections.Generic.List Names { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(3, Name = @"identifier_value")] + [global::System.ComponentModel.DefaultValue("")] + public string IdentifierValue + { + get { return __pbn__IdentifierValue ?? ""; } + set { __pbn__IdentifierValue = value; } + } + public bool ShouldSerializeIdentifierValue() => __pbn__IdentifierValue != null; + public void ResetIdentifierValue() => __pbn__IdentifierValue = null; + private string __pbn__IdentifierValue; + + [global::ProtoBuf.ProtoMember(4, Name = @"positive_int_value")] + public ulong PositiveIntValue + { + get { return __pbn__PositiveIntValue.GetValueOrDefault(); } + set { __pbn__PositiveIntValue = value; } + } + public bool ShouldSerializePositiveIntValue() => __pbn__PositiveIntValue != null; + public void ResetPositiveIntValue() => __pbn__PositiveIntValue = null; + private ulong? __pbn__PositiveIntValue; + + [global::ProtoBuf.ProtoMember(5, Name = @"negative_int_value")] + public long NegativeIntValue + { + get { return __pbn__NegativeIntValue.GetValueOrDefault(); } + set { __pbn__NegativeIntValue = value; } + } + public bool ShouldSerializeNegativeIntValue() => __pbn__NegativeIntValue != null; + public void ResetNegativeIntValue() => __pbn__NegativeIntValue = null; + private long? __pbn__NegativeIntValue; + + [global::ProtoBuf.ProtoMember(6, Name = @"double_value")] + public double DoubleValue + { + get { return __pbn__DoubleValue.GetValueOrDefault(); } + set { __pbn__DoubleValue = value; } + } + public bool ShouldSerializeDoubleValue() => __pbn__DoubleValue != null; + public void ResetDoubleValue() => __pbn__DoubleValue = null; + private double? __pbn__DoubleValue; + + [global::ProtoBuf.ProtoMember(7, Name = @"string_value")] + public byte[] StringValue + { + get { return __pbn__StringValue; } + set { __pbn__StringValue = value; } + } + public bool ShouldSerializeStringValue() => __pbn__StringValue != null; + public void ResetStringValue() => __pbn__StringValue = null; + private byte[] __pbn__StringValue; + + [global::ProtoBuf.ProtoMember(8, Name = @"aggregate_value")] + [global::System.ComponentModel.DefaultValue("")] + public string AggregateValue + { + get { return __pbn__AggregateValue ?? ""; } + set { __pbn__AggregateValue = value; } + } + public bool ShouldSerializeAggregateValue() => __pbn__AggregateValue != null; + public void ResetAggregateValue() => __pbn__AggregateValue = null; + private string __pbn__AggregateValue; + + [global::ProtoBuf.ProtoContract()] + public partial class NamePart + { + [global::ProtoBuf.ProtoMember(1, IsRequired = true)] + public string name_part { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"is_extension", IsRequired = true)] + public bool IsExtension { get; set; } + + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class SourceCodeInfo + { + [global::ProtoBuf.ProtoMember(1, Name = @"location")] + public global::System.Collections.Generic.List Locations { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public partial class Location + { + [global::ProtoBuf.ProtoMember(1, Name = @"path", IsPacked = true)] + public int[] Paths { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"span", IsPacked = true)] + public int[] Spans { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"leading_comments")] + [global::System.ComponentModel.DefaultValue("")] + public string LeadingComments + { + get { return __pbn__LeadingComments ?? ""; } + set { __pbn__LeadingComments = value; } + } + public bool ShouldSerializeLeadingComments() => __pbn__LeadingComments != null; + public void ResetLeadingComments() => __pbn__LeadingComments = null; + private string __pbn__LeadingComments; + + [global::ProtoBuf.ProtoMember(4, Name = @"trailing_comments")] + [global::System.ComponentModel.DefaultValue("")] + public string TrailingComments + { + get { return __pbn__TrailingComments ?? ""; } + set { __pbn__TrailingComments = value; } + } + public bool ShouldSerializeTrailingComments() => __pbn__TrailingComments != null; + public void ResetTrailingComments() => __pbn__TrailingComments = null; + private string __pbn__TrailingComments; + + [global::ProtoBuf.ProtoMember(6, Name = @"leading_detached_comments")] + public global::System.Collections.Generic.List LeadingDetachedComments { get; } = new global::System.Collections.Generic.List(); + + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class GeneratedCodeInfo + { + [global::ProtoBuf.ProtoMember(1, Name = @"annotation")] + public global::System.Collections.Generic.List Annotations { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public partial class Annotation + { + [global::ProtoBuf.ProtoMember(1, Name = @"path", IsPacked = true)] + public int[] Paths { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"source_file")] + [global::System.ComponentModel.DefaultValue("")] + public string SourceFile + { + get { return __pbn__SourceFile ?? ""; } + set { __pbn__SourceFile = value; } + } + public bool ShouldSerializeSourceFile() => __pbn__SourceFile != null; + public void ResetSourceFile() => __pbn__SourceFile = null; + private string __pbn__SourceFile; + + [global::ProtoBuf.ProtoMember(3, Name = @"begin")] + public int Begin + { + get { return __pbn__Begin.GetValueOrDefault(); } + set { __pbn__Begin = value; } + } + public bool ShouldSerializeBegin() => __pbn__Begin != null; + public void ResetBegin() => __pbn__Begin = null; + private int? __pbn__Begin; + + [global::ProtoBuf.ProtoMember(4, Name = @"end")] + public int End + { + get { return __pbn__End.GetValueOrDefault(); } + set { __pbn__End = value; } + } + public bool ShouldSerializeEnd() => __pbn__End != null; + public void ResetEnd() => __pbn__End = null; + private int? __pbn__End; + + } + + } + +} + +#pragma warning restore CS1591, CS0612, CS3021 diff --git a/Editor/protobuf-net.Reflection/Descriptor.cs.meta b/Editor/protobuf-net.Reflection/Descriptor.cs.meta new file mode 100644 index 0000000..fe22734 --- /dev/null +++ b/Editor/protobuf-net.Reflection/Descriptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a97ff5a986e394a77a730ff22b235b42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/NameNormalizer.cs b/Editor/protobuf-net.Reflection/NameNormalizer.cs new file mode 100644 index 0000000..f675775 --- /dev/null +++ b/Editor/protobuf-net.Reflection/NameNormalizer.cs @@ -0,0 +1,236 @@ +using Google.Protobuf.Reflection; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + + +namespace ProtoBuf.Reflection +{ + + internal class ParserException : Exception + { + public int ColumnNumber { get; } + public int LineNumber { get; } + public string File { get; } + public string Text { get; } + public string LineContents { get; } + public bool IsError { get; } + internal ParserException(Token token, string message, bool isError) + : base(message ?? "error") + { + ColumnNumber = token.ColumnNumber; + LineNumber = token.LineNumber; + File = token.File; + LineContents = token.LineContents; + Text = token.Value ?? ""; + IsError = isError; + } + } + /// + /// Provides general purpose name suggestions + /// + public abstract class NameNormalizer + { + private class NullNormalizer : NameNormalizer + { + protected override string GetName(string identifier) => identifier; + /// + /// Suggest a name with idiomatic pluralization + /// + public override string Pluralize(string identifier) => identifier; + } + private class DefaultNormalizer : NameNormalizer + { + protected override string GetName(string identifier) => AutoCapitalize(identifier); + /// + /// Suggest a name with idiomatic pluralization + /// + public override string Pluralize(string identifier) => AutoPluralize(identifier); + } + /// + /// Suggest a name with idiomatic name capitalization + /// + public static string AutoCapitalize(string identifier) + { + if (string.IsNullOrEmpty(identifier)) return identifier; + // if all upper-case, make proper-case + if (Regex.IsMatch(identifier, @"^[_A-Z0-9]*$")) + { + return Regex.Replace(identifier, @"(^|_)([A-Z0-9])([A-Z0-9]*)", + match => match.Groups[2].Value.ToUpperInvariant() + match.Groups[3].Value.ToLowerInvariant()); + } + // if all lower-case, make proper case + if (Regex.IsMatch(identifier, @"^[_a-z0-9]*$")) + { + return Regex.Replace(identifier, @"(^|_)([a-z0-9])([a-z0-9]*)", + match => match.Groups[2].Value.ToUpperInvariant() + match.Groups[3].Value.ToLowerInvariant()); + } + // just remove underscores - leave their chosen casing alone + return identifier.Replace("_", ""); + } + public static string AutoCapitalizeFullName(string fullName) + { + var names = fullName.Split('.'); + var s = ""; + for (int i = 0; i < names.Length; i++) + { + if (i == names.Length - 1) + { + s += $"{AutoCapitalize(names[i])}"; + } + else + { + s += $"{AutoCapitalize(names[i])}."; + } + } + return s; + } + /// + /// Suggest a name with idiomatic pluralization + /// + protected static string AutoPluralize(string identifier) + { + // horribly Anglo-centric and only covers common cases; but: is swappable + + if (string.IsNullOrEmpty(identifier) || identifier.Length == 1) return identifier; + + if (identifier.EndsWith("ss") || identifier.EndsWith("o")) return identifier + "es"; + if (identifier.EndsWith("is") && identifier.Length > 2) return identifier.Substring(0, identifier.Length - 2) + "es"; + + if (identifier.EndsWith("s")) return identifier; // misses some things (bus => buses), but: might already be pluralized + + if (identifier.EndsWith("y") && identifier.Length > 2) + { // identity => identities etc + switch (identifier[identifier.Length - 2]) + { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + break; // only for consonant prefix + default: + return identifier.Substring(0, identifier.Length - 1) + "ies"; + } + } + return identifier + "s"; + } + /// + /// Name normalizer with default protobuf-net behaviour, using .NET idioms + /// + public static NameNormalizer Default { get; } = new DefaultNormalizer(); + /// + /// Name normalizer that passes through all identifiers without any changes + /// + public static NameNormalizer Null { get; } = new NullNormalizer(); + /// + /// Suggest a normalized identifier + /// + protected abstract string GetName(string identifier); + /// + /// Suggest a name with idiomatic pluralization + /// + public abstract string Pluralize(string identifier); + + /// + /// Suggest a normalized identifier + /// + public virtual string GetName(FileDescriptorProto definition) + { + var ns = definition?.Options?.GetOptions()?.Namespace; + if (!string.IsNullOrWhiteSpace(ns)) return ns; + ns = definition.Options?.CsharpNamespace; + if (string.IsNullOrWhiteSpace(ns)) ns = GetName(definition.Package); + + return string.IsNullOrWhiteSpace(ns) ? null : ns; + } + /// + /// Suggest a normalized identifier + /// + public virtual string GetName(DescriptorProto definition) + { + var name = definition?.Options?.GetOptions()?.Name; + if (!string.IsNullOrWhiteSpace(name)) return name; + return GetName(definition.Parent as DescriptorProto, GetName(definition.Name), definition.Name, false); + } + /// + /// Suggest a normalized identifier + /// + public virtual string GetName(EnumDescriptorProto definition) + { + var name = definition?.Options?.GetOptions()?.Name; + if (!string.IsNullOrWhiteSpace(name)) return name; + return GetName(definition.Parent as DescriptorProto, GetName(definition.Name), definition.Name, false); + } + /// + /// Suggest a normalized identifier + /// + public virtual string GetName(EnumValueDescriptorProto definition) + { + var name = definition?.Options?.GetOptions()?.Name; + if (!string.IsNullOrWhiteSpace(name)) return name; + return AutoCapitalize(definition.Name); + } + /// + /// Suggest a normalized identifier + /// + public virtual string GetName(FieldDescriptorProto definition) + { + var name = definition?.Options?.GetOptions()?.Name; + if (!string.IsNullOrWhiteSpace(name)) return name; + var preferred = GetName(definition.Name); + if (definition.label == FieldDescriptorProto.Label.LabelRepeated) + { + preferred = Pluralize(preferred); + } + return GetName(definition.Parent as DescriptorProto, preferred, definition.Name, true); + } + /// + /// Obtain a set of all names defined for a message + /// + protected HashSet BuildConflicts(DescriptorProto parent, bool includeDescendents) + { + var conflicts = new HashSet(); + if (parent != null) + { + conflicts.Add(GetName(parent)); + if (includeDescendents) + { + foreach (var type in parent.NestedTypes) + { + conflicts.Add(GetName(type)); + } + foreach (var type in parent.EnumTypes) + { + conflicts.Add(GetName(type)); + } + } + } + return conflicts; + } + /// + /// Get the preferred name for an element + /// + protected virtual string GetName(DescriptorProto parent, string preferred, string fallback, bool includeDescendents) + { + var conflicts = BuildConflicts(parent, includeDescendents); + + if (!conflicts.Contains(preferred)) return preferred; + if (!conflicts.Contains(fallback)) return fallback; + + var attempt = preferred + "Value"; + if (!conflicts.Contains(attempt)) return attempt; + + attempt = fallback + "Value"; + if (!conflicts.Contains(attempt)) return attempt; + + int i = 1; + while (true) + { + attempt = preferred + i.ToString(); + if (!conflicts.Contains(attempt)) return attempt; + } + } + } + +} diff --git a/Editor/protobuf-net.Reflection/NameNormalizer.cs.meta b/Editor/protobuf-net.Reflection/NameNormalizer.cs.meta new file mode 100644 index 0000000..4deb5fe --- /dev/null +++ b/Editor/protobuf-net.Reflection/NameNormalizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acacb9da00b544a9782cc2e20c7cf13a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/Parsers.cs b/Editor/protobuf-net.Reflection/Parsers.cs new file mode 100644 index 0000000..53504c4 --- /dev/null +++ b/Editor/protobuf-net.Reflection/Parsers.cs @@ -0,0 +1,2841 @@ +using Google.Protobuf.Reflection; +using ProtoBuf; +using ProtoBuf.Reflection; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Google.Protobuf.Reflection +{ +#pragma warning disable CS1591 + + interface IType + { + IType Parent { get; } + string FullyQualifiedName { get; } + + IType Find(string name); + } + partial class FileDescriptorSet + { + internal const string Namespace = ".google.protobuf."; + public Func ImportValidator { get; set; } + + internal List importPaths = new List(); + public void AddImportPath(string path) + { + importPaths.Add(path); + } + public Error[] GetErrors() => Error.GetArray(Errors); + internal List Errors { get; } = new List(); + + public bool Add(string name, bool includeInOutput, TextReader source = null) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + if (Path.IsPathRooted(name) || name.Contains("..")) + throw new ArgumentException("Paths should be relative to the import paths, not rooted", nameof(name)); + FileDescriptorProto descriptor; + if (TryResolve(name, out descriptor)) + { + if (includeInOutput) descriptor.IncludeInOutput = true; + return true; // already exists, that counts as success + } + + using (var reader = source ?? Open(name)) + { + if (reader == null) return false; // not found + + descriptor = new FileDescriptorProto + { + Name = name, + IncludeInOutput = includeInOutput + }; + Files.Add(descriptor); + + descriptor.Parse(reader, Errors, name); + return true; + } + } + + private TextReader Open(string name) + { + var found = FindFile(name); + if (found == null) return null; + return File.OpenText(found); + } + string FindFile(string file) + { + foreach (var path in importPaths) + { + var rel = Path.Combine(path, file); + if (File.Exists(rel)) return rel; + } + return null; + } + + bool TryResolve(string name, out FileDescriptorProto descriptor) + { + descriptor = Files.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + return descriptor != null; + } + + private void ApplyImports() + { + bool didSomething; + do + { + didSomething = false; + var file = Files.FirstOrDefault(x => x.HasPendingImports); + if (file != null) + { + // note that GetImports clears the flag + foreach (var import in file.GetImports(true)) + { + if (!(ImportValidator?.Invoke(import.Path) ?? true)) + { + Errors.Error(import.Token, $"import of {import.Path} is disallowed"); + } + else if (Add(import.Path, false)) + { + didSomething = true; + } + else + { + Errors.Error(import.Token, $"unable to find: '{import.Path}'"); + } + } + } + } while (didSomething); + } + + public void Process() + { + ApplyImports(); + foreach (var file in Files) + { + using (var ctx = new ParserContext(file, null, Errors)) + { + file.BuildTypeHierarchy(this, ctx); + } + } + foreach (var file in Files) + { + using (var ctx = new ParserContext(file, null, Errors)) + { + file.ResolveTypes(ctx, false); + } + } + foreach (var file in Files) + { + using (var ctx = new ParserContext(file, null, Errors)) + { + file.ResolveTypes(ctx, true); + } + } + } + + public T Serialize(Func customSerializer, bool includeImports, object state = null) + { + T result; + if (includeImports || Files.All(x => x.IncludeInOutput)) + { + result = customSerializer(this, state); + } + else + { + var snapshort = Files.ToArray(); + Files.RemoveAll(x => !x.IncludeInOutput); + result = customSerializer(this, state); + Files.Clear(); + Files.AddRange(snapshort); + } + return result; + } + + public void Serialize(Stream destination, bool includeImports) + { + Serialize((s,o) => { Serializer.Serialize((Stream)o, s); return true; }, includeImports, destination); + } + + internal FileDescriptorProto GetFile(string path) + // try full match first, then name-only match + => Files.FirstOrDefault(x => string.Equals(x.Name, path, StringComparison.OrdinalIgnoreCase)); + } + partial class DescriptorProto : ISchemaObject, IType, IMessage + { + public static byte[] GetExtensionData(IExtensible obj) + { + var ext = obj?.GetExtensionObject(false); + int len; + if (ext == null || (len = ext.GetLength()) == 0) return null; + var s = ext.BeginQuery(); + try + { + if (s is MemoryStream) return ((MemoryStream)s).ToArray(); + + byte[] buffer = new byte[len]; + int offset = 0, read; + while ((read = s.Read(buffer, offset, len)) > 0) + { + offset += read; + len -= read; + } + if (len != 0) throw new EndOfStreamException(); + return buffer; + } + finally + { + ext.EndQuery(s); + } + } + public static void SetExtensionData(IExtensible obj, byte[] data) + { + if (obj == null || data == null || data.Length == 0) return; + var ext = obj.GetExtensionObject(true); + (ext as IExtensionResettable)?.Reset(); + var s = ext.BeginAppend(); + try + { + s.Write(data, 0, data.Length); + ext.EndAppend(s, true); + } + catch + { + ext.EndAppend(s, false); + throw; + } + } + + public override string ToString() => Name; + internal IType Parent { get; set; } + IType IType.Parent => Parent; + string IType.FullyQualifiedName => FullyQualifiedName; + IType IType.Find(string name) + { + return (IType)NestedTypes.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) + ?? (IType)EnumTypes.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + internal string FullyQualifiedName { get; set; } + + List IMessage.Types => NestedTypes; + + internal int MaxField => (Options?.MessageSetWireFormat == true) ? int.MaxValue : FieldDescriptorProto.DefaultMaxField; + int IMessage.MaxField => MaxField; + + + internal static bool TryParse(ParserContext ctx, IHazNames parent, out DescriptorProto obj) + { + var name = ctx.Tokens.Consume(TokenType.AlphaNumeric); + ctx.CheckNames(parent, name, ctx.Tokens.Previous); + if (ctx.TryReadObject(out obj)) + { + obj.Name = name; + return true; + } + return false; + } + void ISchemaObject.ReadOne(ParserContext ctx) + { + var tokens = ctx.Tokens; + if (tokens.ConsumeIf(TokenType.AlphaNumeric, "message")) + { + DescriptorProto obj; + if (DescriptorProto.TryParse(ctx, this, out obj)) + { + NestedTypes.Add(obj); + } + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "enum")) + { + EnumDescriptorProto obj; + if (EnumDescriptorProto.TryParse(ctx, this, out obj)) + EnumTypes.Add(obj); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "option")) + { + Options = ctx.ParseOptionStatement(Options, this); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "reserved")) + { + ParseReservedRanges(ctx); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "extensions")) + { + ParseExtensionRange(ctx); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "extend")) + { + FieldDescriptorProto.ParseExtensions(ctx, this); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "oneof")) + { + OneofDescriptorProto.Parse(ctx, this); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "map")) + { + ParseMap(ctx); + } + else + { + FieldDescriptorProto obj; + if (FieldDescriptorProto.TryParse(ctx, this, false, out obj)) + Fields.Add(obj); + } + } + + + private void ParseMap(ParserContext ctx) + { + ctx.AbortState = AbortState.Statement; + var tokens = ctx.Tokens; + tokens.Consume(TokenType.Symbol, "<"); + var keyName = tokens.Consume(TokenType.AlphaNumeric); + var keyToken = tokens.Previous; + FieldDescriptorProto.Type keyType; + if (FieldDescriptorProto.TryIdentifyType(keyName, out keyType)) + { + keyName = null; + } + switch (keyType) + { + case 0: + case FieldDescriptorProto.Type.TypeBytes: + case FieldDescriptorProto.Type.TypeMessage: + case FieldDescriptorProto.Type.TypeGroup: + case FieldDescriptorProto.Type.TypeFloat: + case FieldDescriptorProto.Type.TypeDouble: + ctx.Errors.Error(tokens.Previous, "invalid map key type (only integral and string types are allowed)"); + break; + } + tokens.Consume(TokenType.Symbol, ","); + var valueName = tokens.Consume(TokenType.AlphaNumeric); + var valueToken = tokens.Previous; + FieldDescriptorProto.Type valueType; + if (FieldDescriptorProto.TryIdentifyType(valueName, out valueType)) + { + valueName = null; + } + tokens.Consume(TokenType.Symbol, ">"); + + var name = tokens.Consume(TokenType.AlphaNumeric); + var nameToken = tokens.Previous; + ctx.CheckNames(this, name, nameToken); + + tokens.Consume(TokenType.Symbol, "="); + int number = tokens.ConsumeInt32(); + + var jsonName = FieldDescriptorProto.GetJsonName(name); + var typeName = jsonName.Substring(0, 1).ToUpperInvariant() + jsonName.Substring(1) + "Entry"; + ctx.CheckNames(this, typeName, nameToken); + + var field = new FieldDescriptorProto + { + type = FieldDescriptorProto.Type.TypeMessage, + TypeName = typeName, + Name = name, + JsonName = jsonName, + Number = number, + label = FieldDescriptorProto.Label.LabelRepeated, + TypeToken = nameToken + }; + + if (tokens.ConsumeIf(TokenType.Symbol, "[")) + { + field.Options = ctx.ParseOptionBlock(field.Options, field); + } + Fields.Add(field); + + var msgType = new DescriptorProto + { + Name = typeName, + Fields = + { + new FieldDescriptorProto + { + label = FieldDescriptorProto.Label.LabelOptional, + Name = "key", + JsonName = "key", + Number = 1, + type = keyType, + TypeName = keyName, + TypeToken = keyToken, + }, + new FieldDescriptorProto + { + label = FieldDescriptorProto.Label.LabelOptional, + Name = "value", + JsonName = "value", + Number = 2, + type = valueType, + TypeName = valueName, + TypeToken = valueToken, + } + } + }; + if (msgType.Options == null) msgType.Options = new MessageOptions(); + msgType.Options.MapEntry = true; + NestedTypes.Add(msgType); + + ctx.AbortState = AbortState.None; + } + + private void ParseExtensionRange(ParserContext ctx) + { + ctx.AbortState = AbortState.Statement; + var tokens = ctx.Tokens; + tokens.Previous.RequireProto2(ctx); + + while (true) + { + int from = tokens.ConsumeInt32(MaxField), to = from; + if (tokens.Read().Is(TokenType.AlphaNumeric, "to")) + { + tokens.Consume(); + to = tokens.ConsumeInt32(MaxField); + } + // the end is off by one + if (to != int.MaxValue) to++; + ExtensionRanges.Add(new ExtensionRange { Start = from, End = to }); + + if (tokens.ConsumeIf(TokenType.Symbol, ",")) + { + tokens.Consume(); + } + else if (tokens.ConsumeIf(TokenType.Symbol, ";")) + { + break; + } + else + { + tokens.Read().Throw("unable to parse extension range"); + } + } + ctx.AbortState = AbortState.None; + } + + + + private void ParseReservedRanges(ParserContext ctx) + { + ctx.AbortState = AbortState.Statement; + var tokens = ctx.Tokens; + var token = tokens.Read(); // test the first one to determine what we're doing + switch (token.Type) + { + case TokenType.StringLiteral: + while (true) + { + var name = tokens.Consume(TokenType.StringLiteral); + var conflict = Fields.FirstOrDefault(x => x.Name == name); + if (conflict != null) + { + ctx.Errors.Error(tokens.Previous, $"'{conflict.Name}' is already in use by field {conflict.Number}"); + } + ReservedNames.Add(name); + + if (tokens.ConsumeIf(TokenType.Symbol, ",")) + { + } + else if (tokens.ConsumeIf(TokenType.Symbol, ";")) + { + break; + } + else + { + tokens.Read().Throw("unable to parse reserved range"); + } + } + break; + case TokenType.AlphaNumeric: + while (true) + { + int from = tokens.ConsumeInt32(), to = from; + if (tokens.Read().Is(TokenType.AlphaNumeric, "to")) + { + tokens.Consume(); + to = tokens.ConsumeInt32(); + } + var conflict = Fields.FirstOrDefault(x => x.Number >= from && x.Number <= to); + if (conflict != null) + { + ctx.Errors.Error(tokens.Previous, $"field {conflict.Number} is already in use by '{conflict.Name}'"); + } + ReservedRanges.Add(new ReservedRange { Start = from, End = to + 1 }); + + token = tokens.Read(); + if (token.Is(TokenType.Symbol, ",")) + { + tokens.Consume(); + } + else if (token.Is(TokenType.Symbol, ";")) + { + tokens.Consume(); + break; + } + else + { + token.Throw(); + } + } + break; + default: + throw token.Throw(); + } + ctx.AbortState = AbortState.None; + } + + IEnumerable IHazNames.GetNames() + { + foreach (var field in Fields) yield return field.Name; + foreach (var type in NestedTypes) yield return type.Name; + foreach (var type in EnumTypes) yield return type.Name; + foreach (var name in ReservedNames) yield return name; + } + } + + partial class OneofDescriptorProto : ISchemaObject + { + internal DescriptorProto Parent { get; set; } + internal static void Parse(ParserContext ctx, DescriptorProto parent) + { + ctx.AbortState = AbortState.Object; + var oneOf = new OneofDescriptorProto + { + Name = ctx.Tokens.Consume(TokenType.AlphaNumeric) + }; + parent.OneofDecls.Add(oneOf); + oneOf.Parent = parent; + + if (ctx.TryReadObjectImpl(oneOf)) + { + ctx.AbortState = AbortState.None; + } + } + void ISchemaObject.ReadOne(ParserContext ctx) + { + var tokens = ctx.Tokens; + if (tokens.ConsumeIf(TokenType.AlphaNumeric, "option")) + { + Options = ctx.ParseOptionStatement(Options, this); + } + else + { + FieldDescriptorProto field; + if (FieldDescriptorProto.TryParse(ctx, Parent, true, out field)) + { + field.OneofIndex = Parent.OneofDecls.Count() - 1; + Parent.Fields.Add(field); + } + } + } + } + partial class OneofOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(OneofOptions); + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + bool ISchemaOptions.Deprecated { get { return false; } set { } } + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) => false; + } + partial class FileDescriptorProto : ISchemaObject, IMessage, IType + { + internal static FileDescriptorProto GetFile(IType type) + { + while (type != null) + { + if (type is FileDescriptorProto) return (FileDescriptorProto)type; + type = type.Parent; + } + return null; + } + int IMessage.MaxField => FieldDescriptorProto.DefaultMaxField; + List IMessage.Fields => null; + List IMessage.Extensions => Extensions; + List IMessage.Types => MessageTypes; + + public override string ToString() => Name; + + string IType.FullyQualifiedName => null; + IType IType.Parent => null; + IType IType.Find(string name) + { + return (IType)MessageTypes.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) + ?? (IType)EnumTypes.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + internal bool HasPendingImports { get; private set; } + internal FileDescriptorSet Parent { get; private set; } + + internal bool IncludeInOutput { get; set; } + + public bool HasImports() => _imports.Count != 0; + internal IEnumerable GetImports(bool resetPendingFlag = false) + { + if (resetPendingFlag) + { + HasPendingImports = false; + } + return _imports; + } + readonly List _imports = new List(); + internal bool AddImport(string path, bool isPublic, Token token) + { + var existing = _imports.FirstOrDefault(x => string.Equals(x.Path, path, StringComparison.OrdinalIgnoreCase)); + if (existing != null) + { + // we'll allow this to upgrade + if (isPublic) existing.IsPublic = true; + return false; + } + HasPendingImports = true; + _imports.Add(new Import { Path = path, IsPublic = isPublic, Token = token }); + return true; + } + + internal const string SyntaxProto2 = "proto2", SyntaxProto3 = "proto3"; + + void ISchemaObject.ReadOne(ParserContext ctx) + { + var tokens = ctx.Tokens; + Token token; + if (tokens.ConsumeIf(TokenType.AlphaNumeric, "message")) + { + DescriptorProto obj; + if (DescriptorProto.TryParse(ctx, this, out obj)) + MessageTypes.Add(obj); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "enum")) + { + EnumDescriptorProto obj; + if (EnumDescriptorProto.TryParse(ctx, this, out obj)) + EnumTypes.Add(obj); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "extend")) + { + FieldDescriptorProto.ParseExtensions(ctx, this); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "service")) + { + ServiceDescriptorProto obj; + if (ServiceDescriptorProto.TryParse(ctx, out obj)) + Services.Add(obj); + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "import")) + { + ctx.AbortState = AbortState.Statement; + bool isPublic = tokens.ConsumeIf(TokenType.AlphaNumeric, "public"); + string path = tokens.Consume(TokenType.StringLiteral); + + if (!AddImport(path, isPublic, tokens.Previous)) + { + ctx.Errors.Warn(tokens.Previous, $"duplicate import: '{path}'"); + } + tokens.Consume(TokenType.Symbol, ";"); + ctx.AbortState = AbortState.None; + + + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "syntax")) + { + ctx.AbortState = AbortState.Statement; + if (MessageTypes.Any() || EnumTypes.Any() || Extensions.Any()) + { + ctx.Errors.Error(tokens.Previous, "syntax must be set before types are defined"); + } + tokens.Consume(TokenType.Symbol, "="); + Syntax = tokens.Consume(TokenType.StringLiteral); + switch (Syntax) + { + case SyntaxProto2: + case SyntaxProto3: + break; + default: + ctx.Errors.Error(tokens.Previous, $"unknown syntax '{Syntax}'"); + break; + } + tokens.Consume(TokenType.Symbol, ";"); + ctx.AbortState = AbortState.None; + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "package")) + { + ctx.AbortState = AbortState.Statement; + Package = tokens.Consume(TokenType.AlphaNumeric); + ctx.AbortState = AbortState.None; + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "option")) + { + Options = ctx.ParseOptionStatement(Options, this); + } + else if (tokens.Peek(out token)) + { + token.Throw(); + } // else EOF + } + + public void Parse(TextReader schema, List errors, string file) + { + Syntax = ""; + using (var ctx = new ParserContext(this, new Peekable(schema.Tokenize(file).RemoveCommentsAndWhitespace()), errors)) + { + var tokens = ctx.Tokens; + Token startOfFile; + tokens.Peek(out startOfFile); // want this for "stuff you didn't do" warnings + + // read the file into the object + ctx.Fill(this); + + // finish up + if (string.IsNullOrWhiteSpace(Syntax)) + { + ctx.Errors.Warn(startOfFile, "no syntax specified; it is strongly recommended to specify 'syntax=\"proto2\";' or 'syntax=\"proto3\";'"); + } + if (Syntax == "" || Syntax == SyntaxProto2) + { + Syntax = null; // for output compatibility; is blank even if set to proto2 explicitly + } + } + } + + + internal bool TryResolveEnum(string typeName, IType parent, out EnumDescriptorProto @enum, bool allowImports, bool treatAllAsPublic = false) + { + IType type; + if (TryResolveType(typeName, parent, out type, allowImports, true, treatAllAsPublic)) + { + @enum = type as EnumDescriptorProto; + return @enum != null; + } + @enum = null; + return false; + } + internal bool TryResolveMessage(string typeName, IType parent, out DescriptorProto message, bool allowImports, bool treatAllAsPublic = false) + { + IType type; + if (TryResolveType(typeName, parent, out type, allowImports, true, treatAllAsPublic)) + { + message = type as DescriptorProto; + return message != null; + } + message = null; + return false; + } + internal static bool TrySplit(string input, out string left, out string right) + { + var split = input.IndexOf('.'); + if (split < 0) + { + left = right = null; + return false; + } + left = input.Substring(0, split).Trim(); + right = input.Substring(split + 1).Trim(); + return true; + } + internal static bool TrySplitLast(string input, out string left, out string right) + { + var split = input.LastIndexOf('.'); + if (split < 0) + { + left = right = null; + return false; + } + left = input.Substring(0, split).Trim(); + right = input.Substring(split + 1).Trim(); + return true; + } + bool TryResolveFromFile(FileDescriptorProto file, string ee, string ion, out FieldDescriptorProto fld, bool withPackageName, bool ai) + { + fld = null; + if (file == null) return false; + + if (withPackageName) + { + var pkg = file.Package; + if (string.IsNullOrWhiteSpace(pkg)) return false; // we're only looking *with* packages right now + + if (!ion.StartsWith(pkg + ".")) return false; // wrong file + + ion = ion.Substring(pkg.Length + 1); // and fully qualified (non-qualified is a second pass) + } + + return file.TryResolveExtension(ee, ion, out fld, ai, false); + } + private bool TryResolveExtension(string extendee, string extension, out FieldDescriptorProto field, bool allowImports = true, bool checkOwnPackage = true) + { + bool isRooted = extension.StartsWith("."); + if (isRooted) + { + // rooted + extension = extension.Substring(1); // remove the root + } + string left; + string right; + if (TrySplitLast(extension, out left, out right)) + { + IType type; + if (TryResolveType(left, null, out type, true, true)) + { + field = (type as DescriptorProto)?.Extensions?.FirstOrDefault(x => x.Extendee == extendee + && x.Name == right); + if (field != null) return true; + } + } + else + { + field = Extensions?.FirstOrDefault(x => x.Extendee == extendee && x.Name == extension); + if (field != null) return true; + } + + if (checkOwnPackage) + { + if (TryResolveFromFile(this, extendee, extension, out field, true, false)) return true; + if (TryResolveFromFile(this, extendee, extension, out field, false, false)) return true; + } + if (allowImports) + { + foreach (var import in _imports) + { + var file = Parent?.GetFile(import.Path); + if (file != null) + { + if (TryResolveFromFile(file, extendee, extension, out field, true, import.IsPublic)) + { + import.Used = true; + return true; + } + } + } + foreach (var import in _imports) + { + var file = Parent?.GetFile(import.Path); + if (file != null) + { + if (TryResolveFromFile(file, extendee, extension, out field, false, import.IsPublic)) + { + import.Used = true; + return true; + } + } + } + } + field = null; + return false; + } + bool TryResolveFromFile(FileDescriptorProto file, string tn, bool ai, out IType tp, bool withPackageName, bool treatAllAsPublic) + { + tp = null; + if (file == null) return false; + + if (withPackageName) + { + var pkg = file.Package; + if (string.IsNullOrWhiteSpace(pkg)) return false; // we're only looking *with* packages right now + + if (!tn.StartsWith(pkg + ".")) return false; // wrong file + + tn = tn.Substring(pkg.Length + 1); // and fully qualified (non-qualified is a second pass) + } + + return file.TryResolveType(tn, file, out tp, ai, false, treatAllAsPublic); + } + internal bool TryResolveType(string typeName, IType parent, out IType type, bool allowImports, bool checkOwnPackage = true, bool treatAllAsPublic = false) + { + bool isRooted = typeName.StartsWith("."); + string left; + string right; + if (isRooted) + { + // rooted + typeName = typeName.Substring(1); // remove the root + } + else if (TrySplit(typeName, out left, out right)) + { + while (parent != null) + { + var next = parent?.Find(left); + if (next != null && TryResolveType(right, next, out type, false, treatAllAsPublic)) return true; + + parent = parent.Parent; + } + } + else + { + // simple name + while (parent != null) + { + type = parent.Find(typeName); + if (type != null) + { + return true; + } + parent = parent.Parent; + } + } + + if (checkOwnPackage && TryResolveFromFile(this, typeName, false, out type, true, treatAllAsPublic)) return true; + if (checkOwnPackage && TryResolveFromFile(this, typeName, false, out type, false, treatAllAsPublic)) return true; + + // look at imports + // check for the name including the package prefix + foreach (var import in _imports) + { + if (allowImports || import.IsPublic || treatAllAsPublic) + { + var file = Parent?.GetFile(import.Path); + if (TryResolveFromFile(file, typeName, false, out type, true, treatAllAsPublic)) + { + import.Used = true; + return true; + } + } + } + + // now look without package prefix + foreach (var import in _imports) + { + if (allowImports || import.IsPublic || treatAllAsPublic) + { + var file = Parent?.GetFile(import.Path); + if (TryResolveFromFile(file, typeName, false, out type, false, treatAllAsPublic)) + { + import.Used = true; + return true; + } + } + } + + type = null; + return false; + + } + + static void SetParents(string prefix, EnumDescriptorProto parent) + { + parent.FullyQualifiedName = prefix + "." + parent.Name; + foreach (var val in parent.Values) + { + val.Parent = parent; + } + } + static void SetParents(string prefix, DescriptorProto parent) + { + var fqn = parent.FullyQualifiedName = prefix + "." + parent.Name; + foreach (var field in parent.Fields) + { + field.Parent = parent; + } + foreach (var @enum in parent.EnumTypes) + { + @enum.Parent = parent; + SetParents(fqn, @enum); + } + foreach (var child in parent.NestedTypes) + { + child.Parent = parent; + SetParents(fqn, child); + } + foreach (var ext in parent.Extensions) + { + ext.Parent = parent; + } + } + internal void BuildTypeHierarchy(FileDescriptorSet set, ParserContext ctx) + { + // build the tree starting at the root + Parent = set; + var prefix = string.IsNullOrWhiteSpace(Package) ? "" : ("." + Package); + foreach (var type in EnumTypes) + { + type.Parent = this; + SetParents(prefix, type); + } + foreach (var type in MessageTypes) + { + type.Parent = this; + SetParents(prefix, type); + } + foreach (var type in Extensions) + { + type.Parent = this; + } + } + + static bool ShouldResolveType(FieldDescriptorProto.Type type) + { + switch (type) + { + case 0: + case FieldDescriptorProto.Type.TypeMessage: + case FieldDescriptorProto.Type.TypeEnum: + case FieldDescriptorProto.Type.TypeGroup: + return true; + default: + return false; + } + } + private void ResolveTypes(ParserContext ctx, List fields, IType parent, bool options) + { + foreach (var field in fields) + { + if (options) ResolveOptions(ctx, field.Options); + else + { + if (!string.IsNullOrEmpty(field.TypeName) && ShouldResolveType(field.type)) + { + // TODO: use TryResolveType once rather than twice + string fqn; + DescriptorProto msg; + EnumDescriptorProto @enum; + if (TryResolveMessage(field.TypeName, parent, out msg, true)) + { + if (field.type != FieldDescriptorProto.Type.TypeGroup) + { + field.type = FieldDescriptorProto.Type.TypeMessage; + } + fqn = msg?.FullyQualifiedName; + } + else if (TryResolveEnum(field.TypeName, parent, out @enum, true)) + { + field.type = FieldDescriptorProto.Type.TypeEnum; + if (!string.IsNullOrWhiteSpace(field.DefaultValue) + & !@enum.Values.Any(x => x.Name == field.DefaultValue)) + { + ctx.Errors.Error(field.TypeToken, $"enum {@enum.Name} does not contain value '{field.DefaultValue}'"); + } + fqn = @enum?.FullyQualifiedName; + } + else + { + ctx.Errors.Add(field.TypeToken.TypeNotFound(field.TypeName)); + fqn = field.TypeName; + field.type = FieldDescriptorProto.Type.TypeMessage; // just an assumption + } + field.TypeName = fqn; + } + + if (!string.IsNullOrEmpty(field.Extendee)) + { + string fqn; + DescriptorProto msg; + if (TryResolveMessage(field.Extendee, parent, out msg, true)) + { + fqn = msg?.FullyQualifiedName; + } + else + { + ctx.Errors.Add(field.TypeToken.TypeNotFound(field.Extendee)); + fqn = field.Extendee; + } + field.Extendee = fqn; + } + + if (field.Options?.Packed ?? false) + { + bool canPack = FieldDescriptorProto.CanPack(field.type); + if (!canPack) + { + ctx.Errors.Error(field.TypeToken, $"field of type {field.type} cannot be packed"); + field.Options.Packed = false; + } + } + } + } + } + + private void ResolveTypes(ParserContext ctx, ServiceDescriptorProto service, bool options) + { + if (options) ResolveOptions(ctx, service.Options); + foreach (var method in service.Methods) + { + if (options) ResolveOptions(ctx, method.Options); + else + { + DescriptorProto msg; + if (!TryResolveMessage(method.InputType, this, out msg, true)) + { + ctx.Errors.Add(method.InputTypeToken.TypeNotFound(method.InputType)); + } + method.InputType = msg?.FullyQualifiedName; + if (!TryResolveMessage(method.OutputType, this, out msg, true)) + { + ctx.Errors.Add(method.OutputTypeToken.TypeNotFound(method.OutputType)); + } + method.OutputType = msg?.FullyQualifiedName; + } + } + } + + private void ResolveTypes(ParserContext ctx, DescriptorProto type, bool options) + { + if (options) + { + ResolveOptions(ctx, type.Options); + foreach (var decl in type.OneofDecls) + ResolveOptions(ctx, decl.Options); + } + + ResolveTypes(ctx, type.Fields, type, options); + ResolveTypes(ctx, type.Extensions, type, options); + foreach (var nested in type.NestedTypes) + { + ResolveTypes(ctx, nested, options); + } + foreach (var nested in type.EnumTypes) + { + ResolveTypes(ctx, nested, options); + } + } + + + IEnumerable IHazNames.GetNames() + { + foreach (var type in MessageTypes) yield return type.Name; + foreach (var type in EnumTypes) yield return type.Name; + } + internal void ResolveTypes(ParserContext ctx, bool options) + { + if (options) ResolveOptions(ctx, Options); + foreach (var type in MessageTypes) + { + ResolveTypes(ctx, type, options); + } + foreach (var type in EnumTypes) + { + ResolveTypes(ctx, type, options); + } + foreach (var service in Services) + { + ResolveTypes(ctx, service, options); + } + ResolveTypes(ctx, Extensions, this, options); + + if (options) // can only process deps on the second pass, once options have been resolved + { + HashSet publicDependencies = null; + foreach (var import in _imports) + { + if (!Dependencies.Contains(import.Path)) + Dependencies.Add(import.Path); + if (import.IsPublic) + { + (publicDependencies ?? (publicDependencies = new HashSet())).Add(import.Path); + } + if (IncludeInOutput && !import.Used) + { + ctx.Errors.Warn(import.Token, $"import not used: '{import.Path}'"); + } + } + // note that Dependencies should stay in declaration order to be consistent with protoc + if (publicDependencies != null) + { + var arr = publicDependencies.Select(path => Dependencies.IndexOf(path)).ToArray(); + Array.Sort(arr); + PublicDependencies = arr; + } + } + } + + private void ResolveTypes(ParserContext ctx, EnumDescriptorProto type, bool options) + { + if (options) + { + ResolveOptions(ctx, type.Options); + foreach (var val in type.Values) + { + ResolveOptions(ctx, val.Options); + } + } + } + + private void ResolveOptions(ParserContext ctx, ISchemaOptions options) + { + if (options == null || options.UninterpretedOptions.Count == 0) return; + + var extension = ((IExtensible)options).GetExtensionObject(true); + var target = extension.BeginAppend(); + try + { + using (var writer = new ProtoWriter(target, null, null)) + { + var hive = OptionHive.Build(options.UninterpretedOptions); + + // first pass is used to sort the fields so we write them in the right order + AppendOptions(this, writer, ctx, options.Extendee, hive.Children, true, 0, false); + // second pass applies the data + AppendOptions(this, writer, ctx, options.Extendee, hive.Children, false, 0, false); + } + options.UninterpretedOptions.RemoveAll(x => x.Applied); + } + finally + { + extension.EndAppend(target, true); + } + + } + + class OptionHive + { + public OptionHive(string name, bool isExtension, Token token) + { + Name = name; + IsExtension = isExtension; + Token = token; + } + public override string ToString() + { + var sb = new StringBuilder(); + Concat(sb); + return sb.ToString(); + } + private void Concat(StringBuilder sb) + { + bool isFirst = true; + foreach (var value in Options) + { + if (!isFirst) sb.Append(", "); + isFirst = false; + sb.Append(value); + } + foreach (var child in Children) + { + if (!isFirst) sb.Append(", "); + sb.Append(child.Name).Append("={"); + child.Concat(sb); + sb.Append("}"); + } + } + public bool IsExtension { get; } + public string Name { get; } + public Token Token { get; } + public List Options { get; } = new List(); + public List Children { get; } = new List(); + public FieldDescriptorProto Field { get; set; } + + public static OptionHive Build(List options) + { + if (options == null || options.Count == 0) return null; + + var root = new OptionHive(null, false, default(Token)); + foreach (var option in options) + { + var level = root; + OptionHive nextLevel = null; + foreach (var name in option.Names) + { + nextLevel = level.Children.FirstOrDefault(x => x.Name == name.name_part && x.IsExtension == name.IsExtension); + if (nextLevel == null) + { + nextLevel = new OptionHive(name.name_part, name.IsExtension, name.Token); + level.Children.Add(nextLevel); + } + level = nextLevel; + } + level.Options.Add(option); + } + return root; + } + } + private static void AppendOptions(FileDescriptorProto file, ProtoWriter writer, ParserContext ctx, string extendee, List options, bool resolveOnly, int depth, bool messageSet) + { + foreach (var option in options) + AppendOption(file, writer, ctx, extendee, option, resolveOnly, depth, messageSet); + + if (resolveOnly && depth != 0) // fun fact: proto writes root fields in *file* order, but sub-fields in *field* order + { + // ascending field order + options.Sort((x, y) => (x.Field?.Number ?? 0).CompareTo(y.Field?.Number ?? 0)); + } + } + private static bool ShouldWrite(FieldDescriptorProto f, string v, string d){ + return f.label != FieldDescriptorProto.Label.LabelOptional || v != (f.DefaultValue ?? d); + } + private static void AppendOption(FileDescriptorProto file, ProtoWriter writer, ParserContext ctx, string extendee, OptionHive option, bool resolveOnly, int depth, bool messageSet) + { + // resolve the field for this level + FieldDescriptorProto field = option.Field; + DescriptorProto msg; + if (field != null) + { + // already resolved + } + else if (option.IsExtension) + { + if (!file.TryResolveExtension(extendee, option.Name, out field)) field = null; + } + else if (file.TryResolveMessage(extendee, null, out msg, true)) + { + field = msg.Fields.FirstOrDefault(x => x.Name == option.Name); + } + else + { + field = null; + } + + if (field == null) + { + if (!resolveOnly) + { + ctx.Errors.Error(option.Token, $"unable to resolve custom option '{option.Name}' for '{extendee}'"); + } + return; + } + option.Field = field; + + switch (field.type) + { + case FieldDescriptorProto.Type.TypeMessage: + case FieldDescriptorProto.Type.TypeGroup: + var nextFile = GetFile(field.Parent as IType); + DescriptorProto fieldType; + var nextMessageSet = !resolveOnly && nextFile.TryResolveMessage(field.TypeName, null, out fieldType, true) + && (fieldType.Options?.MessageSetWireFormat ?? false); + + if (option.Children.Count != 0) + { + if (resolveOnly) + { + AppendOptions(nextFile, writer, ctx, field.TypeName, option.Children, resolveOnly, depth + 1, nextMessageSet); + } + else if (messageSet) + { + ProtoWriter.WriteFieldHeader(1, WireType.StartGroup, writer); + var grp = ProtoWriter.StartSubItem(null, writer); + + ProtoWriter.WriteFieldHeader(2, WireType.Variant, writer); + ProtoWriter.WriteInt32(field.Number, writer); + + ProtoWriter.WriteFieldHeader(3, WireType.String, writer); + var payload = ProtoWriter.StartSubItem(null, writer); + + AppendOptions(nextFile, writer, ctx, field.TypeName, option.Children, resolveOnly, depth + 1, nextMessageSet); + + ProtoWriter.EndSubItem(payload, writer); + ProtoWriter.EndSubItem(grp, writer); + } + else + { + ProtoWriter.WriteFieldHeader(field.Number, + field.type == FieldDescriptorProto.Type.TypeGroup ? WireType.StartGroup : WireType.String, writer); + var tok = ProtoWriter.StartSubItem(null, writer); + + AppendOptions(nextFile, writer, ctx, field.TypeName, option.Children, resolveOnly, depth + 1, nextMessageSet); + + ProtoWriter.EndSubItem(tok, writer); + } + } + if (resolveOnly) return; // nothing more to do + + if (option.Options.Count == 1 && !option.Options.Single().ShouldSerializeAggregateValue()) + { + // need to write an empty object to match protoc + if (messageSet) + { + ProtoWriter.WriteFieldHeader(1, WireType.StartGroup, writer); + var grp = ProtoWriter.StartSubItem(null, writer); + + ProtoWriter.WriteFieldHeader(2, WireType.Variant, writer); + ProtoWriter.WriteInt32(field.Number, writer); + + ProtoWriter.WriteFieldHeader(3, WireType.String, writer); + var payload = ProtoWriter.StartSubItem(null, writer); + ProtoWriter.EndSubItem(payload, writer); + ProtoWriter.EndSubItem(grp, writer); + } + else + { + ProtoWriter.WriteFieldHeader(field.Number, + field.type == FieldDescriptorProto.Type.TypeGroup ? WireType.StartGroup : WireType.String, writer); + var payload = ProtoWriter.StartSubItem(null, writer); + ProtoWriter.EndSubItem(payload, writer); + } + option.Options.Single().Applied = true; + } + else + { + foreach (var values in option.Options) + { + ctx.Errors.Error(option.Token, $"unable to assign custom option '{option.Name}' for '{extendee}'"); + } + } + break; + default: + if (resolveOnly) return; // nothing more to do + + foreach (var child in option.Children) + { + ctx.Errors.Error(option.Token, $"unable to assign custom option '{child.Name}' for '{extendee}'"); + } + foreach (var value in option.Options) + { + int i32; + switch (field.type) + { + case FieldDescriptorProto.Type.TypeFloat: + float f32; + if (!TokenExtensions.TryParseSingle(value.AggregateValue, out f32)) + { + ctx.Errors.Error(option.Token, $"invalid value for floating point '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "0")) + { + ProtoWriter.WriteFieldHeader(field.Number, WireType.Fixed32, writer); + ProtoWriter.WriteSingle(f32, writer); + } + break; + case FieldDescriptorProto.Type.TypeDouble: + double f64; + if (!TokenExtensions.TryParseDouble(value.AggregateValue, out f64)) + { + ctx.Errors.Error(option.Token, $"invalid value for floating point '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "0")) + { + ProtoWriter.WriteFieldHeader(field.Number, WireType.Fixed64, writer); + ProtoWriter.WriteDouble(f64, writer); + } + break; + case FieldDescriptorProto.Type.TypeBool: + switch (value.AggregateValue) + { + case "true": + i32 = 1; + break; + case "false": + i32 = 0; + break; + default: + ctx.Errors.Error(option.Token, $"invalid value for boolean '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "false")) + { + ProtoWriter.WriteFieldHeader(field.Number, WireType.Variant, writer); + ProtoWriter.WriteInt32(i32, writer); + } + break; + case FieldDescriptorProto.Type.TypeUint32: + case FieldDescriptorProto.Type.TypeFixed32: + { + uint ui32; + if (!TokenExtensions.TryParseUInt32(value.AggregateValue, out ui32)) + { + ctx.Errors.Error(option.Token, $"invalid value for unsigned integer '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "0")) + { + switch (field.type) + { + case FieldDescriptorProto.Type.TypeUint32: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Variant, writer); + break; + case FieldDescriptorProto.Type.TypeFixed32: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Fixed32, writer); + break; + } + ProtoWriter.WriteUInt32(ui32, writer); + } + } + break; + case FieldDescriptorProto.Type.TypeUint64: + case FieldDescriptorProto.Type.TypeFixed64: + { + ulong ui64; + if (!TokenExtensions.TryParseUInt64(value.AggregateValue, out ui64)) + { + ctx.Errors.Error(option.Token, $"invalid value for unsigned integer '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "0")) + { + switch (field.type) + { + case FieldDescriptorProto.Type.TypeUint64: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Variant, writer); + break; + case FieldDescriptorProto.Type.TypeFixed64: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Fixed64, writer); + break; + } + ProtoWriter.WriteUInt64(ui64, writer); + } + } + break; + case FieldDescriptorProto.Type.TypeInt32: + case FieldDescriptorProto.Type.TypeSint32: + case FieldDescriptorProto.Type.TypeSfixed32: + if (!TokenExtensions.TryParseInt32(value.AggregateValue, out i32)) + { + ctx.Errors.Error(option.Token, $"invalid value for integer '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "0")) + { + switch (field.type) + { + case FieldDescriptorProto.Type.TypeInt32: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Variant, writer); + break; + case FieldDescriptorProto.Type.TypeSint32: + ProtoWriter.WriteFieldHeader(field.Number, WireType.SignedVariant, writer); + break; + case FieldDescriptorProto.Type.TypeSfixed32: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Fixed32, writer); + break; + } + ProtoWriter.WriteInt32(i32, writer); + } + break; + case FieldDescriptorProto.Type.TypeInt64: + case FieldDescriptorProto.Type.TypeSint64: + case FieldDescriptorProto.Type.TypeSfixed64: + { + long i64; + if (!TokenExtensions.TryParseInt64(value.AggregateValue, out i64)) + { + ctx.Errors.Error(option.Token, $"invalid value for integer '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + if (ShouldWrite(field, value.AggregateValue, "0")) + { + switch (field.type) + { + case FieldDescriptorProto.Type.TypeInt64: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Variant, writer); + break; + case FieldDescriptorProto.Type.TypeSint64: + ProtoWriter.WriteFieldHeader(field.Number, WireType.SignedVariant, writer); + break; + case FieldDescriptorProto.Type.TypeSfixed64: + ProtoWriter.WriteFieldHeader(field.Number, WireType.Fixed64, writer); + break; + } + ProtoWriter.WriteInt64(i64, writer); + } + } + break; + case FieldDescriptorProto.Type.TypeEnum: + EnumDescriptorProto @enum; + if (file.TryResolveEnum(field.TypeName, null, out @enum, true, true)) + { + var found = @enum.Values.FirstOrDefault(x => x.Name == value.AggregateValue); + if (found == null) + { + ctx.Errors.Error(option.Token, $"invalid value for enum '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + else + { + if (ShouldWrite(field, value.AggregateValue, @enum.Values.FirstOrDefault()?.Name)) + { + ProtoWriter.WriteFieldHeader(field.Number, WireType.Variant, writer); + ProtoWriter.WriteInt32(found.Number, writer); + } + } + } + else + { + ctx.Errors.Error(option.Token, $"unable to resolve enum '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + break; + case FieldDescriptorProto.Type.TypeString: + case FieldDescriptorProto.Type.TypeBytes: + if (ShouldWrite(field, value.AggregateValue, "")) + { + ProtoWriter.WriteFieldHeader(field.Number, WireType.String, writer); + if (value.AggregateValue == null || value.AggregateValue.IndexOf('\\') < 0) + ProtoWriter.WriteString(value.AggregateValue ?? "", writer); + else + { + using (var ms = new MemoryStream(value.AggregateValue.Length)) + { + if (!LoadBytes(ms, value.AggregateValue)) + { + ctx.Errors.Error(option.Token, $"invalid escape sequence '{field.TypeName}': '{option.Name}' = '{value.AggregateValue}'"); + continue; + } +#if NETSTANDARD1_3 + if (ms.TryGetBuffer(out var seg)) + ProtoWriter.WriteBytes(seg.Array, seg.Offset, seg.Count, writer); + else + ProtoWriter.WriteBytes(ms.ToArray(), writer); +#else + ProtoWriter.WriteBytes(ms.GetBuffer(), 0, (int)ms.Length, writer); +#endif + } + } + } + break; + default: + ctx.Errors.Error(option.Token, $"{field.type} options not yet implemented: '{option.Name}' = '{value.AggregateValue}'"); + continue; + } + value.Applied = true; + } + break; + } + } + + private static unsafe bool LoadBytes(Stream ms, string value) + { + bool isEscaped = false; + byte* b = stackalloc byte[10]; + foreach (char c in value) + { + if (isEscaped) + { + isEscaped = false; + // only a few things remain escaped after ConsumeString: + switch (c) + { + case '\\': ms.WriteByte((byte)'\\'); break; + case '\'': ms.WriteByte((byte)'\''); break; + case '"': ms.WriteByte((byte)'"'); break; + case 'r': ms.WriteByte((byte)'\r'); break; + case 'n': ms.WriteByte((byte)'\n'); break; + case 't': ms.WriteByte((byte)'\t'); break; + default: return false; + } + } + else if (c == '\\') + { + isEscaped = true; + } + else + { + var x = c; // can't take address of readonly local + int bytes = Encoding.UTF8.GetBytes(&x, 1, b, 10); + for (int i = 0; i < bytes; i++) + { + ms.WriteByte(b[i]); + } + } + } + return !isEscaped; + } + } + + partial class EnumDescriptorProto : ISchemaObject, IType + { + public override string ToString() => Name; + internal IType Parent { get; set; } + string IType.FullyQualifiedName => FullyQualifiedName; + IType IType.Parent => Parent; + IType IType.Find(string name) => null; + internal string FullyQualifiedName { get; set; } + + internal static bool TryParse(ParserContext ctx, IHazNames parent, out EnumDescriptorProto obj) + { + var name = ctx.Tokens.Consume(TokenType.AlphaNumeric); + ctx.CheckNames(parent, name, ctx.Tokens.Previous); + if (ctx.TryReadObject(out obj)) + { + obj.Name = name; + return true; + } + return false; + } + + void ISchemaObject.ReadOne(ParserContext ctx) + { + ctx.AbortState = AbortState.Statement; + var tokens = ctx.Tokens; + if (tokens.ConsumeIf(TokenType.AlphaNumeric, "option")) + { + Options = ctx.ParseOptionStatement(Options, this); + } + else + { + Values.Add(EnumValueDescriptorProto.Parse(ctx)); + } + ctx.AbortState = AbortState.None; + } + + } + partial class FieldDescriptorProto : ISchemaObject + { + + public bool IsPacked(string syntax) + { + if (label != Label.LabelRepeated) return false; + + var exp = Options?.Packed; + if (exp.HasValue) return exp.GetValueOrDefault(); + + if (syntax != FileDescriptorProto.SyntaxProto2 && FieldDescriptorProto.CanPack(type)) + { + return true; + } + + return false; + } + public override string ToString() => Name; + internal const int DefaultMaxField = 536870911; + internal const int FirstReservedField = 19000; + internal const int LastReservedField = 19999; + + internal IMessage Parent { get; set; } + internal Token TypeToken { get; set; } + + internal int MaxField => Parent?.MaxField ?? DefaultMaxField; + + internal static void NotAllowedOneOf(ParserContext ctx) + { + var token = ctx.Tokens.Previous; + ctx.Errors.Error(token, $"'{token.Value}' not allowed with 'oneof'"); + } + + internal static bool TryParse(ParserContext ctx, IMessage parent, bool isOneOf, out FieldDescriptorProto field) + { + var tokens = ctx.Tokens; + ctx.AbortState = AbortState.Statement; + Label label = Label.LabelOptional; // default + + if (tokens.ConsumeIf(TokenType.AlphaNumeric, "repeated")) + { + if (isOneOf) NotAllowedOneOf(ctx); + label = Label.LabelRepeated; + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "required")) + { + if (isOneOf) NotAllowedOneOf(ctx); + else tokens.Previous.RequireProto2(ctx); + label = Label.LabelRequired; + } + else if (tokens.ConsumeIf(TokenType.AlphaNumeric, "optional")) + { + if (isOneOf) NotAllowedOneOf(ctx); + else tokens.Previous.RequireProto2(ctx); + label = Label.LabelOptional; + } + else if (ctx.Syntax == FileDescriptorProto.SyntaxProto2 && !isOneOf) + { + // required in proto2 + throw tokens.Read().Throw("expected 'repeated' / 'required' / 'optional'"); + } + + var typeToken = tokens.Read(); + if (typeToken.Is(TokenType.AlphaNumeric, "map")) + { + tokens.Previous.Throw($"'{tokens.Previous.Value}' can not be used with 'map'"); + } + string typeName = tokens.Consume(TokenType.AlphaNumeric); + + var isGroup = typeName == "group"; + if (isGroup) + { + //if (isOneOf) NotAllowedOneOf(ctx); + //else if (parentTyped == null) + //{ + // ctx.Errors.Error(tokens.Previous, "group not allowed in this context"); + //} + ctx.AbortState = AbortState.Object; + } + + string name = tokens.Consume(TokenType.AlphaNumeric); + var nameToken = tokens.Previous; + tokens.Consume(TokenType.Symbol, "="); + var number = tokens.ConsumeInt32(); + var numberToken = tokens.Previous; + + if (number < 1 || number > parent.MaxField) + { + ctx.Errors.Error(numberToken, $"field numbers must be in the range 1-{parent.MaxField}"); + } + else if (number >= FirstReservedField && number <= LastReservedField) + { + ctx.Errors.Warn(numberToken, $"field numbers in the range {FirstReservedField}-{LastReservedField} are reserved; this may cause problems on many implementations"); + } + ctx.CheckNames(parent, name, nameToken); + if (parent is DescriptorProto) + { + var parentTyped = parent as DescriptorProto; + var conflict = parentTyped.Fields.FirstOrDefault(x => x.Number == number); + if (conflict != null) + { + ctx.Errors.Error(numberToken, $"field {number} is already in use by '{conflict.Name}'"); + } + if (parentTyped.ReservedNames.Contains(name)) + { + ctx.Errors.Error(nameToken, $"field '{name}' is reserved"); + } + if (parentTyped.ReservedRanges.Any(x => x.Start <= number && x.End > number)) + { + ctx.Errors.Error(numberToken, $"field {number} is reserved"); + } + } + + Type type; + if (isGroup) + { + type = Type.TypeGroup; + typeName = name; + + typeToken.RequireProto2(ctx); + + var firstChar = typeName[0].ToString(); + if (firstChar.ToLowerInvariant() == firstChar) + { + ctx.Errors.Error(nameToken, "group names must start with an upper-case letter"); + } + name = typeName.ToLowerInvariant(); + DescriptorProto grpType; + if (ctx.TryReadObject(out grpType)) + { + grpType.Name = typeName; + ctx.CheckNames(parent, typeName, nameToken); + parent?.Types?.Add(grpType); + } + } + else if (TryIdentifyType(typeName, out type)) + { + typeName = null; + } + + field = new FieldDescriptorProto + { + type = type, + TypeName = typeName, + Name = name, + JsonName = GetJsonName(name), + Number = number, + label = label, + TypeToken = typeToken // internal property that helps give useful error messages + }; + + if (!isGroup) + { + if (tokens.ConsumeIf(TokenType.Symbol, "[")) + { + field.Options = ctx.ParseOptionBlock(field.Options, field); + } + + tokens.Consume(TokenType.Symbol, ";"); + } + ctx.AbortState = AbortState.None; + return true; + } + static readonly char[] Underscores = { '_' }; + internal static string GetJsonName(string name) + => Regex.Replace(name, "_+([0-9a-zA-Z])", match => match.Groups[1].Value.ToUpperInvariant()).TrimEnd(Underscores); + + + internal static bool CanPack(Type type) + { + switch (type) + { + case Type.TypeBool: + case Type.TypeDouble: + case Type.TypeEnum: + case Type.TypeFixed32: + case Type.TypeFixed64: + case Type.TypeFloat: + case Type.TypeInt32: + case Type.TypeInt64: + case Type.TypeSfixed32: + case Type.TypeSfixed64: + case Type.TypeSint32: + case Type.TypeSint64: + case Type.TypeUint32: + case Type.TypeUint64: + return true; + default: + return false; + } + } + internal static bool Assign(Type @in, out Type @out) + { + @out = @in; + return true; + } + internal static bool TryIdentifyType(string typeName, out Type type) + { + switch (typeName) + { + case "bool": return Assign(Type.TypeBool, out @type); + case "bytes": return Assign(Type.TypeBytes, out @type); + case "double": return Assign(Type.TypeDouble, out @type); + case "fixed32": return Assign(Type.TypeFixed32, out @type); + case "fixed64": return Assign(Type.TypeFixed64, out @type); + case "float": return Assign(Type.TypeFloat, out @type); + case "int32": return Assign(Type.TypeInt32, out @type); + case "int64": return Assign(Type.TypeInt64, out @type); + case "sfixed32": return Assign(Type.TypeSfixed32, out @type); + case "sfixed64": return Assign(Type.TypeSfixed64, out @type); + case "sint32": return Assign(Type.TypeSint32, out @type); + case "sint64": return Assign(Type.TypeSint64, out @type); + case "string": return Assign(Type.TypeString, out @type); + case "uint32": return Assign(Type.TypeUint32, out @type); + case "uint64": return Assign(Type.TypeUint64, out @type); + default: + type = default(Type); + return false; + } + } + + internal static void ParseExtensions(ParserContext ctx, IMessage message) + { + var extendee = ctx.Tokens.Consume(TokenType.AlphaNumeric); + var dummy = new DummyExtensions(extendee, message); + ctx.TryReadObjectImpl(dummy); + } + + void ISchemaObject.ReadOne(ParserContext ctx) + { + throw new InvalidOperationException(); + } + + class DummyExtensions : ISchemaObject, IHazNames, IMessage + { + int IMessage.MaxField => message.MaxField; + List IMessage.Types => message.Types; + List IMessage.Extensions => message.Extensions; + List IMessage.Fields => message.Fields; + public byte[] ExtensionData + { + get { return null; } + set { } + } + IEnumerable IHazNames.GetNames() + { + var fields = message.Fields; + if (fields != null) + { + foreach (var field in fields) yield return field.Name; + } + foreach (var field in message.Extensions) yield return field.Name; + foreach (var type in message.Types) yield return type.Name; + } + + void ISchemaObject.ReadOne(ParserContext ctx) + { + ctx.AbortState = AbortState.Statement; + FieldDescriptorProto field; + if (TryParse(ctx, this, false, out field)) + { + field.Extendee = extendee; + message.Extensions.Add(field); + } + ctx.AbortState = AbortState.None; + } + + private IMessage message; + private string extendee; + + public DummyExtensions(string extendee, IMessage message) + { + this.extendee = extendee; + this.message = message; + } + } + } + + internal interface IMessage : IHazNames + { + int MaxField { get; } + List Types { get; } + List Extensions { get; } + List Fields { get; } + } + + partial class ServiceDescriptorProto : ISchemaObject + { + internal static bool TryParse(ParserContext ctx, out ServiceDescriptorProto obj) + { + var name = ctx.Tokens.Consume(TokenType.AlphaNumeric); + if (ctx.TryReadObject(out obj)) + { + obj.Name = name; + return true; + } + return false; + } + void ISchemaObject.ReadOne(ParserContext ctx) + { + ctx.AbortState = AbortState.Statement; + var tokens = ctx.Tokens; + + if (tokens.ConsumeIf(TokenType.AlphaNumeric, "option")) + { + Options = ctx.ParseOptionStatement(Options, this); + } + else + { + // is a method + Methods.Add(MethodDescriptorProto.Parse(ctx)); + } + ctx.AbortState = AbortState.None; + } + } + + partial class MethodDescriptorProto : ISchemaObject + { + internal Token InputTypeToken { get; set; } + internal Token OutputTypeToken { get; set; } + + internal static MethodDescriptorProto Parse(ParserContext ctx) + { + var tokens = ctx.Tokens; + tokens.Consume(TokenType.AlphaNumeric, "rpc"); + var name = tokens.Consume(TokenType.AlphaNumeric); + tokens.Consume(TokenType.Symbol, "("); + bool isInputStream = tokens.ConsumeIf(TokenType.AlphaNumeric, "stream"); + var inputTypeToken = tokens.Read(); + var inputType = tokens.Consume(TokenType.AlphaNumeric); + tokens.Consume(TokenType.Symbol, ")"); + tokens.Consume(TokenType.AlphaNumeric, "returns"); + tokens.Consume(TokenType.Symbol, "("); + bool isOutputStream = tokens.ConsumeIf(TokenType.AlphaNumeric, "stream"); + var outputTypeToken = tokens.Read(); + var outputType = tokens.Consume(TokenType.AlphaNumeric); + tokens.Consume(TokenType.Symbol, ")"); + + var method = new MethodDescriptorProto + { + Name = name, + InputType = inputType, + OutputType = outputType, + InputTypeToken = inputTypeToken, + OutputTypeToken = outputTypeToken + }; + if (isInputStream) method.ClientStreaming = true; + if (isOutputStream) method.ServerStreaming = true; + Token token; + if (tokens.Peek(out token) && token.Is(TokenType.Symbol, "{")) + { + ctx.AbortState = AbortState.Object; + ctx.TryReadObjectImpl(method); + } + else + { + tokens.Consume(TokenType.Symbol, ";"); + } + return method; + } + + void ISchemaObject.ReadOne(ParserContext ctx) + { + ctx.Tokens.Consume(TokenType.AlphaNumeric, "option"); + Options = ctx.ParseOptionStatement(Options, this); + } + } + + partial class EnumValueDescriptorProto + { + internal static EnumValueDescriptorProto Parse(ParserContext ctx) + { + var tokens = ctx.Tokens; + string name = tokens.Consume(TokenType.AlphaNumeric); + tokens.Consume(TokenType.Symbol, "="); + var value = tokens.ConsumeInt32(); + + var obj = new EnumValueDescriptorProto { Name = name, Number = value }; + if (tokens.ConsumeIf(TokenType.Symbol, "[")) + { + obj.Options = ctx.ParseOptionBlock(obj.Options); + } + tokens.Consume(TokenType.Symbol, ";"); + return obj; + } + internal EnumDescriptorProto Parent { get; set; } + + } + partial class MessageOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(MessageOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) + { + switch (key) + { + case "map_entry": + MapEntry = ctx.Tokens.ConsumeBoolean(); + ctx.Errors.Error(ctx.Tokens.Previous, "'map_entry' should not be set explicitly; use 'map' instead"); + return true; + case "message_set_wire_format": MessageSetWireFormat = ctx.Tokens.ConsumeBoolean(); return true; + case "no_standard_descriptor_accessor": NoStandardDescriptorAccessor = ctx.Tokens.ConsumeBoolean(); return true; + default: return false; + } + } + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + partial class MethodOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(MethodOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) + { + switch (key) + { + case "idempotency_level": idempotency_level = ctx.Tokens.ConsumeEnum(); return true; + default: return false; + } + } + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + partial class ServiceOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(ServiceOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) => false; + + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + + partial class UninterpretedOption + { + partial class NamePart + { + public override string ToString() => IsExtension ? ("(" + name_part + ")") : name_part; + internal Token Token { get; set; } + } + internal bool Applied { get; set; } + internal Token Token { get; set; } + } + partial class EnumOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(EnumOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) + { + switch (key) + { + case "allow_alias": AllowAlias = ctx.Tokens.ConsumeBoolean(); return true; + default: return false; + } + } + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + partial class EnumValueOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(EnumValueOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) => false; + + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + partial class FieldOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(FieldOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) + { + switch (key) + { + case "jstype": Jstype = ctx.Tokens.ConsumeEnum(); return true; + case "ctype": Ctype = ctx.Tokens.ConsumeEnum(); return true; + case "lazy": Lazy = ctx.Tokens.ConsumeBoolean(); return true; + case "packed": Packed = ctx.Tokens.ConsumeBoolean(); return true; + case "weak": Weak = ctx.Tokens.ConsumeBoolean(); return true; + default: return false; + } + } + + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + partial class FileOptions : ISchemaOptions + { + string ISchemaOptions.Extendee => FileDescriptorSet.Namespace + nameof(FileOptions); + bool ISchemaOptions.ReadOne(ParserContext ctx, string key) + { + switch (key) + { + case "optimize_for": OptimizeFor = ctx.Tokens.ConsumeEnum(); return true; + case "cc_enable_arenas": CcEnableArenas = ctx.Tokens.ConsumeBoolean(); return true; + case "cc_generic_services": CcGenericServices = ctx.Tokens.ConsumeBoolean(); return true; +#pragma warning disable 0612 + case "java_generate_equals_and_hash": JavaGenerateEqualsAndHash = ctx.Tokens.ConsumeBoolean(); return true; +#pragma warning restore 0612 + case "java_generic_services": JavaGenericServices = ctx.Tokens.ConsumeBoolean(); return true; + case "java_multiple_files": JavaMultipleFiles = ctx.Tokens.ConsumeBoolean(); return true; + case "java_string_check_utf8": JavaStringCheckUtf8 = ctx.Tokens.ConsumeBoolean(); return true; + case "py_generic_services": PyGenericServices = ctx.Tokens.ConsumeBoolean(); return true; + + case "csharp_namespace": CsharpNamespace = ctx.Tokens.ConsumeString(); return true; + case "go_package": GoPackage = ctx.Tokens.ConsumeString(); return true; + case "java_outer_classname": JavaOuterClassname = ctx.Tokens.ConsumeString(); return true; + case "java_package": JavaPackage = ctx.Tokens.ConsumeString(); return true; + case "objc_class_prefix": ObjcClassPrefix = ctx.Tokens.ConsumeString(); return true; + case "php_class_prefix": PhpClassPrefix = ctx.Tokens.ConsumeString(); return true; + case "swift_prefix": SwiftPrefix = ctx.Tokens.ConsumeString(); return true; + + default: return false; + } + } + public byte[] ExtensionData + { + get { return DescriptorProto.GetExtensionData(this); } + set { DescriptorProto.SetExtensionData(this, value); } + } + } + +#pragma warning restore CS1591 +} +namespace ProtoBuf.Reflection +{ + internal static class ErrorExtensions + { + public static void Warn(this List errors, Token token, string message) + => errors.Add(new Error(token, message, false)); + public static void Error(this List errors, Token token, string message) + => errors.Add(new Error(token, message, true)); + public static void Error(this List errors, ParserException ex) + => errors.Add(new Error(ex)); + } + + /// + /// Describes a generated file + /// + public class CodeFile + { + /// + /// Get a string representation of this instance + /// + /// + public override string ToString() => Name; + /// + /// Create a new CodeFile instance + /// + public CodeFile(string name, string text) + { + Name = name; + Text = text; + } + /// + /// The name (including path if necessary) of this file + /// + public string Name { get; } + /// + /// The contents of this file + /// + public string Text { get; } + } + + /// + /// Represents the overall result of a compilation process + /// + public class CompilerResult + { + internal CompilerResult(Error[] errors, CodeFile[] files) + { + Errors = errors; + Files = files; + } + /// + /// The errors from this execution + /// + public Error[] Errors { get; } + /// + /// The output files from this execution + /// + public CodeFile[] Files { get; } + } + + internal class Import + { + public override string ToString() => Path; + public string Path { get; set; } + public bool IsPublic { get; set; } + public Token Token { get; set; } + public bool Used { get; set; } + } + /// + /// Describes an error that occurred during processing + /// + public class Error + { + /// + /// Parse an error from a PROTOC error message + /// + public static Error[] Parse(string stdout, string stderr) + { + if (string.IsNullOrWhiteSpace(stdout) && string.IsNullOrWhiteSpace(stderr)) + return noErrors; + + List errors = new List(); + using (var reader = new StringReader(stdout)) + { + Add(reader, errors); + } + using (var reader = new StringReader(stderr)) + { + Add(reader, errors); + } + return errors.ToArray(); + } + static void Add(TextReader lines, List errors) + { + string line; + while ((line = lines.ReadLine()) != null) + { + var s = line; + bool isError = true; + int lineNumber = 1, columnNumber = 1; + if (s[0] == '[') + { + int i = s.IndexOf(']'); + if (i > 0) + { + var prefix = line.Substring(1, i).Trim(); + s = line.Substring(i + 1).Trim(); + if (prefix.IndexOf("WARNING", StringComparison.OrdinalIgnoreCase) >= 0 + && prefix.IndexOf("ERROR", StringComparison.OrdinalIgnoreCase) < 0) + { + isError = false; + } + } + } + var match = Regex.Match(s, @"^([^:]+):([0-9]+):([0-9]+):\s+"); + string file = ""; + if (match.Success) + { + file = match.Groups[1].Value; + if (!int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out lineNumber)) + lineNumber = 1; + if (!int.TryParse(match.Groups[3].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out columnNumber)) + columnNumber = 1; + s = s.Substring(match.Length).Trim(); + } + errors.Add(new Error(new Token(" ", lineNumber, columnNumber, TokenType.None, "", 0, file), s, isError)); + } + } + internal string ToString(bool includeType) => Text.Length == 0 + ? $"{File}({LineNumber},{ColumnNumber}): {(includeType ? (IsError ? "error: " : "warning: ") : "")}{Message}" + : $"{File}({LineNumber},{ColumnNumber},{LineNumber},{ColumnNumber + Text.Length}): {(includeType ? (IsError ? "error: " : "warning: ") : "")}{Message}"; + /// + /// Get a text representation of this instance + /// + /// + public override string ToString() => ToString(true); + + internal static Error[] GetArray(List errors) + => errors.Count == 0 ? noErrors : errors.ToArray(); + + private static readonly Error[] noErrors = new Error[0]; + + internal Error(Token token, string message, bool isError) + { + ColumnNumber = token.ColumnNumber; + LineNumber = token.LineNumber; + File = token.File; + LineContents = token.LineContents; + Message = message; + IsError = isError; + Text = token.Value; + } + internal Error(ParserException ex) + { + ColumnNumber = ex.ColumnNumber; + LineNumber = ex.LineNumber; + File = ex.File; + LineContents = ex.LineContents; + Message = ex.Message; + IsError = ex.IsError; + Text = ex.Text ?? ""; + } + /// + /// True if this instance represents a non-fatal warning + /// + public bool IsWarning => !IsError; + /// + /// True if this instance represents a fatal error + /// + public bool IsError { get; } + /// + /// The file in which this error was identified + /// + public string File { get; } + /// + /// The source text relating to this error + /// + public string Text { get; } + /// + /// The error message + /// + public string Message { get; } + /// + /// The entire line contents in the source in which this error was located + /// + public string LineContents { get; } + /// + /// The line number in which this error was located + /// + public int LineNumber { get; } + /// + /// The column number in which this error was located + /// + public int ColumnNumber { get; } + } + enum AbortState + { + None, Statement, Object + } + interface ISchemaOptions + { + List UninterpretedOptions { get; } + bool Deprecated { get; set; } + bool ReadOne(ParserContext ctx, string key); + byte[] ExtensionData { get; set; } + string Extendee { get; } + } + + interface IHazNames + { + IEnumerable GetNames(); + } + + interface ISchemaObject + { + void ReadOne(ParserContext ctx); + } + internal class ParserContext : IDisposable + { + public AbortState AbortState { get; set; } + private void ReadOne(T obj) where T : class, ISchemaObject + { + AbortState oldState = AbortState; + AbortState = AbortState.None; + Token stateBefore; + if (!Tokens.Peek(out stateBefore)) return; + + try + { + obj.ReadOne(this); + } + catch (ParserException ex) + { + Errors.Error(ex); + } + finally + { + var state = AbortState; + Token stateAfter; + if (Tokens.Peek(out stateAfter) && stateBefore == stateAfter) + { + // we didn't move! avoid looping forever failing to do the same thing + Errors.Error(stateAfter, "unknown error"); + state = stateAfter.Is(TokenType.Symbol, "}") + ? AbortState.Object : AbortState.Statement; + } + AbortState = oldState; + switch (state) + { + case AbortState.Object: + Tokens.SkipToEndObject(); + break; + case AbortState.Statement: + Tokens.SkipToEndStatement(); + break; + } + } + } + public void Fill(T obj) where T : class, ISchemaObject + { + var tokens = Tokens; + Token token; + while (tokens.Peek(out token)) + { + if (tokens.ConsumeIf(TokenType.Symbol, ";")) + { } + else + { + ReadOne(obj); + } + } + } + static readonly char[] Period = { '.' }; + private void ReadOption(ref T obj, ISchemaObject parent, List existingNameParts = null) where T : class, ISchemaOptions, new() + { + var tokens = Tokens; + bool isBlock = existingNameParts != null; + var nameParts = isBlock + ? new List(existingNameParts) // create a clone we can append to + : new List(); + + do + { + if (nameParts.Count != 0) tokens.ConsumeIf(TokenType.AlphaNumeric, "."); + + bool isExtension = tokens.ConsumeIf(TokenType.Symbol, isBlock ? "[" : "("); + string key = tokens.Consume(TokenType.AlphaNumeric); + var keyToken = tokens.Previous; + if (isExtension) tokens.Consume(TokenType.Symbol, isBlock ? "]" : ")"); + + if (!isExtension && key.StartsWith(".")) + { + key = key.TrimStart(Period); + } + + key = key.Trim(); + if (isExtension || nameParts.Count == 0 || key.IndexOf('.') < 0) + { + var name = new UninterpretedOption.NamePart { IsExtension = isExtension, name_part = key, Token = keyToken }; + nameParts.Add(name); + } + else + { + foreach (var part in key.Split(Period, StringSplitOptions.RemoveEmptyEntries)) + { + var name = new UninterpretedOption.NamePart { IsExtension = false, name_part = part, Token = keyToken }; + nameParts.Add(name); + } + } + } while (!( + (isBlock && tokens.Is(TokenType.Symbol, "{")) + || tokens.ConsumeIf(TokenType.Symbol, isBlock ? ":" : "="))); + + if (tokens.ConsumeIf(TokenType.Symbol, "{")) + { + if (obj == null) obj = new T(); + bool any = false; + while (!tokens.ConsumeIf(TokenType.Symbol, "}")) + { + ReadOption(ref obj, parent, nameParts); + any = true; + } + if (!any) + { + var newOption = new UninterpretedOption(); + newOption.Names.AddRange(nameParts); + obj.UninterpretedOptions.Add(newOption); + } + } + else + { + + var field = parent as FieldDescriptorProto; + bool isField = typeof(T) == typeof(FieldOptions) && field != null; + var singleKey = (nameParts.Count == 1 && !nameParts[0].IsExtension) ? nameParts[0].name_part : null; + if (singleKey == "default" && isField) + { + string defaultValue = tokens.ConsumeString(field.type == FieldDescriptorProto.Type.TypeBytes); + nameParts[0].Token.RequireProto2(this); + ParseDefault(tokens.Previous, field.type, ref defaultValue); + if (defaultValue != null) + { + field.DefaultValue = defaultValue; + } + } + else if (singleKey == "json_name" && isField) + { + string jsonName = tokens.ConsumeString(); + field.JsonName = jsonName; + } + else + { + if (obj == null) obj = new T(); + if (singleKey == "deprecated") + { + obj.Deprecated = tokens.ConsumeBoolean(); + } + else if (singleKey == null || !obj.ReadOne(this, singleKey)) + { + var newOption = new UninterpretedOption + { + AggregateValue = tokens.ConsumeString(), + Token = tokens.Previous + }; + newOption.Names.AddRange(nameParts); + obj.UninterpretedOptions.Add(newOption); + } + } + } + } + + private void ParseDefault(Token token, FieldDescriptorProto.Type type, ref string defaultValue) + { + switch (type) + { + case FieldDescriptorProto.Type.TypeBool: + switch (defaultValue) + { + case "true": + case "false": + break; + default: + Errors.Error(token, "expected 'true' or 'false'"); + break; + } + break; + case FieldDescriptorProto.Type.TypeDouble: + switch (defaultValue) + { + case "inf": + case "-inf": + case "nan": + break; + default: + double val; + if (TokenExtensions.TryParseDouble(defaultValue, out val)) + { + defaultValue = Format(val); + } + else + { + Errors.Error(token, "invalid floating-point number"); + } + break; + } + break; + case FieldDescriptorProto.Type.TypeFloat: + switch (defaultValue) + { + case "inf": + case "-inf": + case "nan": + break; + default: + float val; + if (TokenExtensions.TryParseSingle(defaultValue, out val)) + { + defaultValue = Format(val); + } + else + { + Errors.Error(token, "invalid floating-point number"); + } + break; + } + break; + case FieldDescriptorProto.Type.TypeSfixed32: + case FieldDescriptorProto.Type.TypeInt32: + case FieldDescriptorProto.Type.TypeSint32: + { + int val; + if (TokenExtensions.TryParseInt32(defaultValue, out val)) + { + defaultValue = val.ToString(CultureInfo.InvariantCulture); + } + else + { + Errors.Error(token, "invalid integer"); + } + } + break; + case FieldDescriptorProto.Type.TypeFixed32: + case FieldDescriptorProto.Type.TypeUint32: + { + uint val; + if (TokenExtensions.TryParseUInt32(defaultValue, out val)) + { + defaultValue = val.ToString(CultureInfo.InvariantCulture); + } + else + { + Errors.Error(token, "invalid unsigned integer"); + } + } + break; + case FieldDescriptorProto.Type.TypeSfixed64: + case FieldDescriptorProto.Type.TypeInt64: + case FieldDescriptorProto.Type.TypeSint64: + { + long val; + if (TokenExtensions.TryParseInt64(defaultValue, out val)) + { + defaultValue = val.ToString(CultureInfo.InvariantCulture); + } + else + { + Errors.Error(token, "invalid integer"); + } + } + break; + case FieldDescriptorProto.Type.TypeFixed64: + case FieldDescriptorProto.Type.TypeUint64: + { + ulong val; + if (TokenExtensions.TryParseUInt64(defaultValue, out val)) + { + defaultValue = val.ToString(CultureInfo.InvariantCulture); + } + else + { + Errors.Error(token, "invalid unsigned integer"); + } + } + break; + case 0: + case FieldDescriptorProto.Type.TypeBytes: + case FieldDescriptorProto.Type.TypeString: + case FieldDescriptorProto.Type.TypeEnum: + break; + default: + Errors.Error(token, $"default value not handled: {type}={defaultValue}"); + break; + } + } + + static readonly char[] ExponentChars = { 'e', 'E' }; + static readonly string[] ExponentFormats = { "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10" }; + static string Format(float val) + { + string s = val.ToString(CultureInfo.InvariantCulture); + if (s.IndexOfAny(ExponentChars) < 0) return s; + + foreach (var format in ExponentFormats) + { + var tmp = val.ToString(format, CultureInfo.InvariantCulture); + float x; + if (float.TryParse(tmp, NumberStyles.Any, CultureInfo.InvariantCulture, out x) && x == val) return tmp; + } + return val.ToString("e", CultureInfo.InvariantCulture); + + } + + static string Format(double val) + { + string s = val.ToString(CultureInfo.InvariantCulture).ToUpperInvariant(); + if (s.IndexOfAny(ExponentChars) < 0) return s; + + foreach (var format in ExponentFormats) + { + var tmp = val.ToString(format, CultureInfo.InvariantCulture); + double x; + if (double.TryParse(tmp, NumberStyles.Any, CultureInfo.InvariantCulture, out x) && x == val) return tmp; + } + return val.ToString("e", CultureInfo.InvariantCulture); + } + + public T ParseOptionBlock(T obj, ISchemaObject parent = null) where T : class, ISchemaOptions, new() + { + var tokens = Tokens; + try + { + while (true) + { + if (tokens.ConsumeIf(TokenType.Symbol, "]")) + { + break; + } + else if (tokens.ConsumeIf(TokenType.Symbol, ",")) + { + } + else + { + ReadOption(ref obj, parent); + } + } + } + catch (ParserException ex) + { + Errors.Error(ex); + tokens.SkipToEndOptions(); + } + return obj; + } + public T ParseOptionStatement(T obj, ISchemaObject parent) where T : class, ISchemaOptions, new() + { + var tokens = Tokens; + try + { + ReadOption(ref obj, parent); + tokens.Consume(TokenType.Symbol, ";"); + } + catch (ParserException ex) + { + Errors.Error(ex); + tokens.SkipToEndStatement(); + } + return obj; + } + public bool TryReadObject(out T obj) where T : class, ISchemaObject, new() + { + obj = new T(); + return TryReadObjectImpl(obj); + } + internal bool TryReadObjectImpl(T obj) where T : class, ISchemaObject + { + var tokens = Tokens; + + try + { + tokens.Consume(TokenType.Symbol, "{"); + Token token; + while (tokens.Peek(out token) && !token.Is(TokenType.Symbol, "}")) + { + if (tokens.ConsumeIf(TokenType.Symbol, ";")) + { } + else + { + ReadOne(obj); + } + } + tokens.Consume(TokenType.Symbol, "}"); + return true; + } + catch (ParserException ex) + { + Errors.Error(ex); + tokens.SkipToEndObject(); + } + obj = null; + return false; + } + public ParserContext(FileDescriptorProto file, Peekable tokens, List errors) + { + Tokens = tokens; + Errors = errors; + _file = file; + } + + public string Syntax + { + get + { + var syntax = _file.Syntax; + return string.IsNullOrEmpty(syntax) ? FileDescriptorProto.SyntaxProto2 : syntax; + } + } + + private readonly FileDescriptorProto _file; + public Peekable Tokens { get; } + public List Errors { get; } + + public void Dispose() { Tokens?.Dispose(); } + + internal void CheckNames(IHazNames parent, string name, Token token +#if DEBUG && NETSTANDARD1_3 + , [System.Runtime.CompilerServices.CallerMemberName] string caller = null +#endif + ) + { + if (parent != null && parent.GetNames().Contains(name)) + { + Errors.Error(token, $"name '{name}' is already in use" +#if DEBUG && NETSTANDARD1_3 + + $" ({caller})" +#endif + ); + } + } + } +} diff --git a/Editor/protobuf-net.Reflection/Parsers.cs.meta b/Editor/protobuf-net.Reflection/Parsers.cs.meta new file mode 100644 index 0000000..e62bd49 --- /dev/null +++ b/Editor/protobuf-net.Reflection/Parsers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e55e7fdae8bab4a52bbc61d0bb88cb6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/Peekable.cs b/Editor/protobuf-net.Reflection/Peekable.cs new file mode 100644 index 0000000..87341e2 --- /dev/null +++ b/Editor/protobuf-net.Reflection/Peekable.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; + +namespace ProtoBuf.Reflection +{ + internal sealed class Peekable : IDisposable + { + public override string ToString() + { + T val; + return Peek(out val) ? (val?.ToString() ?? "(null)") : "(EOF)"; + } + private readonly IEnumerator _iter; + private T _peek, _prev; + private bool _havePeek, _eof; + public Peekable(IEnumerable sequence) + { + _iter = sequence.GetEnumerator(); + } + public T Previous => _prev; + public bool Consume() + { + T val; + bool haveData = _havePeek || Peek(out val); + _prev = _peek; + _havePeek = false; + return haveData; + } + public bool Peek(out T next) + { + if (!_havePeek) + { + if (_iter.MoveNext()) + { + _prev = _peek; + _peek = _iter.Current; + _havePeek = true; + } + else + { + _eof = true; + _havePeek = false; + } + } + if (_eof) + { + next = default(T); + return false; + } + next = _peek; + return true; + } + public void Dispose() => _iter?.Dispose(); + } +} diff --git a/Editor/protobuf-net.Reflection/Peekable.cs.meta b/Editor/protobuf-net.Reflection/Peekable.cs.meta new file mode 100644 index 0000000..5422d06 --- /dev/null +++ b/Editor/protobuf-net.Reflection/Peekable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a3c973520c394370a13670e5eff7ae2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/Token.cs b/Editor/protobuf-net.Reflection/Token.cs new file mode 100644 index 0000000..bc34d04 --- /dev/null +++ b/Editor/protobuf-net.Reflection/Token.cs @@ -0,0 +1,83 @@ +using Google.Protobuf.Reflection; +using System; + +namespace ProtoBuf.Reflection +{ + internal struct Token + { + + public static bool operator ==(Token x, Token y) + { + return x.Offset == y.Offset && x.File == y.File; + } + public static bool operator !=(Token x, Token y) + { + return x.Offset != y.Offset || x.File != y.File; + } + public override int GetHashCode() => Offset; + public override bool Equals(object obj) => (obj is Token) && ((Token)obj).Offset == this.Offset; + public bool Equals(Token token) => token.Offset == this.Offset; + public int Offset { get; } + public int LineNumber { get; } + public string File { get; } + public int ColumnNumber { get; } + public TokenType Type { get; } + public string Value { get; } + public string LineContents { get; } + internal Token(string value, int lineNumber, int columnNumber, TokenType type, string lineContents, int offset, string file) + { + Value = value; + LineNumber = lineNumber; + ColumnNumber = columnNumber; + File = file; + Type = type; + LineContents = lineContents; + Offset = offset; + } + public override string ToString() => $"({LineNumber},{ColumnNumber}) '{Value}'"; + + + internal Exception Throw(string error = null, bool isError = true) + { + throw new ParserException(this, string.IsNullOrWhiteSpace(error) ? $"syntax error: '{Value}'" : error, isError); + } + + internal void Assert(TokenType type, string value = null) + { + if (value != null) + { + if (type != Type || value != Value) + { + Throw($"expected {type} '{value}'"); + } + + } + else + { + if (type != Type) + { + Throw($"expected {type}"); + } + } + } + + internal bool Is(TokenType type, string value = null) + { + if (type != Type) return false; + if (value != null && value != Value) return false; + return true; + } + + internal void RequireProto2(ParserContext ctx) + { + if(ctx.Syntax != FileDescriptorProto.SyntaxProto2) + { + var msg = "'" + Value + "' requires " + FileDescriptorProto.SyntaxProto2 + " syntax"; + ctx.Errors.Error(this, msg); + } + } + + internal Error TypeNotFound(string typeName = null) => new Error(this, + $"type not found: '{(string.IsNullOrWhiteSpace(typeName) ? Value : typeName)}'", true); + } +} diff --git a/Editor/protobuf-net.Reflection/Token.cs.meta b/Editor/protobuf-net.Reflection/Token.cs.meta new file mode 100644 index 0000000..b4ca2ad --- /dev/null +++ b/Editor/protobuf-net.Reflection/Token.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aea55379c2cfd4ea8a17e19afd57938c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/TokenExtensions.cs b/Editor/protobuf-net.Reflection/TokenExtensions.cs new file mode 100644 index 0000000..d11fc66 --- /dev/null +++ b/Editor/protobuf-net.Reflection/TokenExtensions.cs @@ -0,0 +1,642 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ProtoBuf.Reflection +{ + internal static class TokenExtensions + { + public static bool Is(this Peekable tokens, TokenType type, string value = null) + { + Token val; + return tokens.Peek(out val) && val.Is(type, value); + } + + public static void Consume(this Peekable tokens, TokenType type, string value) + { + var token = tokens.Read(); + token.Assert(type, value); + tokens.Consume(); + } + public static bool ConsumeIf(this Peekable tokens, TokenType type, string value) + { + Token token; + if (tokens.Peek(out token) && token.Is(type, value)) + { + tokens.Consume(); + return true; + } + return false; + } + + public static Token Read(this Peekable tokens) + { + Token val; + if (!tokens.Peek(out val)) + { + throw new ParserException(tokens.Previous, "Unexpected end of file", true); + } + return val; + } + public static bool SkipToEndOptions(this Peekable tokens) + { + Token token; + while (tokens.Peek(out token)) + { + if (token.Is(TokenType.Symbol, ";") || token.Is(TokenType.Symbol, "}")) + return true; // but don't consume + + tokens.Consume(); + if (token.Is(TokenType.Symbol, "]")) + return true; + } + return false; + } + public static bool SkipToEndStatement(this Peekable tokens) + { + Token token; + while (tokens.Peek(out token)) + { + if (token.Is(TokenType.Symbol, "}")) + return true; // but don't consume + + tokens.Consume(); + if (token.Is(TokenType.Symbol, ";")) + return true; + } + return false; + } + public static bool SkipToEndObject(this Peekable tokens) => SkipToSymbol(tokens, "}"); + private static bool SkipToSymbol(this Peekable tokens, string symbol) + { + Token token; + while (tokens.Peek(out token)) + { + tokens.Consume(); + if (token.Is(TokenType.Symbol, symbol)) + return true; + } + return false; + } + public static bool SkipToEndStatementOrObject(this Peekable tokens) + { + Token token; + while (tokens.Peek(out token)) + { + tokens.Consume(); + if (token.Is(TokenType.Symbol, "}") || token.Is(TokenType.Symbol, ";")) + return true; + } + return false; + } + public static string Consume(this Peekable tokens, TokenType type) + { + var token = tokens.Read(); + token.Assert(type); + string s = token.Value; + tokens.Consume(); + return s; + } + + static class EnumCache + { + private static readonly Dictionary lookup; + public static bool TryGet(string name, out T value) => lookup.TryGetValue(name, out value); + static EnumCache() + { + var fields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public); + var tmp = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var field in fields) + { + string name = field.Name; + var attrib = (ProtoEnumAttribute)field.GetCustomAttributes(false).FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(attrib?.Name)) name = attrib.Name; + var val = (T)field.GetValue(null); + tmp.Add(name, val); + } + lookup = tmp; + } + } + internal static T ConsumeEnum(this Peekable tokens, bool ignoreCase = true) where T : struct + { + var token = tokens.Read(); + var value = tokens.ConsumeString(); + + T val; + if (!EnumCache.TryGet(token.Value, out val)) + token.Throw("Unable to parse " + typeof(T).Name); + return val; + } + internal static bool TryParseUInt32(string token, out uint val, uint? max = null) + { + if (max.HasValue && token == "max") + { + val = max.GetValueOrDefault(); + return true; + } + + if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && uint.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val)) + { + return true; + } + + return uint.TryParse(token, NumberStyles.Integer | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val); + } + internal static bool TryParseUInt64(string token, out ulong val, ulong? max = null) + { + if (max.HasValue && token == "max") + { + val = max.GetValueOrDefault(); + return true; + } + + if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && ulong.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val)) + { + return true; + } + + return ulong.TryParse(token, NumberStyles.Integer | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val); + } + internal static bool TryParseInt32(string token, out int val, int? max = null) + { + if (max.HasValue && token == "max") + { + val = max.GetValueOrDefault(); + return true; + } + + if (token.StartsWith("-0x", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Substring(3), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val)) + { + val = -val; + return true; + } + + if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val)) + { + return true; + } + + return int.TryParse(token, NumberStyles.Integer | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val); + } + internal static bool TryParseInt64(string token, out long val, long? max = null) + { + if (max.HasValue && token == "max") + { + val = max.GetValueOrDefault(); + return true; + } + + if (token.StartsWith("-0x", StringComparison.OrdinalIgnoreCase) && long.TryParse(token.Substring(3), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val)) + { + val = -val; + return true; + } + + if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && long.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val)) + { + return true; + } + + return long.TryParse(token, NumberStyles.Integer | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val); + } + internal static int ConsumeInt32(this Peekable tokens, int? max = null) + { + var token = tokens.Read(); + token.Assert(TokenType.AlphaNumeric); + tokens.Consume(); + int val; + if (TryParseInt32(token.Value, out val, max)) return val; + throw token.Throw("Unable to parse integer"); + } + + internal static string ConsumeString(this Peekable tokens, bool asBytes = false) + { + var token = tokens.Read(); + switch (token.Type) + { + case TokenType.StringLiteral: + MemoryStream ms = null; + do + { + ReadStringBytes(ref ms, token.Value); + tokens.Consume(); + } while (tokens.Peek(out token) && token.Type == TokenType.StringLiteral); // literal concat is a thing + if (ms == null) return ""; + + if (!asBytes) + { +#if NETSTANDARD1_3 + string s = ms.TryGetBuffer(out var segment) + ? Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count) + : Encoding.UTF8.GetString(ms.ToArray()); + +#else + string s = Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length); +#endif + return s.Replace("\\", @"\\") + .Replace("\'", @"\'") + .Replace("\"", @"\""") + .Replace("\r", @"\r") + .Replace("\n", @"\n") + .Replace("\t", @"\t"); + } + + var sb = new StringBuilder((int)ms.Length); + int b; + ms.Position = 0; + while ((b = ms.ReadByte()) >= 0) + { + switch (b) + { + case '\n': sb.Append(@"\n"); break; + case '\r': sb.Append(@"\r"); break; + case '\t': sb.Append(@"\t"); break; + case '\'': sb.Append(@"\'"); break; + case '\"': sb.Append(@"\"""); break; + case '\\': sb.Append(@"\\"); break; + default: + if (b >= 32 && b < 127) + { + sb.Append((char)b); + } + else + { + // encode as 3-part octal + sb.Append('\\') + .Append((char)(((b >> 6) & 7) + (int)'0')) + .Append((char)(((b >> 3) & 7) + (int)'0')) + .Append((char)(((b >> 0) & 7) + (int)'0')); + } + break; + } + } + return sb.ToString(); + case TokenType.AlphaNumeric: + tokens.Consume(); + return token.Value; + default: + throw token.Throw(); + } + } + internal static void AppendAscii(MemoryStream target, string ascii) + { + foreach (char c in ascii) + target.WriteByte(checked((byte)c)); + } + internal static void AppendByte(MemoryStream target, ref uint codePoint, ref int len) + { + if (len != 0) + { + target.WriteByte(checked((byte)codePoint)); + } + codePoint = 0; + len = 0; + } + internal static unsafe void AppendNormalized(MemoryStream target, ref uint codePoint, ref int len) + { + if (len == 0) + { + codePoint = 0; + return; + } + byte* b = stackalloc byte[10]; + char c = checked((char)codePoint); + int count = Encoding.UTF8.GetBytes(&c, 1, b, 10); + for (int i = 0; i < count; i++) + { + target.WriteByte(b[i]); + } + } + internal static void AppendEscaped(MemoryStream target, char c) + { + uint codePoint; + switch (c) + { + // encoded as octal + case 'a': codePoint = '\a'; break; + case 'b': codePoint = '\b'; break; + case 'f': codePoint = '\f'; break; + case 'v': codePoint = '\v'; break; + case 't': codePoint = '\t'; break; + case 'n': codePoint = '\n'; break; + case 'r': codePoint = '\r'; break; + + case '\\': + case '?': + case '\'': + case '\"': + codePoint = c; + break; + default: + codePoint = '?'; + break; + } + int len = 1; + AppendNormalized(target, ref codePoint, ref len); + } + internal static bool GetHexValue(char c, out uint val, ref int len) + { + len++; + if (c >= '0' && c <= '9') + { + val = (uint)c - (uint)'0'; + return true; + } + if (c >= 'a' && c <= 'f') + { + val = 10 + (uint)c - (uint)'a'; + return true; + } + if (c >= 'A' && c <= 'F') + { + val = 10 + (uint)c - (uint)'A'; + return true; + } + len--; + val = 0; + return false; + } + // the normalized output *includes* the slashes, but expands octal to 3 places; + // it is the job of codegen to change this normalized form to the target language form + internal static void ReadStringBytes(ref MemoryStream ms, string value) + { + const int STATE_NORMAL = 0, STATE_ESCAPE = 1, STATE_OCTAL = 2, STATE_HEX = 3; + int state = STATE_NORMAL; + if (value == null || value.Length == 0) return; + + if (ms == null) ms = new MemoryStream(value.Length); + uint escapedCodePoint = 0; + int escapeLength = 0; + foreach (char c in value) + { + switch (state) + { + case STATE_ESCAPE: + if (c >= '0' && c <= '7') + { + state = STATE_OCTAL; + GetHexValue(c, out escapedCodePoint, ref escapeLength); // not a typo; all 1-char octal values are also the same in hex + } + else if (c == 'x') + { + state = STATE_HEX; + } + else if (c == 'u' || c == 'U') + { + throw new NotSupportedException("Unicode escape points: on my todo list"); + } + else + { + state = STATE_NORMAL; + AppendEscaped(ms, c); + } + break; + case STATE_OCTAL: + if (c >= '0' && c <= '7') + { + uint x; + GetHexValue(c, out x, ref escapeLength); + escapedCodePoint = (escapedCodePoint << 3) | x; + if (escapeLength == 3) + { + AppendByte(ms, ref escapedCodePoint, ref escapeLength); + state = STATE_NORMAL; + } + } + else + { + // not an octal char - regular append + if (escapeLength == 0) + { + // include the malformed \x + AppendAscii(ms, @"\x"); + } + else + { + AppendByte(ms, ref escapedCodePoint, ref escapeLength); + } + state = STATE_NORMAL; + goto case STATE_NORMAL; + } + break; + case STATE_HEX: + { + uint x; + if (GetHexValue(c, out x, ref escapeLength)) + { + escapedCodePoint = (escapedCodePoint << 4) | x; + if (escapeLength == 2) + { + AppendByte(ms, ref escapedCodePoint, ref escapeLength); + state = STATE_NORMAL; + } + } + else + { + // not a hex char - regular append + AppendByte(ms, ref escapedCodePoint, ref escapeLength); + state = STATE_NORMAL; + goto case STATE_NORMAL; + } + } + break; + case STATE_NORMAL: + if (c == '\\') + { + state = STATE_ESCAPE; + } + else + { + uint codePoint = (uint)c; + int len = 1; + AppendNormalized(ms, ref codePoint, ref len); + } + break; + default: + throw new InvalidOperationException(); + } + } + // append any trailing escaped data + AppendByte(ms, ref escapedCodePoint, ref escapeLength); + } + + internal static bool ConsumeBoolean(this Peekable tokens) + { + var token = tokens.Read(); + token.Assert(TokenType.AlphaNumeric); + tokens.Consume(); + if (string.Equals("true", token.Value, StringComparison.OrdinalIgnoreCase)) return true; + if (string.Equals("false", token.Value, StringComparison.OrdinalIgnoreCase)) return false; + throw token.Throw("Unable to parse boolean"); + } + + static TokenType Identify(char c) + { + if (c == '"' || c == '\'') return TokenType.StringLiteral; + if (char.IsWhiteSpace(c)) return TokenType.Whitespace; + if (char.IsLetterOrDigit(c)) return TokenType.AlphaNumeric; + switch (c) + { + case '_': + case '.': + case '-': + return TokenType.AlphaNumeric; + } + return TokenType.Symbol; + } + + public static IEnumerable RemoveCommentsAndWhitespace(this IEnumerable tokens) + { + int commentLineNumber = -1; + bool isBlockComment = false; + foreach (var token in tokens) + { + if (isBlockComment) + { + // swallow everything until the end of the block comment + if (token.Is(TokenType.Symbol, "*/")) + isBlockComment = false; + } + else if (commentLineNumber == token.LineNumber) + { + // swallow everything else on that line + } + else if (token.Is(TokenType.Whitespace)) + { + continue; + } + else if (token.Is(TokenType.Symbol, "//")) + { + commentLineNumber = token.LineNumber; + } + else if (token.Is(TokenType.Symbol, "/*")) + { + isBlockComment = true; + } + else + { + yield return token; + } + } + } + + static bool CanCombine(TokenType type, int len, char prev, char next) + => type != TokenType.Symbol + || (len == 1 && prev == '/' && (next == '/' || next == '*')) + || (len == 1 && prev == '*' && next == '/'); + + + public static IEnumerable Tokenize(this TextReader reader, string file) + { + var buffer = new StringBuilder(); + + int lineNumber = 0, offset = 0; + string line; + string lastLine = null; + while ((line = reader.ReadLine()) != null) + { + lastLine = line; + lineNumber++; + int columnNumber = 0, tokenStart = 1; + char lastChar = '\0', stringType = '\0'; + TokenType type = TokenType.None; + bool isEscaped = false; + foreach (char c in line) + { + columnNumber++; + if (type == TokenType.StringLiteral) + { + if (c == stringType && !isEscaped) + { + yield return new Token(buffer.ToString(), lineNumber, tokenStart, type, line, offset++, file); + buffer.Clear(); + type = TokenType.None; + } + else + { + buffer.Append(c); + isEscaped = !isEscaped && c == '\\'; // ends an existing escape or starts a new one + } + } + else + { + var newType = Identify(c); + if (newType == type && CanCombine(type, buffer.Length, lastChar, c)) + { + buffer.Append(c); + } + else + { + if (buffer.Length != 0) + { + yield return new Token(buffer.ToString(), lineNumber, tokenStart, type, line, offset++, file); + buffer.Clear(); + } + type = newType; + tokenStart = columnNumber; + if (newType == TokenType.StringLiteral) + { + stringType = c; + } + else + { + buffer.Append(c); + } + } + } + lastChar = c; + } + + if (buffer.Length != 0) + { + yield return new Token(buffer.ToString(), lineNumber, tokenStart, type, lastLine, offset++, file); + buffer.Clear(); + } + } + + } + internal static bool TryParseSingle(string token, out float val) + { + if (token == "nan") + { + val = float.NaN; + return true; + } + if (token == "inf") + { + val = float.PositiveInfinity; + return true; + } + if (token == "-inf") + { + val = float.NegativeInfinity; + return true; + } + return float.TryParse(token, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val); + } + internal static bool TryParseDouble(string token, out double val) + { + if(token == "nan") + { + val = double.NaN; + return true; + } + if(token == "inf") + { + val = double.PositiveInfinity; + return true; + } + if(token == "-inf") + { + val = double.NegativeInfinity; + return true; + } + return double.TryParse(token, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val); + } + } +} + diff --git a/Editor/protobuf-net.Reflection/TokenExtensions.cs.meta b/Editor/protobuf-net.Reflection/TokenExtensions.cs.meta new file mode 100644 index 0000000..6edaecc --- /dev/null +++ b/Editor/protobuf-net.Reflection/TokenExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08776b0940a8d42c080f7880619608c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/protobuf-net.Reflection/TokenType.cs b/Editor/protobuf-net.Reflection/TokenType.cs new file mode 100644 index 0000000..9945ff6 --- /dev/null +++ b/Editor/protobuf-net.Reflection/TokenType.cs @@ -0,0 +1,11 @@ +namespace ProtoBuf.Reflection +{ + internal enum TokenType + { + None, + Whitespace, + StringLiteral, + AlphaNumeric, + Symbol + } +} diff --git a/Editor/protobuf-net.Reflection/TokenType.cs.meta b/Editor/protobuf-net.Reflection/TokenType.cs.meta new file mode 100644 index 0000000..0f229db --- /dev/null +++ b/Editor/protobuf-net.Reflection/TokenType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c55473354eb3c4e338593d7b041132be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GuruKCP/README.md b/README.md similarity index 81% rename from Assets/GuruKCP/README.md rename to README.md index e25188c..14e5df4 100644 --- a/Assets/GuruKCP/README.md +++ b/README.md @@ -10,6 +10,10 @@ KCP是一个基于udp的快速可靠协议(rudp),能以比 TCP浪费10%-20%的 主要用于构建unity客户端网络层. +kcp库基于https://github.com/l42111996/csharp-kcp +protobuf-net库基于https://github.com/protobuf-net/protobuf-net +两个库均做了适当修改,以完美适配Unity + ## 安装和接入 diff --git a/Assets/GuruKCP/README.md.meta b/README.md.meta similarity index 100% rename from Assets/GuruKCP/README.md.meta rename to README.md.meta diff --git a/Assets/GuruKCP/Runtime.meta b/Runtime.meta similarity index 100% rename from Assets/GuruKCP/Runtime.meta rename to Runtime.meta diff --git a/Assets/GuruKCP/Runtime/GuruKCP.Runtime.asmdef b/Runtime/GuruKCP.Runtime.asmdef similarity index 78% rename from Assets/GuruKCP/Runtime/GuruKCP.Runtime.asmdef rename to Runtime/GuruKCP.Runtime.asmdef index 4086920..1c70771 100644 --- a/Assets/GuruKCP/Runtime/GuruKCP.Runtime.asmdef +++ b/Runtime/GuruKCP.Runtime.asmdef @@ -3,10 +3,8 @@ "rootNamespace": "Guru", "references": [], "includePlatforms": [], - "excludePlatforms": [ - "Editor" - ], - "allowUnsafeCode": false, + "excludePlatforms": [], + "allowUnsafeCode": true, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, diff --git a/Assets/GuruKCP/Runtime/GuruKCP.Runtime.asmdef.meta b/Runtime/GuruKCP.Runtime.asmdef.meta similarity index 100% rename from Assets/GuruKCP/Runtime/GuruKCP.Runtime.asmdef.meta rename to Runtime/GuruKCP.Runtime.asmdef.meta diff --git a/Assets/GuruKCP/Runtime/GuruKCP.cs b/Runtime/GuruKCP.cs similarity index 100% rename from Assets/GuruKCP/Runtime/GuruKCP.cs rename to Runtime/GuruKCP.cs diff --git a/Assets/GuruKCP/Runtime/GuruKCP.cs.meta b/Runtime/GuruKCP.cs.meta similarity index 100% rename from Assets/GuruKCP/Runtime/GuruKCP.cs.meta rename to Runtime/GuruKCP.cs.meta diff --git a/Runtime/Protobuf-net.meta b/Runtime/Protobuf-net.meta new file mode 100644 index 0000000..c53e343 --- /dev/null +++ b/Runtime/Protobuf-net.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22deed51a0f6d4236a6ae1960d426139 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/BclHelpers.cs b/Runtime/Protobuf-net/BclHelpers.cs new file mode 100644 index 0000000..bcff9a4 --- /dev/null +++ b/Runtime/Protobuf-net/BclHelpers.cs @@ -0,0 +1,712 @@ +using System; +using System.Reflection; +namespace ProtoBuf +{ + internal enum TimeSpanScale + { + Days = 0, + Hours = 1, + Minutes = 2, + Seconds = 3, + Milliseconds = 4, + Ticks = 5, + + MinMax = 15 + } + + /// + /// Provides support for common .NET types that do not have a direct representation + /// in protobuf, using the definitions from bcl.proto + /// + public static class BclHelpers + { + /// + /// Creates a new instance of the specified type, bypassing the constructor. + /// + /// The type to create + /// The new instance + /// If the platform does not support constructor-skipping + public static object GetUninitializedObject(Type type) + { +#if COREFX + object obj = TryGetUninitializedObjectWithFormatterServices(type); + if (obj != null) return obj; +#endif +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type); +#else + throw new NotSupportedException("Constructor-skipping is not supported on this platform"); +#endif + } + +#if COREFX // this is inspired by DCS: https://github.com/dotnet/corefx/blob/c02d33b18398199f6acc17d375dab154e9a1df66/src/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlFormatReaderGenerator.cs#L854-L894 + static Func getUninitializedObject; + static internal object TryGetUninitializedObjectWithFormatterServices(Type type) + { + if (getUninitializedObject == null) + { + try { + var formatterServiceType = typeof(string).GetTypeInfo().Assembly.GetType("System.Runtime.Serialization.FormatterServices"); + if (formatterServiceType == null) + { + // fallback for .Net Core 3.0 + var formatterAssembly = Assembly.Load(new AssemblyName("System.Runtime.Serialization.Formatters")); + formatterServiceType = formatterAssembly.GetType("System.Runtime.Serialization.FormatterServices"); + } + MethodInfo method = formatterServiceType?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + if (method != null) + { + getUninitializedObject = (Func)method.CreateDelegate(typeof(Func)); + } + } + catch { /* best efforts only */ } + if(getUninitializedObject == null) getUninitializedObject = x => null; + } + return getUninitializedObject(type); + } +#endif + + const int FieldTimeSpanValue = 0x01, FieldTimeSpanScale = 0x02, FieldTimeSpanKind = 0x03; + + internal static readonly DateTime[] EpochOrigin = { + new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local) + }; + + /// + /// The default value for dates that are following google.protobuf.Timestamp semantics + /// + private static readonly DateTime TimestampEpoch = EpochOrigin[(int)DateTimeKind.Utc]; + + /// + /// Writes a TimeSpan to a protobuf stream using protobuf-net's own representation, bcl.TimeSpan + /// + public static void WriteTimeSpan(TimeSpan timeSpan, ProtoWriter dest) + { + WriteTimeSpanImpl(timeSpan, dest, DateTimeKind.Unspecified); + } + + private static void WriteTimeSpanImpl(TimeSpan timeSpan, ProtoWriter dest, DateTimeKind kind) + { + if (dest == null) throw new ArgumentNullException(nameof(dest)); + long value; + switch (dest.WireType) + { + case WireType.String: + case WireType.StartGroup: + TimeSpanScale scale; + value = timeSpan.Ticks; + if (timeSpan == TimeSpan.MaxValue) + { + value = 1; + scale = TimeSpanScale.MinMax; + } + else if (timeSpan == TimeSpan.MinValue) + { + value = -1; + scale = TimeSpanScale.MinMax; + } + else if (value % TimeSpan.TicksPerDay == 0) + { + scale = TimeSpanScale.Days; + value /= TimeSpan.TicksPerDay; + } + else if (value % TimeSpan.TicksPerHour == 0) + { + scale = TimeSpanScale.Hours; + value /= TimeSpan.TicksPerHour; + } + else if (value % TimeSpan.TicksPerMinute == 0) + { + scale = TimeSpanScale.Minutes; + value /= TimeSpan.TicksPerMinute; + } + else if (value % TimeSpan.TicksPerSecond == 0) + { + scale = TimeSpanScale.Seconds; + value /= TimeSpan.TicksPerSecond; + } + else if (value % TimeSpan.TicksPerMillisecond == 0) + { + scale = TimeSpanScale.Milliseconds; + value /= TimeSpan.TicksPerMillisecond; + } + else + { + scale = TimeSpanScale.Ticks; + } + + SubItemToken token = ProtoWriter.StartSubItem(null, dest); + + if (value != 0) + { + ProtoWriter.WriteFieldHeader(FieldTimeSpanValue, WireType.SignedVariant, dest); + ProtoWriter.WriteInt64(value, dest); + } + if (scale != TimeSpanScale.Days) + { + ProtoWriter.WriteFieldHeader(FieldTimeSpanScale, WireType.Variant, dest); + ProtoWriter.WriteInt32((int)scale, dest); + } + if (kind != DateTimeKind.Unspecified) + { + ProtoWriter.WriteFieldHeader(FieldTimeSpanKind, WireType.Variant, dest); + ProtoWriter.WriteInt32((int)kind, dest); + } + ProtoWriter.EndSubItem(token, dest); + break; + case WireType.Fixed64: + ProtoWriter.WriteInt64(timeSpan.Ticks, dest); + break; + default: + throw new ProtoException("Unexpected wire-type: " + dest.WireType.ToString()); + } + } + + /// + /// Parses a TimeSpan from a protobuf stream using protobuf-net's own representation, bcl.TimeSpan + /// + public static TimeSpan ReadTimeSpan(ProtoReader source) + { + long ticks = ReadTimeSpanTicks(source, out DateTimeKind kind); + if (ticks == long.MinValue) return TimeSpan.MinValue; + if (ticks == long.MaxValue) return TimeSpan.MaxValue; + return TimeSpan.FromTicks(ticks); + } + + /// + /// Parses a TimeSpan from a protobuf stream using the standardized format, google.protobuf.Duration + /// + public static TimeSpan ReadDuration(ProtoReader source) + { + long seconds = 0; + int nanos = 0; + SubItemToken token = ProtoReader.StartSubItem(source); + int fieldNumber; + while ((fieldNumber = source.ReadFieldHeader()) > 0) + { + switch (fieldNumber) + { + case 1: + seconds = source.ReadInt64(); + break; + case 2: + nanos = source.ReadInt32(); + break; + default: + source.SkipField(); + break; + } + } + ProtoReader.EndSubItem(token, source); + return FromDurationSeconds(seconds, nanos); + } + + /// + /// Writes a TimeSpan to a protobuf stream using the standardized format, google.protobuf.Duration + /// + public static void WriteDuration(TimeSpan value, ProtoWriter dest) + { + var seconds = ToDurationSeconds(value, out int nanos); + WriteSecondsNanos(seconds, nanos, dest); + } + + private static void WriteSecondsNanos(long seconds, int nanos, ProtoWriter dest) + { + SubItemToken token = ProtoWriter.StartSubItem(null, dest); + if (seconds != 0) + { + ProtoWriter.WriteFieldHeader(1, WireType.Variant, dest); + ProtoWriter.WriteInt64(seconds, dest); + } + if (nanos != 0) + { + ProtoWriter.WriteFieldHeader(2, WireType.Variant, dest); + ProtoWriter.WriteInt32(nanos, dest); + } + ProtoWriter.EndSubItem(token, dest); + } + + /// + /// Parses a DateTime from a protobuf stream using the standardized format, google.protobuf.Timestamp + /// + public static DateTime ReadTimestamp(ProtoReader source) + { + // note: DateTime is only defined for just over 0000 to just below 10000; + // TimeSpan has a range of +/- 10,675,199 days === 29k years; + // so we can just use epoch time delta + return TimestampEpoch + ReadDuration(source); + } + + /// + /// Writes a DateTime to a protobuf stream using the standardized format, google.protobuf.Timestamp + /// + public static void WriteTimestamp(DateTime value, ProtoWriter dest) + { + var seconds = ToDurationSeconds(value - TimestampEpoch, out int nanos); + + if (nanos < 0) + { // from Timestamp.proto: + // "Negative second values with fractions must still have + // non -negative nanos values that count forward in time." + seconds--; + nanos += 1000000000; + } + WriteSecondsNanos(seconds, nanos, dest); + } + + static TimeSpan FromDurationSeconds(long seconds, int nanos) + { + + long ticks = checked((seconds * TimeSpan.TicksPerSecond) + + (nanos * TimeSpan.TicksPerMillisecond) / 1000000); + return TimeSpan.FromTicks(ticks); + } + + static long ToDurationSeconds(TimeSpan value, out int nanos) + { + nanos = (int)(((value.Ticks % TimeSpan.TicksPerSecond) * 1000000) + / TimeSpan.TicksPerMillisecond); + return value.Ticks / TimeSpan.TicksPerSecond; + } + + /// + /// Parses a DateTime from a protobuf stream + /// + public static DateTime ReadDateTime(ProtoReader source) + { + long ticks = ReadTimeSpanTicks(source, out DateTimeKind kind); + if (ticks == long.MinValue) return DateTime.MinValue; + if (ticks == long.MaxValue) return DateTime.MaxValue; + return EpochOrigin[(int)kind].AddTicks(ticks); + } + + /// + /// Writes a DateTime to a protobuf stream, excluding the Kind + /// + public static void WriteDateTime(DateTime value, ProtoWriter dest) + { + WriteDateTimeImpl(value, dest, false); + } + + /// + /// Writes a DateTime to a protobuf stream, including the Kind + /// + public static void WriteDateTimeWithKind(DateTime value, ProtoWriter dest) + { + WriteDateTimeImpl(value, dest, true); + } + + private static void WriteDateTimeImpl(DateTime value, ProtoWriter dest, bool includeKind) + { + if (dest == null) throw new ArgumentNullException(nameof(dest)); + TimeSpan delta; + switch (dest.WireType) + { + case WireType.StartGroup: + case WireType.String: + if (value == DateTime.MaxValue) + { + delta = TimeSpan.MaxValue; + includeKind = false; + } + else if (value == DateTime.MinValue) + { + delta = TimeSpan.MinValue; + includeKind = false; + } + else + { + delta = value - EpochOrigin[0]; + } + break; + default: + delta = value - EpochOrigin[0]; + break; + } + WriteTimeSpanImpl(delta, dest, includeKind ? value.Kind : DateTimeKind.Unspecified); + } + + private static long ReadTimeSpanTicks(ProtoReader source, out DateTimeKind kind) + { + kind = DateTimeKind.Unspecified; + switch (source.WireType) + { + case WireType.String: + case WireType.StartGroup: + SubItemToken token = ProtoReader.StartSubItem(source); + int fieldNumber; + TimeSpanScale scale = TimeSpanScale.Days; + long value = 0; + while ((fieldNumber = source.ReadFieldHeader()) > 0) + { + switch (fieldNumber) + { + case FieldTimeSpanScale: + scale = (TimeSpanScale)source.ReadInt32(); + break; + case FieldTimeSpanValue: + source.Assert(WireType.SignedVariant); + value = source.ReadInt64(); + break; + case FieldTimeSpanKind: + kind = (DateTimeKind)source.ReadInt32(); + switch (kind) + { + case DateTimeKind.Unspecified: + case DateTimeKind.Utc: + case DateTimeKind.Local: + break; // fine + default: + throw new ProtoException("Invalid date/time kind: " + kind.ToString()); + } + break; + default: + source.SkipField(); + break; + } + } + ProtoReader.EndSubItem(token, source); + switch (scale) + { + case TimeSpanScale.Days: + return value * TimeSpan.TicksPerDay; + case TimeSpanScale.Hours: + return value * TimeSpan.TicksPerHour; + case TimeSpanScale.Minutes: + return value * TimeSpan.TicksPerMinute; + case TimeSpanScale.Seconds: + return value * TimeSpan.TicksPerSecond; + case TimeSpanScale.Milliseconds: + return value * TimeSpan.TicksPerMillisecond; + case TimeSpanScale.Ticks: + return value; + case TimeSpanScale.MinMax: + switch (value) + { + case 1: return long.MaxValue; + case -1: return long.MinValue; + default: throw new ProtoException("Unknown min/max value: " + value.ToString()); + } + default: + throw new ProtoException("Unknown timescale: " + scale.ToString()); + } + case WireType.Fixed64: + return source.ReadInt64(); + default: + throw new ProtoException("Unexpected wire-type: " + source.WireType.ToString()); + } + } + + const int FieldDecimalLow = 0x01, FieldDecimalHigh = 0x02, FieldDecimalSignScale = 0x03; + + /// + /// Parses a decimal from a protobuf stream + /// + public static decimal ReadDecimal(ProtoReader reader) + { + ulong low = 0; + uint high = 0; + uint signScale = 0; + + int fieldNumber; + SubItemToken token = ProtoReader.StartSubItem(reader); + while ((fieldNumber = reader.ReadFieldHeader()) > 0) + { + switch (fieldNumber) + { + case FieldDecimalLow: low = reader.ReadUInt64(); break; + case FieldDecimalHigh: high = reader.ReadUInt32(); break; + case FieldDecimalSignScale: signScale = reader.ReadUInt32(); break; + default: reader.SkipField(); break; + } + + } + ProtoReader.EndSubItem(token, reader); + + int lo = (int)(low & 0xFFFFFFFFL), + mid = (int)((low >> 32) & 0xFFFFFFFFL), + hi = (int)high; + bool isNeg = (signScale & 0x0001) == 0x0001; + byte scale = (byte)((signScale & 0x01FE) >> 1); + return new decimal(lo, mid, hi, isNeg, scale); + } + + /// + /// Writes a decimal to a protobuf stream + /// + public static void WriteDecimal(decimal value, ProtoWriter writer) + { + int[] bits = decimal.GetBits(value); + ulong a = ((ulong)bits[1]) << 32, b = ((ulong)bits[0]) & 0xFFFFFFFFL; + ulong low = a | b; + uint high = (uint)bits[2]; + uint signScale = (uint)(((bits[3] >> 15) & 0x01FE) | ((bits[3] >> 31) & 0x0001)); + + SubItemToken token = ProtoWriter.StartSubItem(null, writer); + if (low != 0) + { + ProtoWriter.WriteFieldHeader(FieldDecimalLow, WireType.Variant, writer); + ProtoWriter.WriteUInt64(low, writer); + } + if (high != 0) + { + ProtoWriter.WriteFieldHeader(FieldDecimalHigh, WireType.Variant, writer); + ProtoWriter.WriteUInt32(high, writer); + } + if (signScale != 0) + { + ProtoWriter.WriteFieldHeader(FieldDecimalSignScale, WireType.Variant, writer); + ProtoWriter.WriteUInt32(signScale, writer); + } + ProtoWriter.EndSubItem(token, writer); + } + + const int FieldGuidLow = 1, FieldGuidHigh = 2; + /// + /// Writes a Guid to a protobuf stream + /// + public static void WriteGuid(Guid value, ProtoWriter dest) + { + byte[] blob = value.ToByteArray(); + + SubItemToken token = ProtoWriter.StartSubItem(null, dest); + if (value != Guid.Empty) + { + ProtoWriter.WriteFieldHeader(FieldGuidLow, WireType.Fixed64, dest); + ProtoWriter.WriteBytes(blob, 0, 8, dest); + ProtoWriter.WriteFieldHeader(FieldGuidHigh, WireType.Fixed64, dest); + ProtoWriter.WriteBytes(blob, 8, 8, dest); + } + ProtoWriter.EndSubItem(token, dest); + } + /// + /// Parses a Guid from a protobuf stream + /// + public static Guid ReadGuid(ProtoReader source) + { + ulong low = 0, high = 0; + int fieldNumber; + SubItemToken token = ProtoReader.StartSubItem(source); + while ((fieldNumber = source.ReadFieldHeader()) > 0) + { + switch (fieldNumber) + { + case FieldGuidLow: low = source.ReadUInt64(); break; + case FieldGuidHigh: high = source.ReadUInt64(); break; + default: source.SkipField(); break; + } + } + ProtoReader.EndSubItem(token, source); + if (low == 0 && high == 0) return Guid.Empty; + uint a = (uint)(low >> 32), b = (uint)low, c = (uint)(high >> 32), d = (uint)high; + return new Guid((int)b, (short)a, (short)(a >> 16), + (byte)d, (byte)(d >> 8), (byte)(d >> 16), (byte)(d >> 24), + (byte)c, (byte)(c >> 8), (byte)(c >> 16), (byte)(c >> 24)); + + } + + + private const int + FieldExistingObjectKey = 1, + FieldNewObjectKey = 2, + FieldExistingTypeKey = 3, + FieldNewTypeKey = 4, + FieldTypeName = 8, + FieldObject = 10; + + /// + /// Optional behaviours that introduce .NET-specific functionality + /// + [Flags] + public enum NetObjectOptions : byte + { + /// + /// No special behaviour + /// + None = 0, + /// + /// Enables full object-tracking/full-graph support. + /// + AsReference = 1, + /// + /// Embeds the type information into the stream, allowing usage with types not known in advance. + /// + DynamicType = 2, + /// + /// If false, the constructor for the type is bypassed during deserialization, meaning any field initializers + /// or other initialization code is skipped. + /// + UseConstructor = 4, + /// + /// Should the object index be reserved, rather than creating an object promptly + /// + LateSet = 8 + } + + /// + /// Reads an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc. + /// + public static object ReadNetObject(object value, ProtoReader source, int key, Type type, NetObjectOptions options) + { + SubItemToken token = ProtoReader.StartSubItem(source); + int fieldNumber; + int newObjectKey = -1, newTypeKey = -1, tmp; + while ((fieldNumber = source.ReadFieldHeader()) > 0) + { + switch (fieldNumber) + { + case FieldExistingObjectKey: + tmp = source.ReadInt32(); + value = source.NetCache.GetKeyedObject(tmp); + break; + case FieldNewObjectKey: + newObjectKey = source.ReadInt32(); + break; + case FieldExistingTypeKey: + tmp = source.ReadInt32(); + type = (Type)source.NetCache.GetKeyedObject(tmp); + key = source.GetTypeKey(ref type); + break; + case FieldNewTypeKey: + newTypeKey = source.ReadInt32(); + break; + case FieldTypeName: + string typeName = source.ReadString(); + type = source.DeserializeType(typeName); + if (type == null) + { + throw new ProtoException("Unable to resolve type: " + typeName + " (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)"); + } + if (type == typeof(string)) + { + key = -1; + } + else + { + key = source.GetTypeKey(ref type); + if (key < 0) + throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name); + } + break; + case FieldObject: + bool isString = type == typeof(string); + bool wasNull = value == null; + bool lateSet = wasNull && (isString || ((options & NetObjectOptions.LateSet) != 0)); + + if (newObjectKey >= 0 && !lateSet) + { + if (value == null) + { + source.TrapNextObject(newObjectKey); + } + else + { + source.NetCache.SetKeyedObject(newObjectKey, value); + } + if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type); + } + object oldValue = value; + if (isString) + { + value = source.ReadString(); + } + else + { + value = ProtoReader.ReadTypedObject(oldValue, key, source, type); + } + + if (newObjectKey >= 0) + { + if (wasNull && !lateSet) + { // this both ensures (via exception) that it *was* set, and makes sure we don't shout + // about changed references + oldValue = source.NetCache.GetKeyedObject(newObjectKey); + } + if (lateSet) + { + source.NetCache.SetKeyedObject(newObjectKey, value); + if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type); + } + } + if (newObjectKey >= 0 && !lateSet && !ReferenceEquals(oldValue, value)) + { + throw new ProtoException("A reference-tracked object changed reference during deserialization"); + } + if (newObjectKey < 0 && newTypeKey >= 0) + { // have a new type, but not a new object + source.NetCache.SetKeyedObject(newTypeKey, type); + } + break; + default: + source.SkipField(); + break; + } + } + if (newObjectKey >= 0 && (options & NetObjectOptions.AsReference) == 0) + { + throw new ProtoException("Object key in input stream, but reference-tracking was not expected"); + } + ProtoReader.EndSubItem(token, source); + + return value; + } + + /// + /// Writes an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc. + /// + public static void WriteNetObject(object value, ProtoWriter dest, int key, NetObjectOptions options) + { + if (dest == null) throw new ArgumentNullException("dest"); + bool dynamicType = (options & NetObjectOptions.DynamicType) != 0, + asReference = (options & NetObjectOptions.AsReference) != 0; + WireType wireType = dest.WireType; + SubItemToken token = ProtoWriter.StartSubItem(null, dest); + bool writeObject = true; + if (asReference) + { + int objectKey = dest.NetCache.AddObjectKey(value, out bool existing); + ProtoWriter.WriteFieldHeader(existing ? FieldExistingObjectKey : FieldNewObjectKey, WireType.Variant, dest); + ProtoWriter.WriteInt32(objectKey, dest); + if (existing) + { + writeObject = false; + } + } + + if (writeObject) + { + if (dynamicType) + { + Type type = value.GetType(); + + if (!(value is string)) + { + key = dest.GetTypeKey(ref type); + if (key < 0) throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name); + } + int typeKey = dest.NetCache.AddObjectKey(type, out bool existing); + ProtoWriter.WriteFieldHeader(existing ? FieldExistingTypeKey : FieldNewTypeKey, WireType.Variant, dest); + ProtoWriter.WriteInt32(typeKey, dest); + if (!existing) + { + ProtoWriter.WriteFieldHeader(FieldTypeName, WireType.String, dest); + ProtoWriter.WriteString(dest.SerializeType(type), dest); + } + + } + ProtoWriter.WriteFieldHeader(FieldObject, wireType, dest); + if (value is string) + { + ProtoWriter.WriteString((string)value, dest); + } + else + { + ProtoWriter.WriteObject(value, key, dest); + } + } + ProtoWriter.EndSubItem(token, dest); + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/BclHelpers.cs.meta b/Runtime/Protobuf-net/BclHelpers.cs.meta new file mode 100644 index 0000000..da3a37a --- /dev/null +++ b/Runtime/Protobuf-net/BclHelpers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5072fbed211eb9f43a3cd2805dd75ef7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/BufferExtension.cs b/Runtime/Protobuf-net/BufferExtension.cs new file mode 100644 index 0000000..ea428dd --- /dev/null +++ b/Runtime/Protobuf-net/BufferExtension.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; + +namespace ProtoBuf +{ + /// + /// Provides a simple buffer-based implementation of an extension object. + /// + public sealed class BufferExtension : IExtension, IExtensionResettable + { + private byte[] buffer; + + void IExtensionResettable.Reset() + { + buffer = null; + } + + int IExtension.GetLength() + { + return buffer == null ? 0 : buffer.Length; + } + + Stream IExtension.BeginAppend() + { + return new MemoryStream(); + } + + void IExtension.EndAppend(Stream stream, bool commit) + { + using (stream) + { + int len; + if (commit && (len = (int)stream.Length) > 0) + { + MemoryStream ms = (MemoryStream)stream; + + if (buffer == null) + { // allocate new buffer + buffer = ms.ToArray(); + } + else + { // resize and copy the data + // note: Array.Resize not available on CF + int offset = buffer.Length; + byte[] tmp = new byte[offset + len]; + Buffer.BlockCopy(buffer, 0, tmp, 0, offset); + +#if PORTABLE // no GetBuffer() - fine, we'll use Read instead + int bytesRead; + long oldPos = ms.Position; + ms.Position = 0; + while (len > 0 && (bytesRead = ms.Read(tmp, offset, len)) > 0) + { + len -= bytesRead; + offset += bytesRead; + } + if(len != 0) throw new EndOfStreamException(); + ms.Position = oldPos; +#else + Buffer.BlockCopy(Helpers.GetBuffer(ms), 0, tmp, offset, len); +#endif + buffer = tmp; + } + } + } + } + + Stream IExtension.BeginQuery() + { + return buffer == null ? Stream.Null : new MemoryStream(buffer); + } + + void IExtension.EndQuery(Stream stream) + { + using (stream) { } // just clean up + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/BufferExtension.cs.meta b/Runtime/Protobuf-net/BufferExtension.cs.meta new file mode 100644 index 0000000..4a39591 --- /dev/null +++ b/Runtime/Protobuf-net/BufferExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a9cf66041a027e94892d5014c2b905b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/BufferPool.cs b/Runtime/Protobuf-net/BufferPool.cs new file mode 100644 index 0000000..8ad3d1a --- /dev/null +++ b/Runtime/Protobuf-net/BufferPool.cs @@ -0,0 +1,149 @@ +using System; + +namespace ProtoBuf +{ + internal sealed class BufferPool + { + internal static void Flush() + { + lock (Pool) + { + for (var i = 0; i < Pool.Length; i++) + Pool[i] = null; + } + } + + private BufferPool() { } + private const int POOL_SIZE = 20; + internal const int BUFFER_LENGTH = 1024; + private static readonly CachedBuffer[] Pool = new CachedBuffer[POOL_SIZE]; + + internal static byte[] GetBuffer() => GetBuffer(BUFFER_LENGTH); + + internal static byte[] GetBuffer(int minSize) + { + byte[] cachedBuff = GetCachedBuffer(minSize); + return cachedBuff ?? new byte[minSize]; + } + + internal static byte[] GetCachedBuffer(int minSize) + { + lock (Pool) + { + var bestIndex = -1; + byte[] bestMatch = null; + for (var i = 0; i < Pool.Length; i++) + { + var buffer = Pool[i]; + if (buffer == null || buffer.Size < minSize) + { + continue; + } + if (bestMatch != null && bestMatch.Length < buffer.Size) + { + continue; + } + + var tmp = buffer.Buffer; + if (tmp == null) + { + Pool[i] = null; + } + else + { + bestMatch = tmp; + bestIndex = i; + } + } + + if (bestIndex >= 0) + { + Pool[bestIndex] = null; + } + + return bestMatch; + } + } + + /// + /// https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element + /// + private const int MaxByteArraySize = int.MaxValue - 56; + + internal static void ResizeAndFlushLeft(ref byte[] buffer, int toFitAtLeastBytes, int copyFromIndex, int copyBytes) + { + Helpers.DebugAssert(buffer != null); + Helpers.DebugAssert(toFitAtLeastBytes > buffer.Length); + Helpers.DebugAssert(copyFromIndex >= 0); + Helpers.DebugAssert(copyBytes >= 0); + + int newLength = buffer.Length * 2; + if (newLength < 0) + { + newLength = MaxByteArraySize; + } + + if (newLength < toFitAtLeastBytes) newLength = toFitAtLeastBytes; + + if (copyBytes == 0) + { + ReleaseBufferToPool(ref buffer); + } + + var newBuffer = GetCachedBuffer(toFitAtLeastBytes) ?? new byte[newLength]; + + if (copyBytes > 0) + { + Buffer.BlockCopy(buffer, copyFromIndex, newBuffer, 0, copyBytes); + ReleaseBufferToPool(ref buffer); + } + + buffer = newBuffer; + } + + internal static void ReleaseBufferToPool(ref byte[] buffer) + { + if (buffer == null) return; + + lock (Pool) + { + var minIndex = 0; + var minSize = int.MaxValue; + for (var i = 0; i < Pool.Length; i++) + { + var tmp = Pool[i]; + if (tmp == null || !tmp.IsAlive) + { + minIndex = 0; + break; + } + if (tmp.Size < minSize) + { + minIndex = i; + minSize = tmp.Size; + } + } + + Pool[minIndex] = new CachedBuffer(buffer); + } + + buffer = null; + } + + private class CachedBuffer + { + private readonly WeakReference _reference; + + public int Size { get; } + + public bool IsAlive => _reference.IsAlive; + public byte[] Buffer => (byte[])_reference.Target; + + public CachedBuffer(byte[] buffer) + { + Size = buffer.Length; + _reference = new WeakReference(buffer); + } + } + } +} diff --git a/Runtime/Protobuf-net/BufferPool.cs.meta b/Runtime/Protobuf-net/BufferPool.cs.meta new file mode 100644 index 0000000..2870b8c --- /dev/null +++ b/Runtime/Protobuf-net/BufferPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 423b228ed060b91458bc6d4e6aa0f570 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/CallbackAttributes.cs b/Runtime/Protobuf-net/CallbackAttributes.cs new file mode 100644 index 0000000..1adb8e5 --- /dev/null +++ b/Runtime/Protobuf-net/CallbackAttributes.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel; + +namespace ProtoBuf +{ + /// Specifies a method on the root-contract in an hierarchy to be invoked before serialization. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +#if !CF && !PORTABLE && !COREFX && !PROFILE259 + [ImmutableObject(true)] +#endif + public sealed class ProtoBeforeSerializationAttribute : Attribute { } + + /// Specifies a method on the root-contract in an hierarchy to be invoked after serialization. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +#if !CF && !PORTABLE && !COREFX && !PROFILE259 + [ImmutableObject(true)] +#endif + public sealed class ProtoAfterSerializationAttribute : Attribute { } + + /// Specifies a method on the root-contract in an hierarchy to be invoked before deserialization. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +#if !CF && !PORTABLE && !COREFX && !PROFILE259 + [ImmutableObject(true)] +#endif + public sealed class ProtoBeforeDeserializationAttribute : Attribute { } + + /// Specifies a method on the root-contract in an hierarchy to be invoked after deserialization. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +#if !CF && !PORTABLE && !COREFX && !PROFILE259 + [ImmutableObject(true)] +#endif + public sealed class ProtoAfterDeserializationAttribute : Attribute { } +} diff --git a/Runtime/Protobuf-net/CallbackAttributes.cs.meta b/Runtime/Protobuf-net/CallbackAttributes.cs.meta new file mode 100644 index 0000000..7cf81a4 --- /dev/null +++ b/Runtime/Protobuf-net/CallbackAttributes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53de2cb3784c9dd43aa6f30d7df072a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Compiler.meta b/Runtime/Protobuf-net/Compiler.meta new file mode 100644 index 0000000..9de78a6 --- /dev/null +++ b/Runtime/Protobuf-net/Compiler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2cdd9eb2afa3ed24480a6035f507aad4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Compiler/CompilerContext.cs b/Runtime/Protobuf-net/Compiler/CompilerContext.cs new file mode 100644 index 0000000..6100200 --- /dev/null +++ b/Runtime/Protobuf-net/Compiler/CompilerContext.cs @@ -0,0 +1,1435 @@ +#if FEAT_COMPILER +//#define DEBUG_COMPILE +using System; +using System.Threading; +using ProtoBuf.Meta; +using ProtoBuf.Serializers; +using System.Reflection; +using System.Reflection.Emit; + +namespace ProtoBuf.Compiler +{ + internal readonly struct CodeLabel + { + public readonly Label Value; + public readonly int Index; + public CodeLabel(Label value, int index) + { + this.Value = value; + this.Index = index; + } + } + internal sealed class CompilerContext + { + public TypeModel Model => model; + + readonly DynamicMethod method; + static int next; + + internal CodeLabel DefineLabel() + { + CodeLabel result = new CodeLabel(il.DefineLabel(), nextLabel++); + return result; + } +#if DEBUG_COMPILE + static readonly string traceCompilePath; + static CompilerContext() + { + traceCompilePath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), + "TraceCompile.txt"); + Console.WriteLine("DEBUG_COMPILE enabled; writing to " + traceCompilePath); + } +#endif + [System.Diagnostics.Conditional("DEBUG_COMPILE")] + private void TraceCompile(string value) + { +#if DEBUG_COMPILE + if (!string.IsNullOrWhiteSpace(value)) + { + using (System.IO.StreamWriter sw = System.IO.File.AppendText(traceCompilePath)) + { + sw.WriteLine(value); + } + } +#endif + } + internal void MarkLabel(CodeLabel label) + { + il.MarkLabel(label.Value); + TraceCompile("#: " + label.Index); + } + + public static ProtoSerializer BuildSerializer(IProtoSerializer head, TypeModel model) + { + Type type = head.ExpectedType; + try + { + CompilerContext ctx = new CompilerContext(type, true, true, model, typeof(object)); + ctx.LoadValue(ctx.InputValue); + ctx.CastFromObject(type); + ctx.WriteNullCheckedTail(type, head, null); + ctx.Emit(OpCodes.Ret); + return (ProtoSerializer)ctx.method.CreateDelegate( + typeof(ProtoSerializer)); + } + catch (Exception ex) + { + string name = type.FullName; + if (string.IsNullOrEmpty(name)) name = type.Name; + throw new InvalidOperationException("It was not possible to prepare a serializer for: " + name, ex); + } + } + /*public static ProtoCallback BuildCallback(IProtoTypeSerializer head) + { + Type type = head.ExpectedType; + CompilerContext ctx = new CompilerContext(type, true, true); + using (Local typedVal = new Local(ctx, type)) + { + ctx.LoadValue(Local.InputValue); + ctx.CastFromObject(type); + ctx.StoreValue(typedVal); + CodeLabel[] jumpTable = new CodeLabel[4]; + for(int i = 0 ; i < jumpTable.Length ; i++) { + jumpTable[i] = ctx.DefineLabel(); + } + ctx.LoadReaderWriter(); + ctx.Switch(jumpTable); + ctx.Return(); + for(int i = 0 ; i < jumpTable.Length ; i++) { + ctx.MarkLabel(jumpTable[i]); + if (head.HasCallbacks((TypeModel.CallbackType)i)) + { + head.EmitCallback(ctx, typedVal, (TypeModel.CallbackType)i); + } + ctx.Return(); + } + } + + ctx.Emit(OpCodes.Ret); + return (ProtoCallback)ctx.method.CreateDelegate( + typeof(ProtoCallback)); + }*/ + public static ProtoDeserializer BuildDeserializer(IProtoSerializer head, TypeModel model) + { + Type type = head.ExpectedType; + CompilerContext ctx = new CompilerContext(type, false, true, model, typeof(object)); + + using (Local typedVal = new Local(ctx, type)) + { + if (!Helpers.IsValueType(type)) + { + ctx.LoadValue(ctx.InputValue); + ctx.CastFromObject(type); + ctx.StoreValue(typedVal); + } + else + { + ctx.LoadValue(ctx.InputValue); + CodeLabel notNull = ctx.DefineLabel(), endNull = ctx.DefineLabel(); + ctx.BranchIfTrue(notNull, true); + + ctx.LoadAddress(typedVal, type); + ctx.EmitCtor(type); + ctx.Branch(endNull, true); + + ctx.MarkLabel(notNull); + ctx.LoadValue(ctx.InputValue); + ctx.CastFromObject(type); + ctx.StoreValue(typedVal); + + ctx.MarkLabel(endNull); + } + head.EmitRead(ctx, typedVal); + + if (head.ReturnsValue) + { + ctx.StoreValue(typedVal); + } + + ctx.LoadValue(typedVal); + ctx.CastToObject(type); + } + ctx.Emit(OpCodes.Ret); + return (ProtoDeserializer)ctx.method.CreateDelegate( + typeof(ProtoDeserializer)); + } + + internal void Return() + { + Emit(OpCodes.Ret); + } + + static bool IsObject(Type type) + { + return type == typeof(object); + } + + internal void CastToObject(Type type) + { + if (IsObject(type)) + { } + else if (Helpers.IsValueType(type)) + { + il.Emit(OpCodes.Box, type); + TraceCompile(OpCodes.Box + ": " + type); + } + else + { + il.Emit(OpCodes.Castclass, MapType(typeof(object))); + TraceCompile(OpCodes.Castclass + ": " + type); + } + } + + internal void CastFromObject(Type type) + { + if (IsObject(type)) + { } + else if (Helpers.IsValueType(type)) + { + switch (MetadataVersion) + { + case ILVersion.Net1: + il.Emit(OpCodes.Unbox, type); + il.Emit(OpCodes.Ldobj, type); + TraceCompile(OpCodes.Unbox + ": " + type); + TraceCompile(OpCodes.Ldobj + ": " + type); + break; + default: + + il.Emit(OpCodes.Unbox_Any, type); + TraceCompile(OpCodes.Unbox_Any + ": " + type); + break; + } + } + else + { + il.Emit(OpCodes.Castclass, type); + TraceCompile(OpCodes.Castclass + ": " + type); + } + } + private readonly bool isStatic; + private readonly RuntimeTypeModel.SerializerPair[] methodPairs; + + internal MethodBuilder GetDedicatedMethod(int metaKey, bool read) + { + if (methodPairs == null) return null; + // but if we *do* have pairs, we demand that we find a match... + for (int i = 0; i < methodPairs.Length; i++) + { + if (methodPairs[i].MetaKey == metaKey) { return read ? methodPairs[i].Deserialize : methodPairs[i].Serialize; } + } + throw new ArgumentException("Meta-key not found", "metaKey"); + } + + internal int MapMetaKeyToCompiledKey(int metaKey) + { + if (metaKey < 0 || methodPairs == null) return metaKey; // all meta, or a dummy/wildcard key + + for (int i = 0; i < methodPairs.Length; i++) + { + if (methodPairs[i].MetaKey == metaKey) return i; + } + throw new ArgumentException("Key could not be mapped: " + metaKey.ToString(), "metaKey"); + } + + + private readonly bool isWriter; + + private readonly bool nonPublic; + internal bool NonPublic { get { return nonPublic; } } + + private readonly Local inputValue; + public Local InputValue { get { return inputValue; } } + + private readonly string assemblyName; + internal CompilerContext(ILGenerator il, bool isStatic, bool isWriter, RuntimeTypeModel.SerializerPair[] methodPairs, TypeModel model, ILVersion metadataVersion, string assemblyName, Type inputType, string traceName) + { + if (string.IsNullOrEmpty(assemblyName)) throw new ArgumentNullException(nameof(assemblyName)); + this.assemblyName = assemblyName; + this.isStatic = isStatic; + this.methodPairs = methodPairs ?? throw new ArgumentNullException(nameof(methodPairs)); + this.il = il ?? throw new ArgumentNullException(nameof(il)); + // nonPublic = false; <== implicit + this.isWriter = isWriter; + this.model = model ?? throw new ArgumentNullException(nameof(model)); + this.metadataVersion = metadataVersion; + if (inputType != null) this.inputValue = new Local(null, inputType); + TraceCompile(">> " + traceName); + } + + private CompilerContext(Type associatedType, bool isWriter, bool isStatic, TypeModel model, Type inputType) + { + metadataVersion = ILVersion.Net2; + this.isStatic = isStatic; + this.isWriter = isWriter; + this.model = model ?? throw new ArgumentNullException(nameof(model)); + nonPublic = true; + Type[] paramTypes; + Type returnType; + if (isWriter) + { + returnType = typeof(void); + paramTypes = new Type[] { typeof(object), typeof(ProtoWriter) }; + } + else + { + returnType = typeof(object); + paramTypes = new Type[] { typeof(object), typeof(ProtoReader) }; + } + int uniqueIdentifier; +#if PLAT_NO_INTERLOCKED + uniqueIdentifier = ++next; +#else + uniqueIdentifier = Interlocked.Increment(ref next); +#endif + method = new DynamicMethod("proto_" + uniqueIdentifier.ToString(), returnType, paramTypes, associatedType +#if COREFX + .GetTypeInfo() +#endif + .IsInterface ? typeof(object) : associatedType, true); + this.il = method.GetILGenerator(); + if (inputType != null) this.inputValue = new Local(null, inputType); + TraceCompile(">> " + method.Name); + } + + private readonly ILGenerator il; + + private void Emit(OpCode opcode) + { + il.Emit(opcode); + TraceCompile(opcode.ToString()); + } + + public void LoadValue(string value) + { + if (value == null) + { + LoadNullRef(); + } + else + { + il.Emit(OpCodes.Ldstr, value); + TraceCompile(OpCodes.Ldstr + ": " + value); + } + } + + public void LoadValue(float value) + { + il.Emit(OpCodes.Ldc_R4, value); + TraceCompile(OpCodes.Ldc_R4 + ": " + value); + } + + public void LoadValue(double value) + { + il.Emit(OpCodes.Ldc_R8, value); + TraceCompile(OpCodes.Ldc_R8 + ": " + value); + } + + public void LoadValue(long value) + { + il.Emit(OpCodes.Ldc_I8, value); + TraceCompile(OpCodes.Ldc_I8 + ": " + value); + } + + public void LoadValue(int value) + { + switch (value) + { + case 0: Emit(OpCodes.Ldc_I4_0); break; + case 1: Emit(OpCodes.Ldc_I4_1); break; + case 2: Emit(OpCodes.Ldc_I4_2); break; + case 3: Emit(OpCodes.Ldc_I4_3); break; + case 4: Emit(OpCodes.Ldc_I4_4); break; + case 5: Emit(OpCodes.Ldc_I4_5); break; + case 6: Emit(OpCodes.Ldc_I4_6); break; + case 7: Emit(OpCodes.Ldc_I4_7); break; + case 8: Emit(OpCodes.Ldc_I4_8); break; + case -1: Emit(OpCodes.Ldc_I4_M1); break; + default: + if (value >= -128 && value <= 127) + { + il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); + TraceCompile(OpCodes.Ldc_I4_S + ": " + value); + } + else + { + il.Emit(OpCodes.Ldc_I4, value); + TraceCompile(OpCodes.Ldc_I4 + ": " + value); + } + break; + + } + } + + MutableList locals = new MutableList(); + internal LocalBuilder GetFromPool(Type type) + { + int count = locals.Count; + for (int i = 0; i < count; i++) + { + LocalBuilder item = (LocalBuilder)locals[i]; + if (item != null && item.LocalType == type) + { + locals[i] = null; // remove from pool + return item; + } + } + LocalBuilder result = il.DeclareLocal(type); + TraceCompile("$ " + result + ": " + type); + return result; + } + + // + internal void ReleaseToPool(LocalBuilder value) + { + int count = locals.Count; + for (int i = 0; i < count; i++) + { + if (locals[i] == null) + { + locals[i] = value; // released into existing slot + return; + } + } + locals.Add(value); // create a new slot + } + + public void LoadReaderWriter() + { + Emit(isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2); + } + + public void StoreValue(Local local) + { + if (local == this.InputValue) + { + byte b = isStatic ? (byte)0 : (byte)1; + il.Emit(OpCodes.Starg_S, b); + TraceCompile(OpCodes.Starg_S + ": $" + b); + } + else + { + + switch (local.Value.LocalIndex) + { + case 0: Emit(OpCodes.Stloc_0); break; + case 1: Emit(OpCodes.Stloc_1); break; + case 2: Emit(OpCodes.Stloc_2); break; + case 3: Emit(OpCodes.Stloc_3); break; + default: + + OpCode code = UseShortForm(local) ? OpCodes.Stloc_S : OpCodes.Stloc; + il.Emit(code, local.Value); + TraceCompile(code + ": $" + local.Value); + + break; + } + } + } + + public void LoadValue(Local local) + { + if (local == null) { /* nothing to do; top of stack */} + else if (local == this.InputValue) + { + Emit(isStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1); + } + else + { + + switch (local.Value.LocalIndex) + { + case 0: Emit(OpCodes.Ldloc_0); break; + case 1: Emit(OpCodes.Ldloc_1); break; + case 2: Emit(OpCodes.Ldloc_2); break; + case 3: Emit(OpCodes.Ldloc_3); break; + default: + + OpCode code = UseShortForm(local) ? OpCodes.Ldloc_S : OpCodes.Ldloc; + il.Emit(code, local.Value); + TraceCompile(code + ": $" + local.Value); + + break; + } + } + } + + public Local GetLocalWithValue(Type type, Compiler.Local fromValue) + { + if (fromValue != null) + { + if (fromValue.Type == type) return fromValue.AsCopy(); + // otherwise, load onto the stack and let the default handling (below) deal with it + LoadValue(fromValue); + if (!Helpers.IsValueType(type) && (fromValue.Type == null || !type.IsAssignableFrom(fromValue.Type))) + { // need to cast + Cast(type); + } + } + // need to store the value from the stack + Local result = new Local(this, type); + StoreValue(result); + return result; + } + + internal void EmitBasicRead(string methodName, Type expectedType) + { + MethodInfo method = MapType(typeof(ProtoReader)).GetMethod( + methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (method == null || method.ReturnType != expectedType + || method.GetParameters().Length != 0) throw new ArgumentException("methodName"); + LoadReaderWriter(); + EmitCall(method); + } + + internal void EmitBasicRead(Type helperType, string methodName, Type expectedType) + { + MethodInfo method = helperType.GetMethod( + methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if (method == null || method.ReturnType != expectedType + || method.GetParameters().Length != 1) throw new ArgumentException("methodName"); + LoadReaderWriter(); + EmitCall(method); + } + + internal void EmitBasicWrite(string methodName, Compiler.Local fromValue) + { + if (string.IsNullOrEmpty(methodName)) throw new ArgumentNullException("methodName"); + LoadValue(fromValue); + LoadReaderWriter(); + EmitCall(GetWriterMethod(methodName)); + } + + private MethodInfo GetWriterMethod(string methodName) + { + Type writerType = MapType(typeof(ProtoWriter)); + MethodInfo[] methods = writerType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + foreach (MethodInfo method in methods) + { + if (method.Name != methodName) continue; + ParameterInfo[] pis = method.GetParameters(); + if (pis.Length == 2 && pis[1].ParameterType == writerType) return method; + } + throw new ArgumentException("No suitable method found for: " + methodName, "methodName"); + } + + internal void EmitWrite(Type helperType, string methodName, Compiler.Local valueFrom) + { + if (string.IsNullOrEmpty(methodName)) throw new ArgumentNullException("methodName"); + MethodInfo method = helperType.GetMethod( + methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if (method == null || method.ReturnType != MapType(typeof(void))) throw new ArgumentException("methodName"); + LoadValue(valueFrom); + LoadReaderWriter(); + EmitCall(method); + } + + public void EmitCall(MethodInfo method) { EmitCall(method, null); } + + public void EmitCall(MethodInfo method, Type targetType) + { + Helpers.DebugAssert(method != null); + MemberInfo member = method; + CheckAccessibility(ref member); + OpCode opcode; + if (method.IsStatic || Helpers.IsValueType(method.DeclaringType)) + { + opcode = OpCodes.Call; + } + else + { + opcode = OpCodes.Callvirt; + if (targetType != null && Helpers.IsValueType(targetType) && !Helpers.IsValueType(method.DeclaringType)) + { + Constrain(targetType); + } + } + il.EmitCall(opcode, method, null); + TraceCompile(opcode + ": " + method + " on " + method.DeclaringType + (targetType == null ? "" : (" via " + targetType))); + } + + /// + /// Pushes a null reference onto the stack. Note that this should only + /// be used to return a null (or set a variable to null); for null-tests + /// use BranchIfTrue / BranchIfFalse. + /// + public void LoadNullRef() + { + Emit(OpCodes.Ldnull); + } + + private int nextLabel; + + internal void WriteNullCheckedTail(Type type, IProtoSerializer tail, Compiler.Local valueFrom) + { + if (Helpers.IsValueType(type)) + { + Type underlyingType = Helpers.GetUnderlyingType(type); + + if (underlyingType == null) + { // not a nullable T; can invoke directly + tail.EmitWrite(this, valueFrom); + } + else + { // nullable T; check HasValue + using (Compiler.Local valOrNull = GetLocalWithValue(type, valueFrom)) + { + LoadAddress(valOrNull, type); + LoadValue(type.GetProperty("HasValue")); + CodeLabel @end = DefineLabel(); + BranchIfFalse(@end, false); + LoadAddress(valOrNull, type); + EmitCall(type.GetMethod("GetValueOrDefault", Helpers.EmptyTypes)); + tail.EmitWrite(this, null); + MarkLabel(@end); + } + } + } + else + { // ref-type; do a null-check + LoadValue(valueFrom); + CopyValue(); + CodeLabel hasVal = DefineLabel(), @end = DefineLabel(); + BranchIfTrue(hasVal, true); + DiscardValue(); + Branch(@end, false); + MarkLabel(hasVal); + tail.EmitWrite(this, null); + MarkLabel(@end); + } + } + + internal void ReadNullCheckedTail(Type type, IProtoSerializer tail, Compiler.Local valueFrom) + { + + Type underlyingType; + + if (Helpers.IsValueType(type) && (underlyingType = Helpers.GetUnderlyingType(type)) != null) + { + if (tail.RequiresOldValue) + { + // we expect the input value to be in valueFrom; need to unpack it from T? + using (Local loc = GetLocalWithValue(type, valueFrom)) + { + LoadAddress(loc, type); + EmitCall(type.GetMethod("GetValueOrDefault", Helpers.EmptyTypes)); + } + } + else + { + Helpers.DebugAssert(valueFrom == null); // not expecting a valueFrom in this case + } + tail.EmitRead(this, null); // either unwrapped on the stack or not provided + if (tail.ReturnsValue) + { + // now re-wrap the value + EmitCtor(type, underlyingType); + } + return; + } + + // either a ref-type of a non-nullable struct; treat "as is", even if null + // (the type-serializer will handle the null case; it needs to allow null + // inputs to perform the correct type of subclass creation) + tail.EmitRead(this, valueFrom); + } + + public void EmitCtor(Type type) + { + EmitCtor(type, Helpers.EmptyTypes); + } + + public void EmitCtor(ConstructorInfo ctor) + { + if (ctor == null) throw new ArgumentNullException("ctor"); + MemberInfo ctorMember = ctor; + CheckAccessibility(ref ctorMember); + il.Emit(OpCodes.Newobj, ctor); + TraceCompile(OpCodes.Newobj + ": " + ctor.DeclaringType); + } + + public void InitLocal(Type type, Compiler.Local target) + { + LoadAddress(target, type, evenIfClass: true); // for class, initobj is a load-null, store-indirect + il.Emit(OpCodes.Initobj, type); + TraceCompile(OpCodes.Initobj + ": " + type); + } + + public void EmitCtor(Type type, params Type[] parameterTypes) + { + Helpers.DebugAssert(type != null); + Helpers.DebugAssert(parameterTypes != null); + if (Helpers.IsValueType(type) && parameterTypes.Length == 0) + { + il.Emit(OpCodes.Initobj, type); + TraceCompile(OpCodes.Initobj + ": " + type); + } + else + { + ConstructorInfo ctor = Helpers.GetConstructor(type +#if COREFX + .GetTypeInfo() +#endif + , parameterTypes, true); + if (ctor == null) throw new InvalidOperationException("No suitable constructor found for " + type.FullName); + EmitCtor(ctor); + } + } + + BasicList knownTrustedAssemblies, knownUntrustedAssemblies; + + bool InternalsVisible(Assembly assembly) + { + if (string.IsNullOrEmpty(assemblyName)) return false; + if (knownTrustedAssemblies != null) + { + if (knownTrustedAssemblies.IndexOfReference(assembly) >= 0) + { + return true; + } + } + if (knownUntrustedAssemblies != null) + { + if (knownUntrustedAssemblies.IndexOfReference(assembly) >= 0) + { + return false; + } + } + bool isTrusted = false; + Type attributeType = MapType(typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute)); + if (attributeType == null) return false; + +#if COREFX + foreach (System.Runtime.CompilerServices.InternalsVisibleToAttribute attrib in assembly.GetCustomAttributes(attributeType)) +#else + foreach (System.Runtime.CompilerServices.InternalsVisibleToAttribute attrib in assembly.GetCustomAttributes(attributeType, false)) +#endif + { + if (attrib.AssemblyName == assemblyName || attrib.AssemblyName.StartsWith(assemblyName + ",")) + { + isTrusted = true; + break; + } + } + + if (isTrusted) + { + if (knownTrustedAssemblies == null) knownTrustedAssemblies = new BasicList(); + knownTrustedAssemblies.Add(assembly); + } + else + { + if (knownUntrustedAssemblies == null) knownUntrustedAssemblies = new BasicList(); + knownUntrustedAssemblies.Add(assembly); + } + return isTrusted; + } + + internal void CheckAccessibility(ref MemberInfo member) + { + if (member == null) + { + throw new ArgumentNullException(nameof(member)); + } +#if !COREFX + Type type; +#endif + if (!NonPublic) + { + if (member is FieldInfo && member.Name.StartsWith("<") & member.Name.EndsWith(">k__BackingField")) + { + var propName = member.Name.Substring(1, member.Name.Length - 17); + var prop = member.DeclaringType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + if (prop != null) member = prop; + } + bool isPublic; +#if COREFX + if (member is TypeInfo) + { + TypeInfo ti = (TypeInfo)member; + do + { + isPublic = ti.IsNestedPublic || ti.IsPublic || ((ti.IsNested || ti.IsNestedAssembly || ti.IsNestedFamORAssem) && InternalsVisible(ti.Assembly)); + } while (isPublic && ti.IsNested && (ti = ti.DeclaringType.GetTypeInfo()) != null); + } + else if (member is FieldInfo) + { + FieldInfo field = ((FieldInfo)member); + isPublic = field.IsPublic || ((field.IsAssembly || field.IsFamilyOrAssembly) && InternalsVisible(Helpers.GetAssembly(field.DeclaringType))); + } + else if (member is PropertyInfo) + { + isPublic = true; // defer to get/set + } + else if (member is ConstructorInfo) + { + ConstructorInfo ctor = ((ConstructorInfo)member); + isPublic = ctor.IsPublic || ((ctor.IsAssembly || ctor.IsFamilyOrAssembly) && InternalsVisible(Helpers.GetAssembly(ctor.DeclaringType))); + } + else if (member is MethodInfo) + { + MethodInfo method = ((MethodInfo)member); + isPublic = method.IsPublic || ((method.IsAssembly || method.IsFamilyOrAssembly) && InternalsVisible(Helpers.GetAssembly(method.DeclaringType))); + if (!isPublic) + { + // allow calls to TypeModel protected methods, and methods we are in the process of creating + if ( + member is MethodBuilder || + member.DeclaringType == MapType(typeof(TypeModel))) + isPublic = true; + } + } + else + { + throw new NotSupportedException(member.GetType().Name); + } +#else + MemberTypes memberType = member.MemberType; + switch (memberType) + { + case MemberTypes.TypeInfo: + // top-level type + type = (Type)member; + isPublic = type.IsPublic || InternalsVisible(type.Assembly); + break; + case MemberTypes.NestedType: + type = (Type)member; + do + { + isPublic = type.IsNestedPublic || type.IsPublic || ((type.DeclaringType == null || type.IsNestedAssembly || type.IsNestedFamORAssem) && InternalsVisible(type.Assembly)); + } while (isPublic && (type = type.DeclaringType) != null); // ^^^ !type.IsNested, but not all runtimes have that + break; + case MemberTypes.Field: + FieldInfo field = ((FieldInfo)member); + isPublic = field.IsPublic || ((field.IsAssembly || field.IsFamilyOrAssembly) && InternalsVisible(field.DeclaringType.Assembly)); + break; + case MemberTypes.Constructor: + ConstructorInfo ctor = ((ConstructorInfo)member); + isPublic = ctor.IsPublic || ((ctor.IsAssembly || ctor.IsFamilyOrAssembly) && InternalsVisible(ctor.DeclaringType.Assembly)); + break; + case MemberTypes.Method: + MethodInfo method = ((MethodInfo)member); + isPublic = method.IsPublic || ((method.IsAssembly || method.IsFamilyOrAssembly) && InternalsVisible(method.DeclaringType.Assembly)); + if (!isPublic) + { + // allow calls to TypeModel protected methods, and methods we are in the process of creating + if ( + member is MethodBuilder || + member.DeclaringType == MapType(typeof(TypeModel))) isPublic = true; + } + break; + case MemberTypes.Property: + isPublic = true; // defer to get/set + break; + default: + throw new NotSupportedException(memberType.ToString()); + } +#endif + if (!isPublic) + { +#if COREFX + if (member is TypeInfo) + { + throw new InvalidOperationException("Non-public type cannot be used with full dll compilation: " + + ((TypeInfo)member).FullName); + } + else + { + throw new InvalidOperationException("Non-public member cannot be used with full dll compilation: " + + member.DeclaringType.FullName + "." + member.Name); + } + +#else + switch (memberType) + { + case MemberTypes.TypeInfo: + case MemberTypes.NestedType: + throw new InvalidOperationException("Non-public type cannot be used with full dll compilation: " + + ((Type)member).FullName); + default: + throw new InvalidOperationException("Non-public member cannot be used with full dll compilation: " + + member.DeclaringType.FullName + "." + member.Name); + } +#endif + + } + } + } + + public void LoadValue(FieldInfo field) + { + MemberInfo member = field; + CheckAccessibility(ref member); + if (member is PropertyInfo) + { + LoadValue((PropertyInfo)member); + } + else + { + OpCode code = field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld; + il.Emit(code, field); + TraceCompile(code + ": " + field + " on " + field.DeclaringType); + } + } + + public void StoreValue(FieldInfo field) + { + MemberInfo member = field; + CheckAccessibility(ref member); + if (member is PropertyInfo) + { + StoreValue((PropertyInfo)member); + } + else + { + OpCode code = field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld; + il.Emit(code, field); + TraceCompile(code + ": " + field + " on " + field.DeclaringType); + } + } + + public void LoadValue(PropertyInfo property) + { + MemberInfo member = property; + CheckAccessibility(ref member); + EmitCall(Helpers.GetGetMethod(property, true, true)); + } + + public void StoreValue(PropertyInfo property) + { + MemberInfo member = property; + CheckAccessibility(ref member); + EmitCall(Helpers.GetSetMethod(property, true, true)); + } + + //internal void EmitInstance() + //{ + // if (isStatic) throw new InvalidOperationException(); + // Emit(OpCodes.Ldarg_0); + //} + + internal static void LoadValue(ILGenerator il, int value) + { + switch (value) + { + case 0: il.Emit(OpCodes.Ldc_I4_0); break; + case 1: il.Emit(OpCodes.Ldc_I4_1); break; + case 2: il.Emit(OpCodes.Ldc_I4_2); break; + case 3: il.Emit(OpCodes.Ldc_I4_3); break; + case 4: il.Emit(OpCodes.Ldc_I4_4); break; + case 5: il.Emit(OpCodes.Ldc_I4_5); break; + case 6: il.Emit(OpCodes.Ldc_I4_6); break; + case 7: il.Emit(OpCodes.Ldc_I4_7); break; + case 8: il.Emit(OpCodes.Ldc_I4_8); break; + case -1: il.Emit(OpCodes.Ldc_I4_M1); break; + default: il.Emit(OpCodes.Ldc_I4, value); break; + } + } + + private bool UseShortForm(Local local) + { + return local.Value.LocalIndex < 256; + } + + internal void LoadAddress(Local local, Type type, bool evenIfClass = false) + { + if (evenIfClass || Helpers.IsValueType(type)) + { + if (local == null) + { + throw new InvalidOperationException("Cannot load the address of the head of the stack"); + } + + if (local == this.InputValue) + { + il.Emit(OpCodes.Ldarga_S, (isStatic ? (byte)0 : (byte)1)); + TraceCompile(OpCodes.Ldarga_S + ": $" + (isStatic ? 0 : 1)); + } + else + { + OpCode code = UseShortForm(local) ? OpCodes.Ldloca_S : OpCodes.Ldloca; + il.Emit(code, local.Value); + TraceCompile(code + ": $" + local.Value); + } + + } + else + { // reference-type; already *is* the address; just load it + LoadValue(local); + } + } + + internal void Branch(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Br_S : OpCodes.Br; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + internal void BranchIfFalse(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Brfalse_S : OpCodes.Brfalse; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + internal void BranchIfTrue(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Brtrue_S : OpCodes.Brtrue; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + internal void BranchIfEqual(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Beq_S : OpCodes.Beq; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + //internal void TestEqual() + //{ + // Emit(OpCodes.Ceq); + //} + + internal void CopyValue() + { + Emit(OpCodes.Dup); + } + + internal void BranchIfGreater(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Bgt_S : OpCodes.Bgt; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + internal void BranchIfLess(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Blt_S : OpCodes.Blt; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + internal void DiscardValue() + { + Emit(OpCodes.Pop); + } + + public void Subtract() + { + Emit(OpCodes.Sub); + } + + public void Switch(CodeLabel[] jumpTable) + { + const int MAX_JUMPS = 128; + + if (jumpTable.Length <= MAX_JUMPS) + { + // simple case + Label[] labels = new Label[jumpTable.Length]; + for (int i = 0; i < labels.Length; i++) + { + labels[i] = jumpTable[i].Value; + } + TraceCompile(OpCodes.Switch.ToString()); + il.Emit(OpCodes.Switch, labels); + } + else + { + // too many to jump easily (especially on Android) - need to split up (note: uses a local pulled from the stack) + using (Local val = GetLocalWithValue(MapType(typeof(int)), null)) + { + int count = jumpTable.Length, offset = 0; + int blockCount = count / MAX_JUMPS; + if ((count % MAX_JUMPS) != 0) blockCount++; + + Label[] blockLabels = new Label[blockCount]; + for (int i = 0; i < blockCount; i++) + { + blockLabels[i] = il.DefineLabel(); + } + CodeLabel endOfSwitch = DefineLabel(); + + LoadValue(val); + LoadValue(MAX_JUMPS); + Emit(OpCodes.Div); + TraceCompile(OpCodes.Switch.ToString()); + il.Emit(OpCodes.Switch, blockLabels); + Branch(endOfSwitch, false); + + Label[] innerLabels = new Label[MAX_JUMPS]; + for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) + { + il.MarkLabel(blockLabels[blockIndex]); + + int itemsThisBlock = Math.Min(MAX_JUMPS, count); + count -= itemsThisBlock; + if (innerLabels.Length != itemsThisBlock) innerLabels = new Label[itemsThisBlock]; + + int subtract = offset; + for (int j = 0; j < itemsThisBlock; j++) + { + innerLabels[j] = jumpTable[offset++].Value; + } + LoadValue(val); + if (subtract != 0) // switches are always zero-based + { + LoadValue(subtract); + Emit(OpCodes.Sub); + } + TraceCompile(OpCodes.Switch.ToString()); + il.Emit(OpCodes.Switch, innerLabels); + if (count != 0) + { // force default to the very bottom + Branch(endOfSwitch, false); + } + } + Helpers.DebugAssert(count == 0, "Should use exactly all switch items"); + MarkLabel(endOfSwitch); + } + } + } + + internal void EndFinally() + { + il.EndExceptionBlock(); + TraceCompile("EndExceptionBlock"); + } + + internal void BeginFinally() + { + il.BeginFinallyBlock(); + TraceCompile("BeginFinallyBlock"); + } + + internal void EndTry(CodeLabel label, bool @short) + { + OpCode code = @short ? OpCodes.Leave_S : OpCodes.Leave; + il.Emit(code, label.Value); + TraceCompile(code + ": " + label.Index); + } + + internal CodeLabel BeginTry() + { + CodeLabel label = new CodeLabel(il.BeginExceptionBlock(), nextLabel++); + TraceCompile("BeginExceptionBlock: " + label.Index); + return label; + } + + internal void Constrain(Type type) + { + il.Emit(OpCodes.Constrained, type); + TraceCompile(OpCodes.Constrained + ": " + type); + } + + internal void TryCast(Type type) + { + il.Emit(OpCodes.Isinst, type); + TraceCompile(OpCodes.Isinst + ": " + type); + } + + internal void Cast(Type type) + { + il.Emit(OpCodes.Castclass, type); + TraceCompile(OpCodes.Castclass + ": " + type); + } + + public IDisposable Using(Local local) + { + return new UsingBlock(this, local); + } + + private sealed class UsingBlock : IDisposable + { + private Local local; + CompilerContext ctx; + CodeLabel label; + /// + /// Creates a new "using" block (equivalent) around a variable; + /// the variable must exist, and note that (unlike in C#) it is + /// the variables *final* value that gets disposed. If you need + /// *original* disposal, copy your variable first. + /// + /// It is the callers responsibility to ensure that the variable's + /// scope fully-encapsulates the "using"; if not, the variable + /// may be re-used (and thus re-assigned) unexpectedly. + /// + public UsingBlock(CompilerContext ctx, Local local) + { + if (ctx == null) throw new ArgumentNullException("ctx"); + if (local == null) throw new ArgumentNullException("local"); + + Type type = local.Type; + // check if **never** disposable + if ((Helpers.IsValueType(type) || Helpers.IsSealed(type)) && + !ctx.MapType(typeof(IDisposable)).IsAssignableFrom(type)) + { + return; // nothing to do! easiest "using" block ever + // (note that C# wouldn't allow this as a "using" block, + // but we'll be generous and simply not do anything) + } + this.local = local; + this.ctx = ctx; + label = ctx.BeginTry(); + + } + public void Dispose() + { + if (local == null || ctx == null) return; + + ctx.EndTry(label, false); + ctx.BeginFinally(); + Type disposableType = ctx.MapType(typeof(IDisposable)); + MethodInfo dispose = disposableType.GetMethod("Dispose"); + Type type = local.Type; + // remember that we've already (in the .ctor) excluded the case + // where it *cannot* be disposable + if (Helpers.IsValueType(type)) + { + ctx.LoadAddress(local, type); + switch (ctx.MetadataVersion) + { + case ILVersion.Net1: + ctx.LoadValue(local); + ctx.CastToObject(type); + break; + default: + ctx.Constrain(type); + break; + } + ctx.EmitCall(dispose); + } + else + { + Compiler.CodeLabel @null = ctx.DefineLabel(); + if (disposableType.IsAssignableFrom(type)) + { // *known* to be IDisposable; just needs a null-check + ctx.LoadValue(local); + ctx.BranchIfFalse(@null, true); + ctx.LoadAddress(local, type); + } + else + { // *could* be IDisposable; test via "as" + using (Compiler.Local disp = new Compiler.Local(ctx, disposableType)) + { + ctx.LoadValue(local); + ctx.TryCast(disposableType); + ctx.CopyValue(); + ctx.StoreValue(disp); + ctx.BranchIfFalse(@null, true); + ctx.LoadAddress(disp, disposableType); + } + } + ctx.EmitCall(dispose); + ctx.MarkLabel(@null); + } + ctx.EndFinally(); + this.local = null; + this.ctx = null; + label = new CodeLabel(); // default + } + } + + internal void Add() + { + Emit(OpCodes.Add); + } + + internal void LoadLength(Local arr, bool zeroIfNull) + { + Helpers.DebugAssert(arr.Type.IsArray && arr.Type.GetArrayRank() == 1); + + if (zeroIfNull) + { + Compiler.CodeLabel notNull = DefineLabel(), done = DefineLabel(); + LoadValue(arr); + CopyValue(); // optimised for non-null case + BranchIfTrue(notNull, true); + DiscardValue(); + LoadValue(0); + Branch(done, true); + MarkLabel(notNull); + Emit(OpCodes.Ldlen); + Emit(OpCodes.Conv_I4); + MarkLabel(done); + } + else + { + LoadValue(arr); + Emit(OpCodes.Ldlen); + Emit(OpCodes.Conv_I4); + } + } + + internal void CreateArray(Type elementType, Local length) + { + LoadValue(length); + il.Emit(OpCodes.Newarr, elementType); + TraceCompile(OpCodes.Newarr + ": " + elementType); + } + + internal void LoadArrayValue(Local arr, Local i) + { + Type type = arr.Type; + Helpers.DebugAssert(type.IsArray && arr.Type.GetArrayRank() == 1); + type = type.GetElementType(); + Helpers.DebugAssert(type != null, "Not an array: " + arr.Type.FullName); + LoadValue(arr); + LoadValue(i); + switch (Helpers.GetTypeCode(type)) + { + case ProtoTypeCode.SByte: Emit(OpCodes.Ldelem_I1); break; + case ProtoTypeCode.Int16: Emit(OpCodes.Ldelem_I2); break; + case ProtoTypeCode.Int32: Emit(OpCodes.Ldelem_I4); break; + case ProtoTypeCode.Int64: Emit(OpCodes.Ldelem_I8); break; + + case ProtoTypeCode.Byte: Emit(OpCodes.Ldelem_U1); break; + case ProtoTypeCode.UInt16: Emit(OpCodes.Ldelem_U2); break; + case ProtoTypeCode.UInt32: Emit(OpCodes.Ldelem_U4); break; + case ProtoTypeCode.UInt64: Emit(OpCodes.Ldelem_I8); break; // odd, but this is what C# does... + + case ProtoTypeCode.Single: Emit(OpCodes.Ldelem_R4); break; + case ProtoTypeCode.Double: Emit(OpCodes.Ldelem_R8); break; + default: + if (Helpers.IsValueType(type)) + { + il.Emit(OpCodes.Ldelema, type); + il.Emit(OpCodes.Ldobj, type); + TraceCompile(OpCodes.Ldelema + ": " + type); + TraceCompile(OpCodes.Ldobj + ": " + type); + } + else + { + Emit(OpCodes.Ldelem_Ref); + } + + break; + } + } + + internal void LoadValue(Type type) + { + il.Emit(OpCodes.Ldtoken, type); + TraceCompile(OpCodes.Ldtoken + ": " + type); + EmitCall(MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle")); + } + + internal void ConvertToInt32(ProtoTypeCode typeCode, bool uint32Overflow) + { + switch (typeCode) + { + case ProtoTypeCode.Byte: + case ProtoTypeCode.SByte: + case ProtoTypeCode.Int16: + case ProtoTypeCode.UInt16: + Emit(OpCodes.Conv_I4); + break; + case ProtoTypeCode.Int32: + break; + case ProtoTypeCode.Int64: + Emit(OpCodes.Conv_Ovf_I4); + break; + case ProtoTypeCode.UInt32: + Emit(uint32Overflow ? OpCodes.Conv_Ovf_I4_Un : OpCodes.Conv_Ovf_I4); + break; + case ProtoTypeCode.UInt64: + Emit(OpCodes.Conv_Ovf_I4_Un); + break; + default: + throw new InvalidOperationException("ConvertToInt32 not implemented for: " + typeCode.ToString()); + } + } + + internal void ConvertFromInt32(ProtoTypeCode typeCode, bool uint32Overflow) + { + switch (typeCode) + { + case ProtoTypeCode.SByte: Emit(OpCodes.Conv_Ovf_I1); break; + case ProtoTypeCode.Byte: Emit(OpCodes.Conv_Ovf_U1); break; + case ProtoTypeCode.Int16: Emit(OpCodes.Conv_Ovf_I2); break; + case ProtoTypeCode.UInt16: Emit(OpCodes.Conv_Ovf_U2); break; + case ProtoTypeCode.Int32: break; + case ProtoTypeCode.UInt32: Emit(uint32Overflow ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4); break; + case ProtoTypeCode.Int64: Emit(OpCodes.Conv_I8); break; + case ProtoTypeCode.UInt64: Emit(OpCodes.Conv_U8); break; + default: throw new InvalidOperationException(); + } + } + + internal void LoadValue(decimal value) + { + if (value == 0M) + { + LoadValue(typeof(decimal).GetField("Zero")); + } + else + { + int[] bits = decimal.GetBits(value); + LoadValue(bits[0]); // lo + LoadValue(bits[1]); // mid + LoadValue(bits[2]); // hi + LoadValue((int)(((uint)bits[3]) >> 31)); // isNegative (bool, but int for CLI purposes) + LoadValue((bits[3] >> 16) & 0xFF); // scale (byte, but int for CLI purposes) + + EmitCtor(MapType(typeof(decimal)), new Type[] { MapType(typeof(int)), MapType(typeof(int)), MapType(typeof(int)), MapType(typeof(bool)), MapType(typeof(byte)) }); + } + } + + internal void LoadValue(Guid value) + { + if (value == Guid.Empty) + { + LoadValue(typeof(Guid).GetField("Empty")); + } + else + { // note we're adding lots of shorts/bytes here - but at the IL level they are I4, not I1/I2 (which barely exist) + byte[] bytes = value.ToByteArray(); + int i = (bytes[0]) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); + LoadValue(i); + short s = (short)((bytes[4]) | (bytes[5] << 8)); + LoadValue(s); + s = (short)((bytes[6]) | (bytes[7] << 8)); + LoadValue(s); + for (i = 8; i <= 15; i++) + { + LoadValue(bytes[i]); + } + EmitCtor(MapType(typeof(Guid)), new Type[] { MapType(typeof(int)), MapType(typeof(short)), MapType(typeof(short)), + MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)) }); + } + } + + //internal void LoadValue(bool value) + //{ + // Emit(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + //} + + internal void LoadSerializationContext() + { + LoadReaderWriter(); + LoadValue((isWriter ? typeof(ProtoWriter) : typeof(ProtoReader)).GetProperty("Context")); + } + + private readonly TypeModel model; + + internal Type MapType(Type type) + { + return model.MapType(type); + } + + private readonly ILVersion metadataVersion; + public ILVersion MetadataVersion { get { return metadataVersion; } } + public enum ILVersion + { + Net1, Net2 + } + + internal bool AllowInternal(PropertyInfo property) + { + return NonPublic ? true : InternalsVisible(Helpers.GetAssembly(property.DeclaringType)); + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Compiler/CompilerContext.cs.meta b/Runtime/Protobuf-net/Compiler/CompilerContext.cs.meta new file mode 100644 index 0000000..b40174b --- /dev/null +++ b/Runtime/Protobuf-net/Compiler/CompilerContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a58d20a1d8c7730499ef29a11532d07e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Compiler/CompilerDelegates.cs b/Runtime/Protobuf-net/Compiler/CompilerDelegates.cs new file mode 100644 index 0000000..e7f0508 --- /dev/null +++ b/Runtime/Protobuf-net/Compiler/CompilerDelegates.cs @@ -0,0 +1,7 @@ +#if FEAT_COMPILER +namespace ProtoBuf.Compiler +{ + internal delegate void ProtoSerializer(object value, ProtoWriter dest); + internal delegate object ProtoDeserializer(object value, ProtoReader source); +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Compiler/CompilerDelegates.cs.meta b/Runtime/Protobuf-net/Compiler/CompilerDelegates.cs.meta new file mode 100644 index 0000000..c9fedb0 --- /dev/null +++ b/Runtime/Protobuf-net/Compiler/CompilerDelegates.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b923d7ab8e95f740b059ca797596261 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Compiler/Local.cs b/Runtime/Protobuf-net/Compiler/Local.cs new file mode 100644 index 0000000..fd3dfa9 --- /dev/null +++ b/Runtime/Protobuf-net/Compiler/Local.cs @@ -0,0 +1,58 @@ +#if FEAT_COMPILER +using System; +using System.Reflection.Emit; + +namespace ProtoBuf.Compiler +{ + internal sealed class Local : IDisposable + { + // public static readonly Local InputValue = new Local(null, null); + private LocalBuilder value; + private readonly Type type; + private CompilerContext ctx; + + private Local(LocalBuilder value, Type type) + { + this.value = value; + this.type = type; + } + + internal Local(CompilerContext ctx, Type type) + { + this.ctx = ctx; + if (ctx != null) { value = ctx.GetFromPool(type); } + this.type = type; + } + + internal LocalBuilder Value => value ?? throw new ObjectDisposedException(GetType().Name); + + public Type Type => type; + + public Local AsCopy() + { + if (ctx == null) return this; // can re-use if context-free + return new Local(value, this.type); + } + + public void Dispose() + { + if (ctx != null) + { + // only *actually* dispose if this is context-bound; note that non-bound + // objects are cheekily re-used, and *must* be left intact agter a "using" etc + ctx.ReleaseToPool(value); + value = null; + ctx = null; + } + } + + internal bool IsSame(Local other) + { + if((object)this == (object)other) return true; + + object ourVal = value; // use prop to ensure obj-disposed etc + return other != null && ourVal == (object)(other.value); + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Compiler/Local.cs.meta b/Runtime/Protobuf-net/Compiler/Local.cs.meta new file mode 100644 index 0000000..2767c29 --- /dev/null +++ b/Runtime/Protobuf-net/Compiler/Local.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07d12d9a9b7d45b498e28b7c39bdca01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/DataFormat.cs b/Runtime/Protobuf-net/DataFormat.cs new file mode 100644 index 0000000..4d97b4f --- /dev/null +++ b/Runtime/Protobuf-net/DataFormat.cs @@ -0,0 +1,49 @@ + +namespace ProtoBuf +{ + /// + /// Sub-format to use when serializing/deserializing data + /// + public enum DataFormat + { + /// + /// Uses the default encoding for the data-type. + /// + Default, + + /// + /// When applied to signed integer-based data (including Decimal), this + /// indicates that zigzag variant encoding will be used. This means that values + /// with small magnitude (regardless of sign) take a small amount + /// of space to encode. + /// + ZigZag, + + /// + /// When applied to signed integer-based data (including Decimal), this + /// indicates that two's-complement variant encoding will be used. + /// This means that any -ve number will take 10 bytes (even for 32-bit), + /// so should only be used for compatibility. + /// + TwosComplement, + + /// + /// When applied to signed integer-based data (including Decimal), this + /// indicates that a fixed amount of space will be used. + /// + FixedSize, + + /// + /// When applied to a sub-message, indicates that the value should be treated + /// as group-delimited. + /// + Group, + + /// + /// When applied to members of types such as DateTime or TimeSpan, specifies + /// that the "well known" standardized representation should be use; DateTime uses Timestamp, + /// + /// + WellKnown + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/DataFormat.cs.meta b/Runtime/Protobuf-net/DataFormat.cs.meta new file mode 100644 index 0000000..644abad --- /dev/null +++ b/Runtime/Protobuf-net/DataFormat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 875f2f7de4b03ff409de70d226359e8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs b/Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs new file mode 100644 index 0000000..0fd671f --- /dev/null +++ b/Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs @@ -0,0 +1,176 @@ +#if PLAT_BINARYFORMATTER +using System; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +namespace ProtoBuf +{ + [Serializable] + public readonly partial struct DiscriminatedUnionObject : ISerializable + { + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (Discriminator != default) info.AddValue("d", Discriminator); + if (Object is object) info.AddValue("o", Object); + } + private DiscriminatedUnionObject(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": Discriminator = (int)field.Value; break; + case "o": Object = field.Value; break; + } + } + } + } + + [Serializable] + public readonly partial struct DiscriminatedUnion128Object : ISerializable + { + [FieldOffset(8)] private readonly long _lo; + [FieldOffset(16)] private readonly long _hi; + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (_discriminator != default) info.AddValue("d", _discriminator); + if (_lo != default) info.AddValue("l", _lo); + if (_hi != default) info.AddValue("h", _hi); + if (Object != null) info.AddValue("o", Object); + } + private DiscriminatedUnion128Object(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": _discriminator = (int)field.Value; break; + case "l": _lo = (long)field.Value; break; + case "h": _hi = (long)field.Value; break; + case "o": Object = field.Value; break; + } + } + } + } + + [Serializable] + public readonly partial struct DiscriminatedUnion128 : ISerializable + { + [FieldOffset(8)] private readonly long _lo; + [FieldOffset(16)] private readonly long _hi; + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (_discriminator != default) info.AddValue("d", _discriminator); + if (_lo != default) info.AddValue("l", _lo); + if (_hi != default) info.AddValue("h", _hi); + } + private DiscriminatedUnion128(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": _discriminator = (int)field.Value; break; + case "l": _lo = (long)field.Value; break; + case "h": _hi = (long)field.Value; break; + } + } + } + } + + [Serializable] + public readonly partial struct DiscriminatedUnion64 : ISerializable + { + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (_discriminator != default) info.AddValue("d", _discriminator); + if (Int64 != default) info.AddValue("i", Int64); + } + private DiscriminatedUnion64(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": _discriminator = (int)field.Value; break; + case "i": Int64 = (long)field.Value; break; + } + } + } + } + + [Serializable] + public readonly partial struct DiscriminatedUnion64Object : ISerializable + { + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (_discriminator != default) info.AddValue("d", _discriminator); + if (Int64 != default) info.AddValue("i", Int64); + if (Object is object) info.AddValue("o", Object); + } + private DiscriminatedUnion64Object(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": _discriminator = (int)field.Value; break; + case "i": Int64 = (long)field.Value; break; + case "o": Object = field.Value; break; + } + } + } + } + + [Serializable] + public readonly partial struct DiscriminatedUnion32 : ISerializable + { + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (_discriminator != default) info.AddValue("d", _discriminator); + if (Int32 != default) info.AddValue("i", Int32); + } + private DiscriminatedUnion32(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": _discriminator = (int)field.Value; break; + case "i": Int32 = (int)field.Value; break; + } + } + } + } + + [Serializable] + public readonly partial struct DiscriminatedUnion32Object : ISerializable + { + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (_discriminator != default) info.AddValue("d", _discriminator); + if (Int32 != default) info.AddValue("i", Int32); + if (Object is object) info.AddValue("o", Object); + } + private DiscriminatedUnion32Object(SerializationInfo info, StreamingContext context) + { + this = default; + foreach (var field in info) + { + switch (field.Name) + { + case "d": _discriminator = (int)field.Value; break; + case "i": Int32 = (int)field.Value; break; + case "o": Object = field.Value; break; + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta b/Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta new file mode 100644 index 0000000..f616331 --- /dev/null +++ b/Runtime/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a3aeec9c8a4c734e9ad022627502d1d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/DiscriminatedUnion.cs b/Runtime/Protobuf-net/DiscriminatedUnion.cs new file mode 100644 index 0000000..7cc8cf8 --- /dev/null +++ b/Runtime/Protobuf-net/DiscriminatedUnion.cs @@ -0,0 +1,416 @@ +using System; +using System.Runtime.InteropServices; + +namespace ProtoBuf +{ + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + public readonly partial struct DiscriminatedUnionObject + { + + /// The value typed as Object + public readonly object Object; + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => Discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnionObject(int discriminator, object value) + { + Discriminator = discriminator; + Object = value; + } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnionObject value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + + /// The discriminator value + public int Discriminator { get; } + } + + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + [StructLayout(LayoutKind.Explicit)] + public readonly partial struct DiscriminatedUnion64 + { +#if !FEAT_SAFE + unsafe static DiscriminatedUnion64() + { + if (sizeof(DateTime) > 8) throw new InvalidOperationException(nameof(DateTime) + " was unexpectedly too big for " + nameof(DiscriminatedUnion64)); + if (sizeof(TimeSpan) > 8) throw new InvalidOperationException(nameof(TimeSpan) + " was unexpectedly too big for " + nameof(DiscriminatedUnion64)); + } +#endif + [FieldOffset(0)] private readonly int _discriminator; // note that we can't pack further because Object needs x8 alignment/padding on x64 + + /// The value typed as Int64 + [FieldOffset(8)] public readonly long Int64; + /// The value typed as UInt64 + [FieldOffset(8)] public readonly ulong UInt64; + /// The value typed as Int32 + [FieldOffset(8)] public readonly int Int32; + /// The value typed as UInt32 + [FieldOffset(8)] public readonly uint UInt32; + /// The value typed as Boolean + [FieldOffset(8)] public readonly bool Boolean; + /// The value typed as Single + [FieldOffset(8)] public readonly float Single; + /// The value typed as Double + [FieldOffset(8)] public readonly double Double; + /// The value typed as DateTime + [FieldOffset(8)] public readonly DateTime DateTime; + /// The value typed as TimeSpan + [FieldOffset(8)] public readonly TimeSpan TimeSpan; + + private DiscriminatedUnion64(int discriminator) : this() + { + _discriminator = discriminator; + } + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => _discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, long value) : this(discriminator) { Int64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, int value) : this(discriminator) { Int32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, ulong value) : this(discriminator) { UInt64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, uint value) : this(discriminator) { UInt32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, float value) : this(discriminator) { Single = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, double value) : this(discriminator) { Double = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, bool value) : this(discriminator) { Boolean = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, DateTime? value) : this(value.HasValue ? discriminator: 0) { DateTime = value.GetValueOrDefault(); } + /// Create a new discriminated union value + public DiscriminatedUnion64(int discriminator, TimeSpan? value) : this(value.HasValue ? discriminator : 0) { TimeSpan = value.GetValueOrDefault(); } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnion64 value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + /// The discriminator value + public int Discriminator => _discriminator; + } + + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + [StructLayout(LayoutKind.Explicit)] + public readonly partial struct DiscriminatedUnion128Object + { +#if !FEAT_SAFE + unsafe static DiscriminatedUnion128Object() + { + if (sizeof(DateTime) > 16) throw new InvalidOperationException(nameof(DateTime) + " was unexpectedly too big for " + nameof(DiscriminatedUnion128Object)); + if (sizeof(TimeSpan) > 16) throw new InvalidOperationException(nameof(TimeSpan) + " was unexpectedly too big for " + nameof(DiscriminatedUnion128Object)); + if (sizeof(Guid) > 16) throw new InvalidOperationException(nameof(Guid) + " was unexpectedly too big for " + nameof(DiscriminatedUnion128Object)); + } +#endif + + [FieldOffset(0)] private readonly int _discriminator; // note that we can't pack further because Object needs x8 alignment/padding on x64 + + /// The value typed as Int64 + [FieldOffset(8)] public readonly long Int64; + /// The value typed as UInt64 + [FieldOffset(8)] public readonly ulong UInt64; + /// The value typed as Int32 + [FieldOffset(8)] public readonly int Int32; + /// The value typed as UInt32 + [FieldOffset(8)] public readonly uint UInt32; + /// The value typed as Boolean + [FieldOffset(8)] public readonly bool Boolean; + /// The value typed as Single + [FieldOffset(8)] public readonly float Single; + /// The value typed as Double + [FieldOffset(8)] public readonly double Double; + /// The value typed as DateTime + [FieldOffset(8)] public readonly DateTime DateTime; + /// The value typed as TimeSpan + [FieldOffset(8)] public readonly TimeSpan TimeSpan; + /// The value typed as Guid + [FieldOffset(8)] public readonly Guid Guid; + /// The value typed as Object + [FieldOffset(24)] public readonly object Object; + + private DiscriminatedUnion128Object(int discriminator) : this() + { + _discriminator = discriminator; + } + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => _discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, long value) : this(discriminator) { Int64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, int value) : this(discriminator) { Int32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, ulong value) : this(discriminator) { UInt64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, uint value) : this(discriminator) { UInt32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, float value) : this(discriminator) { Single = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, double value) : this(discriminator) { Double = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, bool value) : this(discriminator) { Boolean = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, object value) : this(value != null ? discriminator : 0) { Object = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, DateTime? value) : this(value.HasValue ? discriminator: 0) { DateTime = value.GetValueOrDefault(); } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, TimeSpan? value) : this(value.HasValue ? discriminator : 0) { TimeSpan = value.GetValueOrDefault(); } + /// Create a new discriminated union value + public DiscriminatedUnion128Object(int discriminator, Guid? value) : this(value.HasValue ? discriminator : 0) { Guid = value.GetValueOrDefault(); } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnion128Object value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + /// The discriminator value + public int Discriminator => _discriminator; + } + + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + [StructLayout(LayoutKind.Explicit)] + public readonly partial struct DiscriminatedUnion128 + { +#if !FEAT_SAFE + unsafe static DiscriminatedUnion128() + { + if (sizeof(DateTime) > 16) throw new InvalidOperationException(nameof(DateTime) + " was unexpectedly too big for " + nameof(DiscriminatedUnion128)); + if (sizeof(TimeSpan) > 16) throw new InvalidOperationException(nameof(TimeSpan) + " was unexpectedly too big for " + nameof(DiscriminatedUnion128)); + if (sizeof(Guid) > 16) throw new InvalidOperationException(nameof(Guid) + " was unexpectedly too big for " + nameof(DiscriminatedUnion128)); + } +#endif + [FieldOffset(0)] private readonly int _discriminator; // note that we can't pack further because Object needs x8 alignment/padding on x64 + + /// The value typed as Int64 + [FieldOffset(8)] public readonly long Int64; + /// The value typed as UInt64 + [FieldOffset(8)] public readonly ulong UInt64; + /// The value typed as Int32 + [FieldOffset(8)] public readonly int Int32; + /// The value typed as UInt32 + [FieldOffset(8)] public readonly uint UInt32; + /// The value typed as Boolean + [FieldOffset(8)] public readonly bool Boolean; + /// The value typed as Single + [FieldOffset(8)] public readonly float Single; + /// The value typed as Double + [FieldOffset(8)] public readonly double Double; + /// The value typed as DateTime + [FieldOffset(8)] public readonly DateTime DateTime; + /// The value typed as TimeSpan + [FieldOffset(8)] public readonly TimeSpan TimeSpan; + /// The value typed as Guid + [FieldOffset(8)] public readonly Guid Guid; + + private DiscriminatedUnion128(int discriminator) : this() + { + _discriminator = discriminator; + } + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => _discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, long value) : this(discriminator) { Int64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, int value) : this(discriminator) { Int32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, ulong value) : this(discriminator) { UInt64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, uint value) : this(discriminator) { UInt32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, float value) : this(discriminator) { Single = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, double value) : this(discriminator) { Double = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, bool value) : this(discriminator) { Boolean = value; } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, DateTime? value) : this(value.HasValue ? discriminator: 0) { DateTime = value.GetValueOrDefault(); } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, TimeSpan? value) : this(value.HasValue ? discriminator : 0) { TimeSpan = value.GetValueOrDefault(); } + /// Create a new discriminated union value + public DiscriminatedUnion128(int discriminator, Guid? value) : this(value.HasValue ? discriminator : 0) { Guid = value.GetValueOrDefault(); } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnion128 value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + /// The discriminator value + public int Discriminator => _discriminator; + } + + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + [StructLayout(LayoutKind.Explicit)] + public readonly partial struct DiscriminatedUnion64Object + { +#if !FEAT_SAFE + unsafe static DiscriminatedUnion64Object() + { + if (sizeof(DateTime) > 8) throw new InvalidOperationException(nameof(DateTime) + " was unexpectedly too big for " + nameof(DiscriminatedUnion64Object)); + if (sizeof(TimeSpan) > 8) throw new InvalidOperationException(nameof(TimeSpan) + " was unexpectedly too big for " + nameof(DiscriminatedUnion64Object)); + } +#endif + [FieldOffset(0)] private readonly int _discriminator; // note that we can't pack further because Object needs x8 alignment/padding on x64 + + /// The value typed as Int64 + [FieldOffset(8)] public readonly long Int64; + /// The value typed as UInt64 + [FieldOffset(8)] public readonly ulong UInt64; + /// The value typed as Int32 + [FieldOffset(8)] public readonly int Int32; + /// The value typed as UInt32 + [FieldOffset(8)] public readonly uint UInt32; + /// The value typed as Boolean + [FieldOffset(8)] public readonly bool Boolean; + /// The value typed as Single + [FieldOffset(8)] public readonly float Single; + /// The value typed as Double + [FieldOffset(8)] public readonly double Double; + /// The value typed as DateTime + [FieldOffset(8)] public readonly DateTime DateTime; + /// The value typed as TimeSpan + [FieldOffset(8)] public readonly TimeSpan TimeSpan; + /// The value typed as Object + [FieldOffset(16)] public readonly object Object; + + private DiscriminatedUnion64Object(int discriminator) : this() + { + _discriminator = discriminator; + } + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => _discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, long value) : this(discriminator) { Int64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, int value) : this(discriminator) { Int32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, ulong value) : this(discriminator) { UInt64 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, uint value) : this(discriminator) { UInt32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, float value) : this(discriminator) { Single = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, double value) : this(discriminator) { Double = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, bool value) : this(discriminator) { Boolean = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, object value) : this(value != null ? discriminator : 0) { Object = value; } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, DateTime? value) : this(value.HasValue ? discriminator: 0) { DateTime = value.GetValueOrDefault(); } + /// Create a new discriminated union value + public DiscriminatedUnion64Object(int discriminator, TimeSpan? value) : this(value.HasValue ? discriminator : 0) { TimeSpan = value.GetValueOrDefault(); } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnion64Object value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + /// The discriminator value + public int Discriminator => _discriminator; + } + + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + [StructLayout(LayoutKind.Explicit)] + public readonly partial struct DiscriminatedUnion32 + { + [FieldOffset(0)] private readonly int _discriminator; + + /// The value typed as Int32 + [FieldOffset(4)] public readonly int Int32; + /// The value typed as UInt32 + [FieldOffset(4)] public readonly uint UInt32; + /// The value typed as Boolean + [FieldOffset(4)] public readonly bool Boolean; + /// The value typed as Single + [FieldOffset(4)] public readonly float Single; + + private DiscriminatedUnion32(int discriminator) : this() + { + _discriminator = discriminator; + } + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => _discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnion32(int discriminator, int value) : this(discriminator) { Int32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32(int discriminator, uint value) : this(discriminator) { UInt32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32(int discriminator, float value) : this(discriminator) { Single = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32(int discriminator, bool value) : this(discriminator) { Boolean = value; } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnion32 value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + /// The discriminator value + public int Discriminator => _discriminator; + } + + /// Represent multiple types as a union; this is used as part of OneOf - + /// note that it is the caller's responsbility to only read/write the value as the same type + [StructLayout(LayoutKind.Explicit)] + public readonly partial struct DiscriminatedUnion32Object + { + [FieldOffset(0)] private readonly int _discriminator; + + /// The value typed as Int32 + [FieldOffset(4)] public readonly int Int32; + /// The value typed as UInt32 + [FieldOffset(4)] public readonly uint UInt32; + /// The value typed as Boolean + [FieldOffset(4)] public readonly bool Boolean; + /// The value typed as Single + [FieldOffset(4)] public readonly float Single; + /// The value typed as Object + [FieldOffset(8)] public readonly object Object; + + private DiscriminatedUnion32Object(int discriminator) : this() + { + _discriminator = discriminator; + } + + /// Indicates whether the specified discriminator is assigned + public bool Is(int discriminator) => _discriminator == discriminator; + + /// Create a new discriminated union value + public DiscriminatedUnion32Object(int discriminator, int value) : this(discriminator) { Int32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32Object(int discriminator, uint value) : this(discriminator) { UInt32 = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32Object(int discriminator, float value) : this(discriminator) { Single = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32Object(int discriminator, bool value) : this(discriminator) { Boolean = value; } + /// Create a new discriminated union value + public DiscriminatedUnion32Object(int discriminator, object value) : this(value != null ? discriminator : 0) { Object = value; } + + /// Reset a value if the specified discriminator is assigned + public static void Reset(ref DiscriminatedUnion32Object value, int discriminator) + { + if (value.Discriminator == discriminator) value = default; + } + /// The discriminator value + public int Discriminator => _discriminator; + } +} diff --git a/Runtime/Protobuf-net/DiscriminatedUnion.cs.meta b/Runtime/Protobuf-net/DiscriminatedUnion.cs.meta new file mode 100644 index 0000000..3268148 --- /dev/null +++ b/Runtime/Protobuf-net/DiscriminatedUnion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab51817e163a1144bb8518368ba0a465 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Extensible.cs b/Runtime/Protobuf-net/Extensible.cs new file mode 100644 index 0000000..6bd528b --- /dev/null +++ b/Runtime/Protobuf-net/Extensible.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using ProtoBuf.Meta; +using System.Collections; + +namespace ProtoBuf +{ + /// + /// Simple base class for supporting unexpected fields allowing + /// for loss-less round-tips/merge, even if the data is not understod. + /// The additional fields are (by default) stored in-memory in a buffer. + /// + /// As an example of an alternative implementation, you might + /// choose to use the file system (temporary files) as the back-end, tracking + /// only the paths [such an object would ideally be IDisposable and use + /// a finalizer to ensure that the files are removed]. + /// + public abstract class Extensible : IExtensible + { + // note: not marked ProtoContract - no local state, and can't + // predict sub-classes + + private IExtension extensionObject; + + IExtension IExtensible.GetExtensionObject(bool createIfMissing) + { + return GetExtensionObject(createIfMissing); + } + + /// + /// Retrieves the extension object for the current + /// instance, optionally creating it if it does not already exist. + /// + /// Should a new extension object be + /// created if it does not already exist? + /// The extension object if it exists (or was created), or null + /// if the extension object does not exist or is not available. + /// The createIfMissing argument is false during serialization, + /// and true during deserialization upon encountering unexpected fields. + protected virtual IExtension GetExtensionObject(bool createIfMissing) + { + return GetExtensionObject(ref extensionObject, createIfMissing); + } + + /// + /// Provides a simple, default implementation for extension support, + /// optionally creating it if it does not already exist. Designed to be called by + /// classes implementing . + /// + /// Should a new extension object be + /// created if it does not already exist? + /// The extension field to check (and possibly update). + /// The extension object if it exists (or was created), or null + /// if the extension object does not exist or is not available. + /// The createIfMissing argument is false during serialization, + /// and true during deserialization upon encountering unexpected fields. + public static IExtension GetExtensionObject(ref IExtension extensionObject, bool createIfMissing) + { + if (createIfMissing && extensionObject == null) + { + extensionObject = new BufferExtension(); + } + return extensionObject; + } + +#if !NO_RUNTIME + /// + /// Appends the value as an additional (unexpected) data-field for the instance. + /// Note that for non-repeated sub-objects, this equates to a merge operation; + /// for repeated sub-objects this adds a new instance to the set; for simple + /// values the new value supercedes the old value. + /// + /// Note that appending a value does not remove the old value from + /// the stream; avoid repeatedly appending values for the same field. + /// The type of the value to append. + /// The extensible object to append the value to. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The value to append. + public static void AppendValue(IExtensible instance, int tag, TValue value) + { + AppendValue(instance, tag, DataFormat.Default, value); + } + + /// + /// Appends the value as an additional (unexpected) data-field for the instance. + /// Note that for non-repeated sub-objects, this equates to a merge operation; + /// for repeated sub-objects this adds a new instance to the set; for simple + /// values the new value supercedes the old value. + /// + /// Note that appending a value does not remove the old value from + /// the stream; avoid repeatedly appending values for the same field. + /// The data-type of the field. + /// The data-format to use when encoding the value. + /// The extensible object to append the value to. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The value to append. + public static void AppendValue(IExtensible instance, int tag, DataFormat format, TValue value) + { + ExtensibleUtil.AppendExtendValue(RuntimeTypeModel.Default, instance, tag, format, value); + } + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// The value returned is the composed value after merging any duplicated content; if the + /// value is "repeated" (a list), then use GetValues instead. + /// + /// The data-type of the field. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The effective value of the field, or the default value if not found. + public static TValue GetValue(IExtensible instance, int tag) + { + return GetValue(instance, tag, DataFormat.Default); + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// The value returned is the composed value after merging any duplicated content; if the + /// value is "repeated" (a list), then use GetValues instead. + /// + /// The data-type of the field. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The data-format to use when decoding the value. + /// The effective value of the field, or the default value if not found. + public static TValue GetValue(IExtensible instance, int tag, DataFormat format) + { + TryGetValue(instance, tag, format, out TValue value); + return value; + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// The value returned (in "value") is the composed value after merging any duplicated content; + /// if the value is "repeated" (a list), then use GetValues instead. + /// + /// The data-type of the field. + /// The effective value of the field, or the default value if not found. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// True if data for the field was present, false otherwise. + public static bool TryGetValue(IExtensible instance, int tag, out TValue value) + { + return TryGetValue(instance, tag, DataFormat.Default, out value); + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// The value returned (in "value") is the composed value after merging any duplicated content; + /// if the value is "repeated" (a list), then use GetValues instead. + /// + /// The data-type of the field. + /// The effective value of the field, or the default value if not found. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The data-format to use when decoding the value. + /// True if data for the field was present, false otherwise. + public static bool TryGetValue(IExtensible instance, int tag, DataFormat format, out TValue value) + { + return TryGetValue(instance, tag, format, false, out value); + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// The value returned (in "value") is the composed value after merging any duplicated content; + /// if the value is "repeated" (a list), then use GetValues instead. + /// + /// The data-type of the field. + /// The effective value of the field, or the default value if not found. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The data-format to use when decoding the value. + /// Allow tags that are present as part of the definition; for example, to query unknown enum values. + /// True if data for the field was present, false otherwise. + public static bool TryGetValue(IExtensible instance, int tag, DataFormat format, bool allowDefinedTag, out TValue value) + { + value = default; + bool set = false; + foreach (TValue val in ExtensibleUtil.GetExtendedValues(instance, tag, format, true, allowDefinedTag)) + { + // expecting at most one yield... + // but don't break; need to read entire stream + value = val; + set = true; + } + + return set; + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated" + /// (list) fields. + /// + /// The extended data is processed lazily as the enumerator is iterated. + /// The data-type of the field. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// An enumerator that yields each occurrence of the field. + public static IEnumerable GetValues(IExtensible instance, int tag) + { + return ExtensibleUtil.GetExtendedValues(instance, tag, DataFormat.Default, false, false); + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated" + /// (list) fields. + /// + /// The extended data is processed lazily as the enumerator is iterated. + /// The data-type of the field. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The data-format to use when decoding the value. + /// An enumerator that yields each occurrence of the field. + public static IEnumerable GetValues(IExtensible instance, int tag, DataFormat format) + { + return ExtensibleUtil.GetExtendedValues(instance, tag, format, false, false); + } +#endif + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// The value returned (in "value") is the composed value after merging any duplicated content; + /// if the value is "repeated" (a list), then use GetValues instead. + /// + /// The data-type of the field. + /// The model to use for configuration. + /// The effective value of the field, or the default value if not found. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The data-format to use when decoding the value. + /// Allow tags that are present as part of the definition; for example, to query unknown enum values. + /// True if data for the field was present, false otherwise. + public static bool TryGetValue(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format, bool allowDefinedTag, out object value) + { + value = null; + bool set = false; + foreach (object val in ExtensibleUtil.GetExtendedValues(model, type, instance, tag, format, true, allowDefinedTag)) + { + // expecting at most one yield... + // but don't break; need to read entire stream + value = val; + set = true; + } + + return set; + } + + /// + /// Queries an extensible object for an additional (unexpected) data-field for the instance. + /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated" + /// (list) fields. + /// + /// The extended data is processed lazily as the enumerator is iterated. + /// The model to use for configuration. + /// The data-type of the field. + /// The extensible object to obtain the value from. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The data-format to use when decoding the value. + /// An enumerator that yields each occurrence of the field. + public static IEnumerable GetValues(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format) + { + return ExtensibleUtil.GetExtendedValues(model, type, instance, tag, format, false, false); + } + + /// + /// Appends the value as an additional (unexpected) data-field for the instance. + /// Note that for non-repeated sub-objects, this equates to a merge operation; + /// for repeated sub-objects this adds a new instance to the set; for simple + /// values the new value supercedes the old value. + /// + /// Note that appending a value does not remove the old value from + /// the stream; avoid repeatedly appending values for the same field. + /// The model to use for configuration. + /// The data-format to use when encoding the value. + /// The extensible object to append the value to. + /// The field identifier; the tag should not be defined as a known data-field for the instance. + /// The value to append. + public static void AppendValue(TypeModel model, IExtensible instance, int tag, DataFormat format, object value) + { + ExtensibleUtil.AppendExtendValue(model, instance, tag, format, value); + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/Extensible.cs.meta b/Runtime/Protobuf-net/Extensible.cs.meta new file mode 100644 index 0000000..ac4ec36 --- /dev/null +++ b/Runtime/Protobuf-net/Extensible.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc24b62dbd0b19642bce397e2b061aa0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ExtensibleUtil.cs b/Runtime/Protobuf-net/ExtensibleUtil.cs new file mode 100644 index 0000000..9cc1613 --- /dev/null +++ b/Runtime/Protobuf-net/ExtensibleUtil.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using ProtoBuf.Meta; + +namespace ProtoBuf +{ + /// + /// This class acts as an internal wrapper allowing us to do a dynamic + /// methodinfo invoke; an't put into Serializer as don't want on public + /// API; can't put into Serializer<T> since we need to invoke + /// across classes + /// + internal static class ExtensibleUtil + { + +#if !NO_RUNTIME + /// + /// All this does is call GetExtendedValuesTyped with the correct type for "instance"; + /// this ensures that we don't get issues with subclasses declaring conflicting types - + /// the caller must respect the fields defined for the type they pass in. + /// + internal static IEnumerable GetExtendedValues(IExtensible instance, int tag, DataFormat format, bool singleton, bool allowDefinedTag) + { + foreach (TValue value in GetExtendedValues(RuntimeTypeModel.Default, typeof(TValue), instance, tag, format, singleton, allowDefinedTag)) + { + yield return value; + } + } +#endif + /// + /// All this does is call GetExtendedValuesTyped with the correct type for "instance"; + /// this ensures that we don't get issues with subclasses declaring conflicting types - + /// the caller must respect the fields defined for the type they pass in. + /// + internal static IEnumerable GetExtendedValues(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format, bool singleton, bool allowDefinedTag) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + if (tag <= 0) throw new ArgumentOutOfRangeException(nameof(tag)); + IExtension extn = instance.GetExtensionObject(false); + + if (extn == null) + { + yield break; + } + + Stream stream = extn.BeginQuery(); + object value = null; + ProtoReader reader = null; + try + { + SerializationContext ctx = new SerializationContext(); + reader = ProtoReader.Create(stream, model, ctx, ProtoReader.TO_EOF); + while (model.TryDeserializeAuxiliaryType(reader, format, tag, type, ref value, true, true, false, false, null) && value != null) + { + if (!singleton) + { + yield return value; + + value = null; // fresh item each time + } + } + if (singleton && value != null) + { + yield return value; + } + } + finally + { + ProtoReader.Recycle(reader); + extn.EndQuery(stream); + } + } + + internal static void AppendExtendValue(TypeModel model, IExtensible instance, int tag, DataFormat format, object value) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + if (value == null) throw new ArgumentNullException(nameof(value)); + + // TODO + //model.CheckTagNotInUse(tag); + + // obtain the extension object and prepare to write + IExtension extn = instance.GetExtensionObject(true); + if (extn == null) throw new InvalidOperationException("No extension object available; appended data would be lost."); + bool commit = false; + Stream stream = extn.BeginAppend(); + try + { + using (ProtoWriter writer = ProtoWriter.Create(stream, model, null)) + { + model.TrySerializeAuxiliaryType(writer, null, format, tag, value, false, null); + writer.Close(); + } + commit = true; + } + finally + { + extn.EndAppend(stream, commit); + } + } + + // /// + // /// Stores the given value into the instance's stream; the serializer + // /// is inferred from TValue and format. + // /// + // /// Needs to be public to be callable thru reflection in Silverlight + // public static void AppendExtendValueTyped( + // TypeModel model, TSource instance, int tag, DataFormat format, TValue value) + // where TSource : class, IExtensible + // { + // AppendExtendValue(model, instance, tag, format, value); + // } + + } + +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/ExtensibleUtil.cs.meta b/Runtime/Protobuf-net/ExtensibleUtil.cs.meta new file mode 100644 index 0000000..ea420c6 --- /dev/null +++ b/Runtime/Protobuf-net/ExtensibleUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc71d3f5e8f25ad41bb04ea933cee56e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/GlobalSuppressions.cs b/Runtime/Protobuf-net/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..48b91900b51b472ffc30340fc102117033e243b7 GIT binary patch literal 2750 zcmeH|UuzRl5XI+N@H;H~q|l}cf>`jOR)juOgycoUhc(&!(PUGyyM=st^>=1^+06z; z1RpFVB)j*{+%sp+%-sC=er9Ku*~FH%vYNL!&$X4j#kys;v>EF!w&(ZKwyb2ou*wR2 z_jrVFgDtZSyDMzX-7~YXwRXd2$GMu%_1&|ug(E$-N3al&f>RBCE26c$$v@+{bc^i5 z&{KG8{DNCVmR~SYtgU^;I_31px(FW*ET^99Eq-fI>jBRd7?m?9!4-PR>CD;aOomk% zE7P6l(y-dPPhz^@qoyJayqv}lU8~dSL z@KOC!-PI+XwPW+U9*Z7oL2p1()El@*za@70QO9M2p3D7YQn=5xW0BjH^BZ<=QsNy^ zH7R*d(ab)=riIna6;*$zCo258F9Y#>`PRLn{iFFr}o*W{h=Z;>-~zv-7Fn- w$IrPdG#$- + /// Not all frameworks are created equal (fx1.1 vs fx2.0, + /// micro-framework, compact-framework, + /// silverlight, etc). This class simply wraps up a few things that would + /// otherwise make the real code unnecessarily messy, providing fallback + /// implementations if necessary. + /// + internal sealed class Helpers + { + private Helpers() { } + + public static StringBuilder AppendLine(StringBuilder builder) + { + return builder.AppendLine(); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void DebugWriteLine(string message, object obj) + { +#if DEBUG + string suffix; + try + { + suffix = obj == null ? "(null)" : obj.ToString(); + } + catch + { + suffix = "(exception)"; + } + DebugWriteLine(message + ": " + suffix); +#endif + } + [System.Diagnostics.Conditional("DEBUG")] + public static void DebugWriteLine(string message) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine(message); +#endif + } + [System.Diagnostics.Conditional("TRACE")] + public static void TraceWriteLine(string message) + { +#if TRACE +#if CF2 || PORTABLE || COREFX || PROFILE259 + System.Diagnostics.Debug.WriteLine(message); +#else + System.Diagnostics.Trace.WriteLine(message); +#endif +#endif + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void DebugAssert(bool condition, string message) + { +#if DEBUG + if (!condition) + { + System.Diagnostics.Debug.Assert(false, message); + } +#endif + } + [System.Diagnostics.Conditional("DEBUG")] + public static void DebugAssert(bool condition, string message, params object[] args) + { +#if DEBUG + if (!condition) DebugAssert(false, string.Format(message, args)); +#endif + } + [System.Diagnostics.Conditional("DEBUG")] + public static void DebugAssert(bool condition) + { +#if DEBUG + if (!condition && System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Break(); + System.Diagnostics.Debug.Assert(condition); +#endif + } +#if !NO_RUNTIME + public static void Sort(int[] keys, object[] values) + { + // bubble-sort; it'll work on MF, has small code, + // and works well-enough for our sizes. This approach + // also allows us to do `int` compares without having + // to go via IComparable etc, so win:win + bool swapped; + do + { + swapped = false; + for (int i = 1; i < keys.Length; i++) + { + if (keys[i - 1] > keys[i]) + { + int tmpKey = keys[i]; + keys[i] = keys[i - 1]; + keys[i - 1] = tmpKey; + object tmpValue = values[i]; + values[i] = values[i - 1]; + values[i - 1] = tmpValue; + swapped = true; + } + } + } while (swapped); + } +#endif + +#if COREFX + internal static MemberInfo GetInstanceMember(TypeInfo declaringType, string name) + { + var members = declaringType.AsType().GetMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + switch(members.Length) + { + case 0: return null; + case 1: return members[0]; + default: throw new AmbiguousMatchException(name); + } + } + internal static MethodInfo GetInstanceMethod(Type declaringType, string name) + { + foreach (MethodInfo method in declaringType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.Name == name) return method; + } + return null; + } + internal static MethodInfo GetInstanceMethod(TypeInfo declaringType, string name) + { + return GetInstanceMethod(declaringType.AsType(), name); ; + } + internal static MethodInfo GetStaticMethod(Type declaringType, string name) + { + foreach (MethodInfo method in declaringType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.Name == name) return method; + } + return null; + } + + internal static MethodInfo GetStaticMethod(TypeInfo declaringType, string name) + { + return GetStaticMethod(declaringType.AsType(), name); + } + internal static MethodInfo GetStaticMethod(Type declaringType, string name, Type[] parameterTypes) + { + foreach(MethodInfo method in declaringType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.Name == name && IsMatch(method.GetParameters(), parameterTypes)) return method; + } + return null; + } + internal static MethodInfo GetInstanceMethod(Type declaringType, string name, Type[] parameterTypes) + { + foreach (MethodInfo method in declaringType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.Name == name && IsMatch(method.GetParameters(), parameterTypes)) return method; + } + return null; + } + internal static MethodInfo GetInstanceMethod(TypeInfo declaringType, string name, Type[] types) + { + return GetInstanceMethod(declaringType.AsType(), name, types); + } +#elif PROFILE259 + internal static MemberInfo GetInstanceMember(TypeInfo declaringType, string name) + { + IEnumerable members = declaringType.DeclaredMembers; + IList found = new List(); + foreach (MemberInfo member in members) + { + if (member.Name.Equals(name)) + { + found.Add(member); + } + } + switch (found.Count) + { + case 0: return null; + case 1: return found.First(); + default: throw new AmbiguousMatchException(name); + } + } + internal static MethodInfo GetInstanceMethod(Type declaringType, string name) + { + var methods = declaringType.GetRuntimeMethods(); + foreach (MethodInfo method in methods) + { + if (method.Name == name) + { + return method; + } + } + return null; + } + internal static MethodInfo GetInstanceMethod(TypeInfo declaringType, string name) + { + return GetInstanceMethod(declaringType.AsType(), name); ; + } + internal static MethodInfo GetStaticMethod(Type declaringType, string name) + { + var methods = declaringType.GetRuntimeMethods(); + foreach (MethodInfo method in methods) + { + if (method.Name == name) + { + return method; + } + } + return null; + } + + internal static MethodInfo GetStaticMethod(TypeInfo declaringType, string name) + { + return GetStaticMethod(declaringType.AsType(), name); + } + internal static MethodInfo GetStaticMethod(Type declaringType, string name, Type[] parameterTypes) + { + var methods = declaringType.GetRuntimeMethods(); + foreach (MethodInfo method in methods) + { + if (method.Name == name && + IsMatch(method.GetParameters(), parameterTypes)) + { + return method; + } + } + return null; + } + internal static MethodInfo GetInstanceMethod(Type declaringType, string name, Type[] parameterTypes) + { + var methods = declaringType.GetRuntimeMethods(); + foreach (MethodInfo method in methods) + { + if (method.Name == name && + IsMatch(method.GetParameters(), parameterTypes)) + { + return method; + } + } + return null; + } + internal static MethodInfo GetInstanceMethod(TypeInfo declaringType, string name, Type[] types) + { + return GetInstanceMethod(declaringType.AsType(), name, types); + } +#else + internal static MethodInfo GetInstanceMethod(Type declaringType, string name) + { + return declaringType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + } + internal static MethodInfo GetStaticMethod(Type declaringType, string name) + { + return declaringType.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + internal static MethodInfo GetStaticMethod(Type declaringType, string name, Type[] parameterTypes) + { +#if PORTABLE + foreach (MethodInfo method in declaringType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.Name == name && IsMatch(method.GetParameters(), parameterTypes)) return method; + } + return null; +#else + return declaringType.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null); +#endif + } + internal static MethodInfo GetInstanceMethod(Type declaringType, string name, Type[] types) + { + if (types == null) types = EmptyTypes; +#if PORTABLE || COREFX + MethodInfo method = declaringType.GetMethod(name, types); + if (method != null && method.IsStatic) method = null; + return method; +#else + return declaringType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, types, null); +#endif + } +#endif + + internal static bool IsSubclassOf(Type type, Type baseClass) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().IsSubclassOf(baseClass); +#else + return type.IsSubclassOf(baseClass); +#endif + } + + public readonly static Type[] EmptyTypes = +#if PORTABLE || CF2 || CF35 || PROFILE259 + new Type[0]; +#else + Type.EmptyTypes; +#endif + +#if COREFX || PROFILE259 + private static readonly Type[] knownTypes = new Type[] { + typeof(bool), typeof(char), typeof(sbyte), typeof(byte), + typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(float), typeof(double), + typeof(decimal), typeof(string), + typeof(DateTime), typeof(TimeSpan), typeof(Guid), typeof(Uri), + typeof(byte[]), typeof(Type)}; + private static readonly ProtoTypeCode[] knownCodes = new ProtoTypeCode[] { + ProtoTypeCode.Boolean, ProtoTypeCode.Char, ProtoTypeCode.SByte, ProtoTypeCode.Byte, + ProtoTypeCode.Int16, ProtoTypeCode.UInt16, ProtoTypeCode.Int32, ProtoTypeCode.UInt32, + ProtoTypeCode.Int64, ProtoTypeCode.UInt64, ProtoTypeCode.Single, ProtoTypeCode.Double, + ProtoTypeCode.Decimal, ProtoTypeCode.String, + ProtoTypeCode.DateTime, ProtoTypeCode.TimeSpan, ProtoTypeCode.Guid, ProtoTypeCode.Uri, + ProtoTypeCode.ByteArray, ProtoTypeCode.Type + }; + +#endif + + public static ProtoTypeCode GetTypeCode(Type type) + { +#if COREFX || PROFILE259 + if (IsEnum(type)) + { + type = Enum.GetUnderlyingType(type); + } + int idx = Array.IndexOf(knownTypes, type); + if (idx >= 0) return knownCodes[idx]; + return type == null ? ProtoTypeCode.Empty : ProtoTypeCode.Unknown; +#else + TypeCode code = Type.GetTypeCode(type); + switch (code) + { + case TypeCode.Empty: + case TypeCode.Boolean: + case TypeCode.Char: + case TypeCode.SByte: + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.DateTime: + case TypeCode.String: + return (ProtoTypeCode)code; + } + if (type == typeof(TimeSpan)) return ProtoTypeCode.TimeSpan; + if (type == typeof(Guid)) return ProtoTypeCode.Guid; + if (type == typeof(Uri)) return ProtoTypeCode.Uri; +#if PORTABLE + // In PCLs, the Uri type may not match (WinRT uses Internal/Uri, .Net uses System/Uri), so match on the full name instead + if (type.FullName == typeof(Uri).FullName) return ProtoTypeCode.Uri; +#endif + if (type == typeof(byte[])) return ProtoTypeCode.ByteArray; + if (type == typeof(Type)) return ProtoTypeCode.Type; + + return ProtoTypeCode.Unknown; +#endif + } + + internal static Type GetUnderlyingType(Type type) + { + return Nullable.GetUnderlyingType(type); + } + + internal static bool IsValueType(Type type) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().IsValueType; +#else + return type.IsValueType; +#endif + } + internal static bool IsSealed(Type type) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().IsSealed; +#else + return type.IsSealed; +#endif + } + internal static bool IsClass(Type type) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().IsClass; +#else + return type.IsClass; +#endif + } + + internal static bool IsEnum(Type type) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().IsEnum; +#else + return type.IsEnum; +#endif + } + + internal static MethodInfo GetGetMethod(PropertyInfo property, bool nonPublic, bool allowInternal) + { + if (property == null) return null; +#if COREFX || PROFILE259 + MethodInfo method = property.GetMethod; + if (!nonPublic && method != null && !method.IsPublic) method = null; + return method; +#else + MethodInfo method = property.GetGetMethod(nonPublic); + if (method == null && !nonPublic && allowInternal) + { // could be "internal" or "protected internal"; look for a non-public, then back-check + method = property.GetGetMethod(true); + if (method == null && !(method.IsAssembly || method.IsFamilyOrAssembly)) + { + method = null; + } + } + return method; +#endif + } + internal static MethodInfo GetSetMethod(PropertyInfo property, bool nonPublic, bool allowInternal) + { + if (property == null) return null; +#if COREFX || PROFILE259 + MethodInfo method = property.SetMethod; + if (!nonPublic && method != null && !method.IsPublic) method = null; + return method; +#else + MethodInfo method = property.GetSetMethod(nonPublic); + if (method == null && !nonPublic && allowInternal) + { // could be "internal" or "protected internal"; look for a non-public, then back-check + method = property.GetGetMethod(true); + if (method == null && !(method.IsAssembly || method.IsFamilyOrAssembly)) + { + method = null; + } + } + return method; +#endif + } + +#if COREFX || PORTABLE || PROFILE259 + private static bool IsMatch(ParameterInfo[] parameters, Type[] parameterTypes) + { + if (parameterTypes == null) parameterTypes = EmptyTypes; + if (parameters.Length != parameterTypes.Length) return false; + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType != parameterTypes[i]) return false; + } + return true; + } +#endif +#if COREFX || PROFILE259 + internal static ConstructorInfo GetConstructor(Type type, Type[] parameterTypes, bool nonPublic) + { + return GetConstructor(type.GetTypeInfo(), parameterTypes, nonPublic); + } + internal static ConstructorInfo GetConstructor(TypeInfo type, Type[] parameterTypes, bool nonPublic) + { + return GetConstructors(type, nonPublic).SingleOrDefault(ctor => IsMatch(ctor.GetParameters(), parameterTypes)); + } + internal static ConstructorInfo[] GetConstructors(TypeInfo typeInfo, bool nonPublic) + { + return typeInfo.DeclaredConstructors.Where(c => !c.IsStatic && ((!nonPublic && c.IsPublic) || nonPublic)).ToArray(); + } + internal static PropertyInfo GetProperty(Type type, string name, bool nonPublic) + { + return GetProperty(type.GetTypeInfo(), name, nonPublic); + } + internal static PropertyInfo GetProperty(TypeInfo type, string name, bool nonPublic) + { + return type.GetDeclaredProperty(name); + } +#else + + internal static ConstructorInfo GetConstructor(Type type, Type[] parameterTypes, bool nonPublic) + { +#if PORTABLE || COREFX + // pretty sure this will only ever return public, but... + ConstructorInfo ctor = type.GetConstructor(parameterTypes); + return (ctor != null && (nonPublic || ctor.IsPublic)) ? ctor : null; +#else + return type.GetConstructor( + nonPublic ? BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + : BindingFlags.Instance | BindingFlags.Public, + null, parameterTypes, null); +#endif + + } + internal static ConstructorInfo[] GetConstructors(Type type, bool nonPublic) + { + return type.GetConstructors( + nonPublic ? BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + : BindingFlags.Instance | BindingFlags.Public); + } + internal static PropertyInfo GetProperty(Type type, string name, bool nonPublic) + { + return type.GetProperty(name, + nonPublic ? BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + : BindingFlags.Instance | BindingFlags.Public); + } +#endif + + + internal static object ParseEnum(Type type, string value) + { + return Enum.Parse(type, value, true); + } + + + internal static MemberInfo[] GetInstanceFieldsAndProperties(Type type, bool publicOnly) + { +#if PROFILE259 + var members = new List(); + foreach (FieldInfo field in type.GetRuntimeFields()) + { + if (field.IsStatic) continue; + if (field.IsPublic || !publicOnly) members.Add(field); + } + foreach (PropertyInfo prop in type.GetRuntimeProperties()) + { + MethodInfo getter = Helpers.GetGetMethod(prop, true, true); + if (getter == null || getter.IsStatic) continue; + if (getter.IsPublic || !publicOnly) members.Add(prop); + } + return members.ToArray(); +#else + BindingFlags flags = publicOnly ? BindingFlags.Public | BindingFlags.Instance : BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic; + PropertyInfo[] props = type.GetProperties(flags); + FieldInfo[] fields = type.GetFields(flags); + MemberInfo[] members = new MemberInfo[fields.Length + props.Length]; + props.CopyTo(members, 0); + fields.CopyTo(members, props.Length); + return members; +#endif + } + + internal static Type GetMemberType(MemberInfo member) + { +#if PORTABLE || COREFX || PROFILE259 + if (member is PropertyInfo prop) return prop.PropertyType; + FieldInfo fld = member as FieldInfo; + return fld?.FieldType; +#else + switch (member.MemberType) + { + case MemberTypes.Field: return ((FieldInfo)member).FieldType; + case MemberTypes.Property: return ((PropertyInfo)member).PropertyType; + default: return null; + } +#endif + } + + internal static bool IsAssignableFrom(Type target, Type type) + { +#if PROFILE259 + return target.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); +#else + return target.IsAssignableFrom(type); +#endif + } + internal static Assembly GetAssembly(Type type) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().Assembly; +#else + return type.Assembly; +#endif + } + internal static byte[] GetBuffer(MemoryStream ms) + { +#if COREFX + if(!ms.TryGetBuffer(out var segment)) + { + throw new InvalidOperationException("Unable to obtain underlying MemoryStream buffer"); + } else if(segment.Offset != 0) + { + throw new InvalidOperationException("Underlying MemoryStream buffer was not zero-offset"); + } else + { + return segment.Array; + } +#elif PORTABLE || PROFILE259 + return ms.ToArray(); +#else + return ms.GetBuffer(); +#endif + } + } + /// + /// Intended to be a direct map to regular TypeCode, but: + /// - with missing types + /// - existing on WinRT + /// + internal enum ProtoTypeCode + { + Empty = 0, + Unknown = 1, // maps to TypeCode.Object + Boolean = 3, + Char = 4, + SByte = 5, + Byte = 6, + Int16 = 7, + UInt16 = 8, + Int32 = 9, + UInt32 = 10, + Int64 = 11, + UInt64 = 12, + Single = 13, + Double = 14, + Decimal = 15, + DateTime = 16, + String = 18, + + // additions + TimeSpan = 100, + ByteArray = 101, + Guid = 102, + Uri = 103, + Type = 104 + } +} diff --git a/Runtime/Protobuf-net/Helpers.cs.meta b/Runtime/Protobuf-net/Helpers.cs.meta new file mode 100644 index 0000000..d67edef --- /dev/null +++ b/Runtime/Protobuf-net/Helpers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 227f762ea287cdf42a9293ea6c481ff8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/IExtensible.cs b/Runtime/Protobuf-net/IExtensible.cs new file mode 100644 index 0000000..b7c0b57 --- /dev/null +++ b/Runtime/Protobuf-net/IExtensible.cs @@ -0,0 +1,23 @@ + +namespace ProtoBuf +{ + /// + /// Indicates that the implementing type has support for protocol-buffer + /// extensions. + /// + /// Can be implemented by deriving from Extensible. + public interface IExtensible + { + /// + /// Retrieves the extension object for the current + /// instance, optionally creating it if it does not already exist. + /// + /// Should a new extension object be + /// created if it does not already exist? + /// The extension object if it exists (or was created), or null + /// if the extension object does not exist or is not available. + /// The createIfMissing argument is false during serialization, + /// and true during deserialization upon encountering unexpected fields. + IExtension GetExtensionObject(bool createIfMissing); + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/IExtensible.cs.meta b/Runtime/Protobuf-net/IExtensible.cs.meta new file mode 100644 index 0000000..3c8f29a --- /dev/null +++ b/Runtime/Protobuf-net/IExtensible.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9cd5092c5d6d9d4299fc0c88ebb9390 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/IExtension.cs b/Runtime/Protobuf-net/IExtension.cs new file mode 100644 index 0000000..0a137ac --- /dev/null +++ b/Runtime/Protobuf-net/IExtension.cs @@ -0,0 +1,58 @@ + +using System.IO; +namespace ProtoBuf +{ + /// + /// Provides addition capability for supporting unexpected fields during + /// protocol-buffer serialization/deserialization. This allows for loss-less + /// round-trip/merge, even when the data is not fully understood. + /// + public interface IExtension + { + /// + /// Requests a stream into which any unexpected fields can be persisted. + /// + /// A new stream suitable for storing data. + Stream BeginAppend(); + + /// + /// Indicates that all unexpected fields have now been stored. The + /// implementing class is responsible for closing the stream. If + /// "commit" is not true the data may be discarded. + /// + /// The stream originally obtained by BeginAppend. + /// True if the append operation completed successfully. + void EndAppend(Stream stream, bool commit); + + /// + /// Requests a stream of the unexpected fields previously stored. + /// + /// A prepared stream of the unexpected fields. + Stream BeginQuery(); + + /// + /// Indicates that all unexpected fields have now been read. The + /// implementing class is responsible for closing the stream. + /// + /// The stream originally obtained by BeginQuery. + void EndQuery(Stream stream); + + /// + /// Requests the length of the raw binary stream; this is used + /// when serializing sub-entities to indicate the expected size. + /// + /// The length of the binary stream representing unexpected data. + int GetLength(); + } + + /// + /// Provides the ability to remove all existing extension data + /// + public interface IExtensionResettable : IExtension + { + /// + /// Remove all existing extension data + /// + void Reset(); + } +} diff --git a/Runtime/Protobuf-net/IExtension.cs.meta b/Runtime/Protobuf-net/IExtension.cs.meta new file mode 100644 index 0000000..d5da340 --- /dev/null +++ b/Runtime/Protobuf-net/IExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8018fb363175787478148842225e7d16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/IProtoInputT.cs b/Runtime/Protobuf-net/IProtoInputT.cs new file mode 100644 index 0000000..6eaa0ce --- /dev/null +++ b/Runtime/Protobuf-net/IProtoInputT.cs @@ -0,0 +1,13 @@ +namespace ProtoBuf +{ + /// + /// Represents the ability to deserialize values from an input of type + /// + public interface IProtoInput + { + /// + /// Deserialize a value from the input + /// + T Deserialize(TInput source, T value = default, object userState = null); + } +} diff --git a/Runtime/Protobuf-net/IProtoInputT.cs.meta b/Runtime/Protobuf-net/IProtoInputT.cs.meta new file mode 100644 index 0000000..a80bc62 --- /dev/null +++ b/Runtime/Protobuf-net/IProtoInputT.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6514bacfd3143a49a027f15434586f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/IProtoOutputT.cs b/Runtime/Protobuf-net/IProtoOutputT.cs new file mode 100644 index 0000000..1c7dd42 --- /dev/null +++ b/Runtime/Protobuf-net/IProtoOutputT.cs @@ -0,0 +1,55 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Represents the ability to serialize values to an output of type + /// + public interface IProtoOutput + { + /// + /// Serialize the provided value + /// + void Serialize(TOutput destination, T value, object userState = null); + } + + /// + /// Represents the ability to serialize values to an output of type + /// with pre-computation of the length + /// + public interface IMeasuredProtoOutput : IProtoOutput + { + /// + /// Measure the length of a value in advance of serialization + /// + MeasureState Measure(T value, object userState = null); + + /// + /// Serialize the previously measured value + /// + void Serialize(MeasureState measured, TOutput destination); + } + + /// + /// Represents the outcome of computing the length of an object; since this may have required computing lengths + /// for multiple objects, some metadata is retained so that a subsequent serialize operation using + /// this instance can re-use the previously calculated lengths. If the object state changes between the + /// measure and serialize operations, the behavior is undefined. + /// + public struct MeasureState : IDisposable + // note: 2.4.* does not actually implement this API; + // it only advertises it for 3.* capability/feature-testing, i.e. + // callers can check whether a model implements + // IMeasuredProtoOutput, and *work from that* + { + /// + /// Releases all resources associated with this value + /// + public void Dispose() => throw new NotImplementedException(); + + /// + /// Gets the calculated length of this serialize operation, in bytes + /// + public long Length => throw new NotImplementedException(); + } +} diff --git a/Runtime/Protobuf-net/IProtoOutputT.cs.meta b/Runtime/Protobuf-net/IProtoOutputT.cs.meta new file mode 100644 index 0000000..a6e7d86 --- /dev/null +++ b/Runtime/Protobuf-net/IProtoOutputT.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17c52d90924d69d4aaf31925ea2c90bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ImplicitFields.cs b/Runtime/Protobuf-net/ImplicitFields.cs new file mode 100644 index 0000000..211abdd --- /dev/null +++ b/Runtime/Protobuf-net/ImplicitFields.cs @@ -0,0 +1,29 @@ +namespace ProtoBuf +{ + /// + /// Specifies the method used to infer field tags for members of the type + /// under consideration. Tags are deduced using the invariant alphabetic + /// sequence of the members' names; this makes implicit field tags very brittle, + /// and susceptible to changes such as field names (normally an isolated + /// change). + /// + public enum ImplicitFields + { + /// + /// No members are serialized implicitly; all members require a suitable + /// attribute such as [ProtoMember]. This is the recmomended mode for + /// most scenarios. + /// + None = 0, + /// + /// Public properties and fields are eligible for implicit serialization; + /// this treats the public API as a contract. Ordering beings from ImplicitFirstTag. + /// + AllPublic = 1, + /// + /// Public and non-public fields are eligible for implicit serialization; + /// this acts as a state/implementation serializer. Ordering beings from ImplicitFirstTag. + /// + AllFields = 2 + } +} diff --git a/Runtime/Protobuf-net/ImplicitFields.cs.meta b/Runtime/Protobuf-net/ImplicitFields.cs.meta new file mode 100644 index 0000000..6da3bef --- /dev/null +++ b/Runtime/Protobuf-net/ImplicitFields.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b838f9e3c6536bc438e7c31f73c49160 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/KeyValuePairProxy.cs b/Runtime/Protobuf-net/KeyValuePairProxy.cs new file mode 100644 index 0000000..0da5761 --- /dev/null +++ b/Runtime/Protobuf-net/KeyValuePairProxy.cs @@ -0,0 +1,44 @@ +//using System.Collections.Generic; + +//namespace ProtoBuf +//{ +// /// +// /// Mutable version of the common key/value pair struct; used during serialization. This type is intended for internal use only and should not +// /// be used by calling code; it is required to be public for implementation reasons. +// /// +// [ProtoContract] +// public struct KeyValuePairSurrogate +// { +// private TKey key; +// private TValue value; +// /// +// /// The key of the pair. +// /// +// [ProtoMember(1, IsRequired = true)] +// public TKey Key { get { return key; } set { key = value; } } +// /// +// /// The value of the pair. +// /// +// [ProtoMember(2)] +// public TValue Value{ get { return value; } set { this.value = value; } } +// private KeyValuePairSurrogate(TKey key, TValue value) +// { +// this.key = key; +// this.value = value; +// } +// /// +// /// Convert a surrogate instance to a standard pair instance. +// /// +// public static implicit operator KeyValuePair (KeyValuePairSurrogate value) +// { +// return new KeyValuePair(value.key, value.value); +// } +// /// +// /// Convert a standard pair instance to a surrogate instance. +// /// +// public static implicit operator KeyValuePairSurrogate(KeyValuePair value) +// { +// return new KeyValuePairSurrogate(value.Key, value.Value); +// } +// } +//} \ No newline at end of file diff --git a/Runtime/Protobuf-net/KeyValuePairProxy.cs.meta b/Runtime/Protobuf-net/KeyValuePairProxy.cs.meta new file mode 100644 index 0000000..c74b284 --- /dev/null +++ b/Runtime/Protobuf-net/KeyValuePairProxy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6221476e2339494cb5ee2bdc10ffd81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta.meta b/Runtime/Protobuf-net/Meta.meta new file mode 100644 index 0000000..5f17bdd --- /dev/null +++ b/Runtime/Protobuf-net/Meta.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a70a85c13dddce74d9a6395c440c9156 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/AttributeMap.cs b/Runtime/Protobuf-net/Meta/AttributeMap.cs new file mode 100644 index 0000000..5bab942 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/AttributeMap.cs @@ -0,0 +1,108 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +namespace ProtoBuf.Meta +{ + internal abstract class AttributeMap + { +#if DEBUG + [Obsolete("Please use AttributeType instead")] + new public Type GetType() => AttributeType; +#endif + public override string ToString() => AttributeType?.FullName ?? ""; + public abstract bool TryGet(string key, bool publicOnly, out object value); + public bool TryGet(string key, out object value) + { + return TryGet(key, true, out value); + } + public abstract Type AttributeType { get; } + public static AttributeMap[] Create(TypeModel model, Type type, bool inherit) + { + +#if COREFX || PROFILE259 + Attribute[] all = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.OfType(type.GetTypeInfo().GetCustomAttributes(inherit))); +#else + object[] all = type.GetCustomAttributes(inherit); +#endif + AttributeMap[] result = new AttributeMap[all.Length]; + for(int i = 0 ; i < all.Length ; i++) + { + result[i] = new ReflectionAttributeMap((Attribute)all[i]); + } + return result; + } + + public static AttributeMap[] Create(TypeModel model, MemberInfo member, bool inherit) + { + +#if COREFX || PROFILE259 + Attribute[] all = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.OfType(member.GetCustomAttributes(inherit))); +#else + object[] all = member.GetCustomAttributes(inherit); +#endif + AttributeMap[] result = new AttributeMap[all.Length]; + for(int i = 0 ; i < all.Length ; i++) + { + result[i] = new ReflectionAttributeMap((Attribute)all[i]); + } + return result; + } + public static AttributeMap[] Create(TypeModel model, Assembly assembly) + { +#if COREFX || PROFILE259 + Attribute[] all = System.Linq.Enumerable.ToArray(assembly.GetCustomAttributes()); +#else + const bool inherit = false; + object[] all = assembly.GetCustomAttributes(inherit); +#endif + AttributeMap[] result = new AttributeMap[all.Length]; + for(int i = 0 ; i < all.Length ; i++) + { + result[i] = new ReflectionAttributeMap((Attribute)all[i]); + } + return result; + + } + + public abstract object Target { get; } + + private sealed class ReflectionAttributeMap : AttributeMap + { + private readonly Attribute attribute; + + public ReflectionAttributeMap(Attribute attribute) + { + this.attribute = attribute; + } + + public override object Target => attribute; + + public override Type AttributeType => attribute.GetType(); + + public override bool TryGet(string key, bool publicOnly, out object value) + { + MemberInfo[] members = Helpers.GetInstanceFieldsAndProperties(attribute.GetType(), publicOnly); + foreach (MemberInfo member in members) + { + if (string.Equals(member.Name, key, StringComparison.OrdinalIgnoreCase)) + { + if (member is PropertyInfo prop) { + value = prop.GetValue(attribute, null); + return true; + } + if (member is FieldInfo field) { + value = field.GetValue(attribute); + return true; + } + + throw new NotSupportedException(member.GetType().Name); + } + } + value = null; + return false; + } + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/AttributeMap.cs.meta b/Runtime/Protobuf-net/Meta/AttributeMap.cs.meta new file mode 100644 index 0000000..d92ef3a --- /dev/null +++ b/Runtime/Protobuf-net/Meta/AttributeMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3e64de7ef1358447843db562f78060f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/BasicList.cs b/Runtime/Protobuf-net/Meta/BasicList.cs new file mode 100644 index 0000000..d1308f3 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/BasicList.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections; + +namespace ProtoBuf.Meta +{ + internal sealed class MutableList : BasicList + { + /* Like BasicList, but allows existing values to be changed + */ + public new object this[int index] + { + get { return head[index]; } + set { head[index] = value; } + } + public void RemoveLast() + { + head.RemoveLastWithMutate(); + } + + public void Clear() + { + head.Clear(); + } + } + + internal class BasicList : IEnumerable + { + /* Requirements: + * - Fast access by index + * - Immutable in the tail, so a node can be read (iterated) without locking + * - Lock-free tail handling must match the memory mode; struct for Node + * wouldn't work as "read" would not be atomic + * - Only operation required is append, but this shouldn't go out of its + * way to be inefficient + * - Assume that the caller is handling thread-safety (to co-ordinate with + * other code); no attempt to be thread-safe + * - Assume that the data is private; internal data structure is allowed to + * be mutable (i.e. array is fine as long as we don't screw it up) + */ + private static readonly Node nil = new Node(null, 0); + + public void CopyTo(Array array, int offset) + { + head.CopyTo(array, offset); + } + + protected Node head = nil; + + public int Add(object value) + { + return (head = head.Append(value)).Length - 1; + } + + public object this[int index] => head[index]; + + //public object TryGet(int index) + //{ + // return head.TryGet(index); + //} + + public void Trim() { head = head.Trim(); } + + public int Count => head.Length; + + IEnumerator IEnumerable.GetEnumerator() => new NodeEnumerator(head); + + public NodeEnumerator GetEnumerator() => new NodeEnumerator(head); + + public struct NodeEnumerator : IEnumerator + { + private int position; + private readonly Node node; + internal NodeEnumerator(Node node) + { + this.position = -1; + this.node = node; + } + void IEnumerator.Reset() { position = -1; } + public object Current { get { return node[position]; } } + public bool MoveNext() + { + int len = node.Length; + return (position <= len) && (++position < len); + } + } + + internal sealed class Node + { + public object this[int index] + { + get + { + if (index >= 0 && index < length) + { + return data[index]; + } + throw new ArgumentOutOfRangeException(nameof(index)); + } + set + { + if (index >= 0 && index < length) + { + data[index] = value; + } + else + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } + //public object TryGet(int index) + //{ + // return (index >= 0 && index < length) ? data[index] : null; + //} + private readonly object[] data; + + private int length; + public int Length => length; + + internal Node(object[] data, int length) + { + Helpers.DebugAssert((data == null && length == 0) || + (data != null && length > 0 && length <= data.Length)); + this.data = data; + + this.length = length; + } + + public void RemoveLastWithMutate() + { + if (length == 0) throw new InvalidOperationException(); + length -= 1; + } + + public Node Append(object value) + { + object[] newData; + int newLength = length + 1; + if (data == null) + { + newData = new object[10]; + } + else if (length == data.Length) + { + newData = new object[data.Length * 2]; + Array.Copy(data, newData, length); + } + else + { + newData = data; + } + newData[length] = value; + return new Node(newData, newLength); + } + + public Node Trim() + { + if (length == 0 || length == data.Length) return this; + object[] newData = new object[length]; + Array.Copy(data, newData, length); + return new Node(newData, length); + } + + internal int IndexOfString(string value) + { + for (int i = 0; i < length; i++) + { + if ((string)value == (string)data[i]) return i; + } + return -1; + } + + internal int IndexOfReference(object instance) + { + for (int i = 0; i < length; i++) + { + if ((object)instance == (object)data[i]) return i; + } // ^^^ (object) above should be preserved, even if this was typed; needs + // to be a reference check + return -1; + } + + internal int IndexOf(MatchPredicate predicate, object ctx) + { + for (int i = 0; i < length; i++) + { + if (predicate(data[i], ctx)) return i; + } + return -1; + } + + internal void CopyTo(Array array, int offset) + { + if (length > 0) + { + Array.Copy(data, 0, array, offset, length); + } + } + + internal void Clear() + { + if (data != null) + { + Array.Clear(data, 0, data.Length); + } + length = 0; + } + } + + internal int IndexOf(MatchPredicate predicate, object ctx) + { + return head.IndexOf(predicate, ctx); + } + + internal int IndexOfString(string value) + { + return head.IndexOfString(value); + } + + internal int IndexOfReference(object instance) + { + return head.IndexOfReference(instance); + } + + internal delegate bool MatchPredicate(object value, object ctx); + + internal bool Contains(object value) + { + foreach (object obj in this) + { + if (object.Equals(obj, value)) return true; + } + return false; + } + + internal sealed class Group + { + public readonly int First; + public readonly BasicList Items; + public Group(int first) + { + this.First = first; + this.Items = new BasicList(); + } + } + + internal static BasicList GetContiguousGroups(int[] keys, object[] values) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (values == null) throw new ArgumentNullException(nameof(values)); + if (values.Length < keys.Length) throw new ArgumentException("Not all keys are covered by values", nameof(values)); + BasicList outer = new BasicList(); + Group group = null; + for (int i = 0; i < keys.Length; i++) + { + if (i == 0 || keys[i] != keys[i - 1]) { group = null; } + if (group == null) + { + group = new Group(keys[i]); + outer.Add(group); + } + group.Items.Add(values[i]); + } + return outer; + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/BasicList.cs.meta b/Runtime/Protobuf-net/Meta/BasicList.cs.meta new file mode 100644 index 0000000..3304e30 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/BasicList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be5fc2a1ac0731a44b0365987d942485 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/CallbackSet.cs b/Runtime/Protobuf-net/Meta/CallbackSet.cs new file mode 100644 index 0000000..8b08585 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/CallbackSet.cs @@ -0,0 +1,110 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +namespace ProtoBuf.Meta +{ + /// + /// Represents the set of serialization callbacks to be used when serializing/deserializing a type. + /// + public class CallbackSet + { + private readonly MetaType metaType; + internal CallbackSet(MetaType metaType) + { + this.metaType = metaType ?? throw new ArgumentNullException(nameof(metaType)); + } + + internal MethodInfo this[TypeModel.CallbackType callbackType] + { + get + { + switch (callbackType) + { + case TypeModel.CallbackType.BeforeSerialize: return beforeSerialize; + case TypeModel.CallbackType.AfterSerialize: return afterSerialize; + case TypeModel.CallbackType.BeforeDeserialize: return beforeDeserialize; + case TypeModel.CallbackType.AfterDeserialize: return afterDeserialize; + default: throw new ArgumentException("Callback type not supported: " + callbackType.ToString(), "callbackType"); + } + } + } + + internal static bool CheckCallbackParameters(TypeModel model, MethodInfo method) + { + ParameterInfo[] args = method.GetParameters(); + for (int i = 0; i < args.Length; i++) + { + Type paramType = args[i].ParameterType; + if (paramType == model.MapType(typeof(SerializationContext))) { } + else if (paramType == model.MapType(typeof(System.Type))) { } +#if PLAT_BINARYFORMATTER + else if (paramType == model.MapType(typeof(System.Runtime.Serialization.StreamingContext))) { } +#endif + else return false; + } + return true; + } + + private MethodInfo SanityCheckCallback(TypeModel model, MethodInfo callback) + { + metaType.ThrowIfFrozen(); + if (callback == null) return callback; // fine + if (callback.IsStatic) throw new ArgumentException("Callbacks cannot be static", nameof(callback)); + if (callback.ReturnType != model.MapType(typeof(void)) + || !CheckCallbackParameters(model, callback)) + { + throw CreateInvalidCallbackSignature(callback); + } + return callback; + } + + internal static Exception CreateInvalidCallbackSignature(MethodInfo method) + { + return new NotSupportedException("Invalid callback signature in " + method.DeclaringType.FullName + "." + method.Name); + } + + private MethodInfo beforeSerialize, afterSerialize, beforeDeserialize, afterDeserialize; + + /// Called before serializing an instance + public MethodInfo BeforeSerialize + { + get { return beforeSerialize; } + set { beforeSerialize = SanityCheckCallback(metaType.Model, value); } + } + + /// Called before deserializing an instance + public MethodInfo BeforeDeserialize + { + get { return beforeDeserialize; } + set { beforeDeserialize = SanityCheckCallback(metaType.Model, value); } + } + + /// Called after serializing an instance + public MethodInfo AfterSerialize + { + get { return afterSerialize; } + set { afterSerialize = SanityCheckCallback(metaType.Model, value); } + } + + /// Called after deserializing an instance + public MethodInfo AfterDeserialize + { + get { return afterDeserialize; } + set { afterDeserialize = SanityCheckCallback(metaType.Model, value); } + } + + /// + /// True if any callback is set, else False + /// + public bool NonTrivial + { + get + { + return beforeSerialize != null || beforeDeserialize != null + || afterSerialize != null || afterDeserialize != null; + } + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/CallbackSet.cs.meta b/Runtime/Protobuf-net/Meta/CallbackSet.cs.meta new file mode 100644 index 0000000..0c6da40 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/CallbackSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de0e7cb7bfcf4904aa31e910f241a8aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/MetaType.cs b/Runtime/Protobuf-net/Meta/MetaType.cs new file mode 100644 index 0000000..8d9bed6 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/MetaType.cs @@ -0,0 +1,2171 @@ +#if !NO_RUNTIME +using System; +using System.Collections; +using System.Text; +using ProtoBuf.Serializers; +using System.Reflection; +using System.Collections.Generic; + +#if PROFILE259 +using System.Linq; +#endif + +namespace ProtoBuf.Meta +{ + /// + /// Represents a type at runtime for use with protobuf, allowing the field mappings (etc) to be defined + /// + public class MetaType : ISerializerProxy + { + internal sealed class Comparer : IComparer, IComparer + { + public static readonly Comparer Default = new Comparer(); + public int Compare(object x, object y) + { + return Compare(x as MetaType, y as MetaType); + } + public int Compare(MetaType x, MetaType y) + { + if (ReferenceEquals(x, y)) return 0; + if (x == null) return -1; + if (y == null) return 1; + + return string.Compare(x.GetSchemaTypeName(), y.GetSchemaTypeName(), StringComparison.Ordinal); + } + } + /// + /// Get the name of the type being represented + /// + public override string ToString() + { + return type.ToString(); + } + + IProtoSerializer ISerializerProxy.Serializer => Serializer; + private MetaType baseType; + + /// + /// Gets the base-type for this type + /// + public MetaType BaseType => baseType; + + internal TypeModel Model => model; + + /// + /// When used to compile a model, should public serialization/deserialzation methods + /// be included for this type? + /// + public bool IncludeSerializerMethod + { // negated to minimize common-case / initializer + get { return !HasFlag(OPTIONS_PrivateOnApi); } + set { SetFlag(OPTIONS_PrivateOnApi, !value, true); } + } + + /// + /// Should this type be treated as a reference by default? + /// + public bool AsReferenceDefault + { + get { return HasFlag(OPTIONS_AsReferenceDefault); } + set { SetFlag(OPTIONS_AsReferenceDefault, value, true); } + } + + private BasicList subTypes; + private bool IsValidSubType(Type subType) + { +#if COREFX || PROFILE259 + return typeInfo.IsAssignableFrom(subType.GetTypeInfo()); +#else + return type.IsAssignableFrom(subType); +#endif + } + /// + /// Adds a known sub-type to the inheritance model + /// + public MetaType AddSubType(int fieldNumber, Type derivedType) + { + return AddSubType(fieldNumber, derivedType, DataFormat.Default); + } + /// + /// Adds a known sub-type to the inheritance model + /// + public MetaType AddSubType(int fieldNumber, Type derivedType, DataFormat dataFormat) + { + if (derivedType == null) throw new ArgumentNullException("derivedType"); + if (fieldNumber < 1) throw new ArgumentOutOfRangeException("fieldNumber"); +#if COREFX || COREFX || PROFILE259 + if (!(typeInfo.IsClass || typeInfo.IsInterface) || typeInfo.IsSealed) { +#else + if (!(type.IsClass || type.IsInterface) || type.IsSealed) + { +#endif + throw new InvalidOperationException("Sub-types can only be added to non-sealed classes"); + } + if (!IsValidSubType(derivedType)) + { + throw new ArgumentException(derivedType.Name + " is not a valid sub-type of " + type.Name, "derivedType"); + } + MetaType derivedMeta = model[derivedType]; + ThrowIfFrozen(); + derivedMeta.ThrowIfFrozen(); + SubType subType = new SubType(fieldNumber, derivedMeta, dataFormat); + ThrowIfFrozen(); + + derivedMeta.SetBaseType(this); // includes ThrowIfFrozen + if (subTypes == null) subTypes = new BasicList(); + subTypes.Add(subType); + model.ResetKeyCache(); + return this; + } +#if COREFX || PROFILE259 + internal static readonly TypeInfo ienumerable = typeof(IEnumerable).GetTypeInfo(); +#else + internal static readonly Type ienumerable = typeof(IEnumerable); +#endif + private void SetBaseType(MetaType baseType) + { + if (baseType == null) throw new ArgumentNullException("baseType"); + if (this.baseType == baseType) return; + if (this.baseType != null) throw new InvalidOperationException($"Type '{this.baseType.Type.FullName}' can only participate in one inheritance hierarchy"); + + MetaType type = baseType; + while (type != null) + { + if (ReferenceEquals(type, this)) throw new InvalidOperationException($"Cyclic inheritance of '{this.baseType.Type.FullName}' is not allowed"); + type = type.baseType; + } + this.baseType = baseType; + } + + private CallbackSet callbacks; + + /// + /// Indicates whether the current type has defined callbacks + /// + public bool HasCallbacks => callbacks != null && callbacks.NonTrivial; + + /// + /// Indicates whether the current type has defined subtypes + /// + public bool HasSubtypes => subTypes != null && subTypes.Count != 0; + + /// + /// Returns the set of callbacks defined for this type + /// + public CallbackSet Callbacks + { + get + { + if (callbacks == null) callbacks = new CallbackSet(this); + return callbacks; + } + } + + private bool IsValueType + { + get + { +#if COREFX || PROFILE259 + return typeInfo.IsValueType; +#else + return type.IsValueType; +#endif + } + } + /// + /// Assigns the callbacks to use during serialiation/deserialization. + /// + /// The method (or null) called before serialization begins. + /// The method (or null) called when serialization is complete. + /// The method (or null) called before deserialization begins (or when a new instance is created during deserialization). + /// The method (or null) called when deserialization is complete. + /// The set of callbacks. + public MetaType SetCallbacks(MethodInfo beforeSerialize, MethodInfo afterSerialize, MethodInfo beforeDeserialize, MethodInfo afterDeserialize) + { + CallbackSet callbacks = Callbacks; + callbacks.BeforeSerialize = beforeSerialize; + callbacks.AfterSerialize = afterSerialize; + callbacks.BeforeDeserialize = beforeDeserialize; + callbacks.AfterDeserialize = afterDeserialize; + return this; + } + /// + /// Assigns the callbacks to use during serialiation/deserialization. + /// + /// The name of the method (or null) called before serialization begins. + /// The name of the method (or null) called when serialization is complete. + /// The name of the method (or null) called before deserialization begins (or when a new instance is created during deserialization). + /// The name of the method (or null) called when deserialization is complete. + /// The set of callbacks. + public MetaType SetCallbacks(string beforeSerialize, string afterSerialize, string beforeDeserialize, string afterDeserialize) + { + if (IsValueType) throw new InvalidOperationException(); + CallbackSet callbacks = Callbacks; + callbacks.BeforeSerialize = ResolveMethod(beforeSerialize, true); + callbacks.AfterSerialize = ResolveMethod(afterSerialize, true); + callbacks.BeforeDeserialize = ResolveMethod(beforeDeserialize, true); + callbacks.AfterDeserialize = ResolveMethod(afterDeserialize, true); + return this; + } + + /// + /// Returns the public Type name of this Type used in serialization + /// + public string GetSchemaTypeName() + { + if (surrogate != null) return model[surrogate].GetSchemaTypeName(); + + if (!string.IsNullOrEmpty(name)) return name; + + string typeName = type.Name; + if (type +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + .IsGenericType) + { + var sb = new StringBuilder(typeName); + int split = typeName.IndexOf('`'); + if (split >= 0) sb.Length = split; + foreach (Type arg in type +#if COREFX || PROFILE259 + .GetTypeInfo().GenericTypeArguments +#else + .GetGenericArguments() +#endif + ) + { + sb.Append('_'); + Type tmp = arg; + int key = model.GetKey(ref tmp); + MetaType mt; + if (key >= 0 && (mt = model[tmp]) != null && mt.surrogate == null) // <=== need to exclude surrogate to avoid chance of infinite loop + { + + sb.Append(mt.GetSchemaTypeName()); + } + else + { + sb.Append(tmp.Name); + } + } + return sb.ToString(); + } + + return typeName; + } + + private string name; + + /// + /// Gets or sets the name of this contract. + /// + public string Name + { + get + { + return name; + } + set + { + ThrowIfFrozen(); + name = value; + } + } + + private MethodInfo factory; + /// + /// Designate a factory-method to use to create instances of this type + /// + public MetaType SetFactory(MethodInfo factory) + { + model.VerifyFactory(factory, type); + ThrowIfFrozen(); + this.factory = factory; + return this; + } + + /// + /// Designate a factory-method to use to create instances of this type + /// + public MetaType SetFactory(string factory) + { + return SetFactory(ResolveMethod(factory, false)); + } + + private MethodInfo ResolveMethod(string name, bool instance) + { + if (string.IsNullOrEmpty(name)) return null; +#if COREFX + return instance ? Helpers.GetInstanceMethod(typeInfo, name) : Helpers.GetStaticMethod(typeInfo, name); +#else + return instance ? Helpers.GetInstanceMethod(type, name) : Helpers.GetStaticMethod(type, name); +#endif + } + + private readonly RuntimeTypeModel model; + + internal static Exception InbuiltType(Type type) + { + return new ArgumentException("Data of this type has inbuilt behaviour, and cannot be added to a model in this way: " + type.FullName); + } + + internal MetaType(RuntimeTypeModel model, Type type, MethodInfo factory) + { + this.factory = factory; + if (model == null) throw new ArgumentNullException("model"); + if (type == null) throw new ArgumentNullException("type"); + + if (type.IsArray) throw InbuiltType(type); + IProtoSerializer coreSerializer = model.TryGetBasicTypeSerializer(type); + if (coreSerializer != null) + { + throw InbuiltType(type); + } + + this.type = type; +#if COREFX || PROFILE259 + this.typeInfo = type.GetTypeInfo(); +#endif + this.model = model; + + if (Helpers.IsEnum(type)) + { +#if COREFX || PROFILE259 + EnumPassthru = typeInfo.IsDefined(typeof(FlagsAttribute), false); +#else + EnumPassthru = type.IsDefined(model.MapType(typeof(FlagsAttribute)), false); +#endif + } + } +#if COREFX || PROFILE259 + private readonly TypeInfo typeInfo; +#endif + /// + /// Throws an exception if the type has been made immutable + /// + protected internal void ThrowIfFrozen() + { + if ((flags & OPTIONS_Frozen) != 0) throw new InvalidOperationException("The type cannot be changed once a serializer has been generated for " + type.FullName); + } + + // internal void Freeze() { flags |= OPTIONS_Frozen; } + + private readonly Type type; + /// + /// The runtime type that the meta-type represents + /// + public Type Type => type; + + private IProtoTypeSerializer serializer; + internal IProtoTypeSerializer Serializer + { + get + { + if (serializer == null) + { + int opaqueToken = 0; + try + { + model.TakeLock(ref opaqueToken); + if (serializer == null) + { // double-check, but our main purpse with this lock is to ensure thread-safety with + // serializers needing to wait until another thread has finished adding the properties + SetFlag(OPTIONS_Frozen, true, false); + serializer = BuildSerializer(); +#if FEAT_COMPILER + if (model.AutoCompile) CompileInPlace(); +#endif + } + } + finally + { + model.ReleaseLock(opaqueToken); + } + } + return serializer; + } + } + internal bool IsList + { + get + { + Type itemType = IgnoreListHandling ? null : TypeModel.GetListItemType(model, type); + return itemType != null; + } + } + private IProtoTypeSerializer BuildSerializer() + { + if (Helpers.IsEnum(type)) + { + return new TagDecorator(ProtoBuf.Serializer.ListItemTag, WireType.Variant, false, new EnumSerializer(type, GetEnumMap())); + } + Type itemType = IgnoreListHandling ? null : TypeModel.GetListItemType(model, type); + if (itemType != null) + { + if (surrogate != null) + { + throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot use a surrogate"); + } + if (subTypes != null && subTypes.Count != 0) + { + throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be subclassed"); + } + Type defaultType = null; + ResolveListTypes(model, type, ref itemType, ref defaultType); + ValueMember fakeMember = new ValueMember(model, ProtoBuf.Serializer.ListItemTag, type, itemType, defaultType, DataFormat.Default); + return new TypeSerializer(model, type, new int[] { ProtoBuf.Serializer.ListItemTag }, new IProtoSerializer[] { fakeMember.Serializer }, null, true, true, null, constructType, factory); + } + if (surrogate != null) + { + MetaType mt = model[surrogate], mtBase; + while ((mtBase = mt.baseType) != null) { mt = mtBase; } + return new SurrogateSerializer(model, type, surrogate, mt.Serializer); + } + if (IsAutoTuple) + { + ConstructorInfo ctor = ResolveTupleConstructor(type, out MemberInfo[] mapping); + if (ctor == null) throw new InvalidOperationException(); + return new TupleSerializer(model, ctor, mapping); + } + + fields.Trim(); + int fieldCount = fields.Count; + int subTypeCount = subTypes == null ? 0 : subTypes.Count; + int[] fieldNumbers = new int[fieldCount + subTypeCount]; + IProtoSerializer[] serializers = new IProtoSerializer[fieldCount + subTypeCount]; + int i = 0; + if (subTypeCount != 0) + { + foreach (SubType subType in subTypes) + { +#if COREFX || PROFILE259 + if (!subType.DerivedType.IgnoreListHandling && ienumerable.IsAssignableFrom(subType.DerivedType.Type.GetTypeInfo())) +#else + if (!subType.DerivedType.IgnoreListHandling && model.MapType(ienumerable).IsAssignableFrom(subType.DerivedType.Type)) +#endif + { + throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be used as a subclass"); + } + fieldNumbers[i] = subType.FieldNumber; + serializers[i++] = subType.Serializer; + } + } + if (fieldCount != 0) + { + foreach (ValueMember member in fields) + { + fieldNumbers[i] = member.FieldNumber; + serializers[i++] = member.Serializer; + } + } + + BasicList baseCtorCallbacks = null; + MetaType tmp = BaseType; + + while (tmp != null) + { + MethodInfo method = tmp.HasCallbacks ? tmp.Callbacks.BeforeDeserialize : null; + if (method != null) + { + if (baseCtorCallbacks == null) baseCtorCallbacks = new BasicList(); + baseCtorCallbacks.Add(method); + } + tmp = tmp.BaseType; + } + MethodInfo[] arr = null; + if (baseCtorCallbacks != null) + { + arr = new MethodInfo[baseCtorCallbacks.Count]; + baseCtorCallbacks.CopyTo(arr, 0); + Array.Reverse(arr); + } + return new TypeSerializer(model, type, fieldNumbers, serializers, arr, baseType == null, UseConstructor, callbacks, constructType, factory); + } + + [Flags] + internal enum AttributeFamily + { + None = 0, ProtoBuf = 1, DataContractSerialier = 2, XmlSerializer = 4, AutoTuple = 8 + } + static Type GetBaseType(MetaType type) + { +#if COREFX || PROFILE259 + return type.typeInfo.BaseType; +#else + return type.type.BaseType; +#endif + } + internal static bool GetAsReferenceDefault(RuntimeTypeModel model, Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + if (Helpers.IsEnum(type)) return false; // never as-ref + AttributeMap[] typeAttribs = AttributeMap.Create(model, type, false); + for (int i = 0; i < typeAttribs.Length; i++) + { + if (typeAttribs[i].AttributeType.FullName == "ProtoBuf.ProtoContractAttribute") + { + if (typeAttribs[i].TryGet("AsReferenceDefault", out object tmp)) return (bool)tmp; + } + } + return false; + } + + internal void ApplyDefaultBehaviour() + { + TypeAddedEventArgs args = null; // allows us to share the event-args between events + RuntimeTypeModel.OnBeforeApplyDefaultBehaviour(this, ref args); + if (args == null || args.ApplyDefaultBehaviour) ApplyDefaultBehaviourImpl(); + RuntimeTypeModel.OnAfterApplyDefaultBehaviour(this, ref args); + } + + internal void ApplyDefaultBehaviourImpl() + { + Type baseType = GetBaseType(this); + if (baseType != null && model.FindWithoutAdd(baseType) == null + && GetContractFamily(model, baseType, null) != MetaType.AttributeFamily.None) + { + model.FindOrAddAuto(baseType, true, false, false); + } + + AttributeMap[] typeAttribs = AttributeMap.Create(model, type, false); + AttributeFamily family = GetContractFamily(model, type, typeAttribs); + if (family == AttributeFamily.AutoTuple) + { + SetFlag(OPTIONS_AutoTuple, true, true); + } + bool isEnum = !EnumPassthru && Helpers.IsEnum(type); + if (family == AttributeFamily.None && !isEnum) return; // and you'd like me to do what, exactly? + + bool enumShouldUseImplicitPassThru = isEnum; + BasicList partialIgnores = null, partialMembers = null; + int dataMemberOffset = 0, implicitFirstTag = 1; + bool inferTagByName = model.InferTagFromNameDefault; + ImplicitFields implicitMode = ImplicitFields.None; + string name = null; + for (int i = 0; i < typeAttribs.Length; i++) + { + AttributeMap item = (AttributeMap)typeAttribs[i]; + object tmp; + string fullAttributeTypeName = item.AttributeType.FullName; + if (!isEnum && fullAttributeTypeName == "ProtoBuf.ProtoIncludeAttribute") + { + int tag = 0; + if (item.TryGet("tag", out tmp)) tag = (int)tmp; + DataFormat dataFormat = DataFormat.Default; + if (item.TryGet("DataFormat", out tmp)) + { + dataFormat = (DataFormat)(int)tmp; + } + Type knownType = null; + try + { + if (item.TryGet("knownTypeName", out tmp)) knownType = model.GetType((string)tmp, type +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + .Assembly); + else if (item.TryGet("knownType", out tmp)) knownType = (Type)tmp; + } + catch (Exception ex) + { + throw new InvalidOperationException("Unable to resolve sub-type of: " + type.FullName, ex); + } + if (knownType == null) + { + throw new InvalidOperationException("Unable to resolve sub-type of: " + type.FullName); + } + if (IsValidSubType(knownType)) AddSubType(tag, knownType, dataFormat); + } + + if (fullAttributeTypeName == "ProtoBuf.ProtoPartialIgnoreAttribute") + { + if (item.TryGet(nameof(ProtoPartialIgnoreAttribute.MemberName), out tmp) && tmp != null) + { + if (partialIgnores == null) partialIgnores = new BasicList(); + partialIgnores.Add((string)tmp); + } + } + if (!isEnum && fullAttributeTypeName == "ProtoBuf.ProtoPartialMemberAttribute") + { + if (partialMembers == null) partialMembers = new BasicList(); + partialMembers.Add(item); + } + + if (fullAttributeTypeName == "ProtoBuf.ProtoContractAttribute") + { + if (item.TryGet(nameof(ProtoContractAttribute.Name), out tmp)) name = (string)tmp; + if (Helpers.IsEnum(type)) // note this is subtly different to isEnum; want to do this even if [Flags] + { + if (item.TryGet(nameof(ProtoContractAttribute.EnumPassthruHasValue), false, out tmp) && (bool)tmp) + { + if (item.TryGet(nameof(ProtoContractAttribute.EnumPassthru), out tmp)) + { + EnumPassthru = (bool)tmp; + enumShouldUseImplicitPassThru = false; + if (EnumPassthru) isEnum = false; // no longer treated as an enum + } + } + } + else + { + if (item.TryGet(nameof(ProtoContractAttribute.DataMemberOffset), out tmp)) dataMemberOffset = (int)tmp; + + if (item.TryGet(nameof(ProtoContractAttribute.InferTagFromNameHasValue), false, out tmp) && (bool)tmp) + { + if (item.TryGet(nameof(ProtoContractAttribute.InferTagFromName), out tmp)) inferTagByName = (bool)tmp; + } + + if (item.TryGet(nameof(ProtoContractAttribute.ImplicitFields), out tmp) && tmp != null) + { + implicitMode = (ImplicitFields)(int)tmp; // note that this uses the bizarre unboxing rules of enums/underlying-types + } + + if (item.TryGet(nameof(ProtoContractAttribute.SkipConstructor), out tmp)) UseConstructor = !(bool)tmp; + if (item.TryGet(nameof(ProtoContractAttribute.IgnoreListHandling), out tmp)) IgnoreListHandling = (bool)tmp; + if (item.TryGet(nameof(ProtoContractAttribute.AsReferenceDefault), out tmp)) AsReferenceDefault = (bool)tmp; + if (item.TryGet(nameof(ProtoContractAttribute.ImplicitFirstTag), out tmp) && (int)tmp > 0) implicitFirstTag = (int)tmp; + if (item.TryGet(nameof(ProtoContractAttribute.IsGroup), out tmp)) IsGroup = (bool)tmp; + + if (item.TryGet(nameof(ProtoContractAttribute.Surrogate), out tmp)) + { + SetSurrogate((Type)tmp); + } + } + } + + if (fullAttributeTypeName == "System.Runtime.Serialization.DataContractAttribute") + { + if (name == null && item.TryGet("Name", out tmp)) name = (string)tmp; + } + if (fullAttributeTypeName == "System.Xml.Serialization.XmlTypeAttribute") + { + if (name == null && item.TryGet("TypeName", out tmp)) name = (string)tmp; + } + } + if (!string.IsNullOrEmpty(name)) Name = name; + if (implicitMode != ImplicitFields.None) + { + family &= AttributeFamily.ProtoBuf; // with implicit fields, **only** proto attributes are important + } + MethodInfo[] callbacks = null; + + BasicList members = new BasicList(); + +#if PROFILE259 + IEnumerable foundList; + if(isEnum) { + foundList = type.GetRuntimeFields(); + } + else + { + List list = new List(); + foreach(PropertyInfo prop in type.GetRuntimeProperties()) { + MethodInfo getter = Helpers.GetGetMethod(prop, false, false); + if(getter != null && !getter.IsStatic) list.Add(prop); + } + foreach(FieldInfo fld in type.GetRuntimeFields()) if(fld.IsPublic && !fld.IsStatic) list.Add(fld); + foreach(MethodInfo mthd in type.GetRuntimeMethods()) if(mthd.IsPublic && !mthd.IsStatic) list.Add(mthd); + foundList = list; + } +#else + MemberInfo[] foundList = type.GetMembers(isEnum ? BindingFlags.Public | BindingFlags.Static + : BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); +#endif + bool hasConflictingEnumValue = false; + foreach (MemberInfo member in foundList) + { + if (member.DeclaringType != type) continue; + if (member.IsDefined(model.MapType(typeof(ProtoIgnoreAttribute)), true)) continue; + if (partialIgnores != null && partialIgnores.Contains(member.Name)) continue; + + bool forced = false, isPublic, isField; + Type effectiveType; + + if (member is PropertyInfo property) + { + if (isEnum) continue; // wasn't expecting any props! + MemberInfo backingField = null; + if (!property.CanWrite) + { + // roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField; + var backingFieldName = $"<{property.Name}>k__BackingField"; + foreach (var fieldMemeber in foundList) + { + if ((fieldMemeber as FieldInfo != null) && fieldMemeber.Name == backingFieldName) + { + backingField = fieldMemeber; + break; + } + } + } + effectiveType = property.PropertyType; + isPublic = Helpers.GetGetMethod(property, false, false) != null; + isField = false; + ApplyDefaultBehaviour_AddMembers(model, family, isEnum, partialMembers, dataMemberOffset, inferTagByName, implicitMode, members, member, ref forced, isPublic, isField, ref effectiveType, ref hasConflictingEnumValue, backingField); + } + else if (member is FieldInfo field) + { + effectiveType = field.FieldType; + isPublic = field.IsPublic; + isField = true; + if (isEnum && !field.IsStatic) + { // only care about static things on enums; WinRT has a __value instance field! + continue; + } + ApplyDefaultBehaviour_AddMembers(model, family, isEnum, partialMembers, dataMemberOffset, inferTagByName, implicitMode, members, member, ref forced, isPublic, isField, ref effectiveType, ref hasConflictingEnumValue); + } + else if (member is MethodInfo method) + { + if (isEnum) continue; + AttributeMap[] memberAttribs = AttributeMap.Create(model, method, false); + if (memberAttribs != null && memberAttribs.Length > 0) + { + CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoBeforeSerializationAttribute", ref callbacks, 0); + CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoAfterSerializationAttribute", ref callbacks, 1); + CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoBeforeDeserializationAttribute", ref callbacks, 2); + CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoAfterDeserializationAttribute", ref callbacks, 3); + CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnSerializingAttribute", ref callbacks, 4); + CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnSerializedAttribute", ref callbacks, 5); + CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnDeserializingAttribute", ref callbacks, 6); + CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnDeserializedAttribute", ref callbacks, 7); + } + } + } + + if (isEnum && enumShouldUseImplicitPassThru && !hasConflictingEnumValue) + { + EnumPassthru = true; + // but leave isEnum alone + } + var arr = new ProtoMemberAttribute[members.Count]; + members.CopyTo(arr, 0); + + if (inferTagByName || implicitMode != ImplicitFields.None) + { + Array.Sort(arr); + int nextTag = implicitFirstTag; + foreach (ProtoMemberAttribute normalizedAttribute in arr) + { + if (!normalizedAttribute.TagIsPinned) // if ProtoMember etc sets a tag, we'll trust it + { + normalizedAttribute.Rebase(nextTag++); + } + } + } + + foreach (ProtoMemberAttribute normalizedAttribute in arr) + { + ValueMember vm = ApplyDefaultBehaviour(isEnum, normalizedAttribute); + if (vm != null) + { + Add(vm); + } + } + + if (callbacks != null) + { + SetCallbacks(Coalesce(callbacks, 0, 4), Coalesce(callbacks, 1, 5), + Coalesce(callbacks, 2, 6), Coalesce(callbacks, 3, 7)); + } + } + + private static void ApplyDefaultBehaviour_AddMembers(TypeModel model, AttributeFamily family, bool isEnum, BasicList partialMembers, int dataMemberOffset, bool inferTagByName, ImplicitFields implicitMode, BasicList members, MemberInfo member, ref bool forced, bool isPublic, bool isField, ref Type effectiveType, ref bool hasConflictingEnumValue, MemberInfo backingMember = null) + { + switch (implicitMode) + { + case ImplicitFields.AllFields: + if (isField) forced = true; + break; + case ImplicitFields.AllPublic: + if (isPublic) forced = true; + break; + } + + // we just don't like delegate types ;p +#if COREFX || PROFILE259 + if (effectiveType.GetTypeInfo().IsSubclassOf(typeof(Delegate))) effectiveType = null; +#else + if (effectiveType.IsSubclassOf(model.MapType(typeof(Delegate)))) effectiveType = null; +#endif + if (effectiveType != null) + { + ProtoMemberAttribute normalizedAttribute = NormalizeProtoMember(model, member, family, forced, isEnum, partialMembers, dataMemberOffset, inferTagByName, ref hasConflictingEnumValue, backingMember); + if (normalizedAttribute != null) members.Add(normalizedAttribute); + } + } + + static MethodInfo Coalesce(MethodInfo[] arr, int x, int y) + { + MethodInfo mi = arr[x]; + if (mi == null) mi = arr[y]; + return mi; + } + + internal static AttributeFamily GetContractFamily(RuntimeTypeModel model, Type type, AttributeMap[] attributes) + { + AttributeFamily family = AttributeFamily.None; + + if (attributes == null) attributes = AttributeMap.Create(model, type, false); + + for (int i = 0; i < attributes.Length; i++) + { + switch (attributes[i].AttributeType.FullName) + { + case "ProtoBuf.ProtoContractAttribute": + bool tmp = false; + GetFieldBoolean(ref tmp, attributes[i], "UseProtoMembersOnly"); + if (tmp) return AttributeFamily.ProtoBuf; + family |= AttributeFamily.ProtoBuf; + break; + case "System.Xml.Serialization.XmlTypeAttribute": + if (!model.AutoAddProtoContractTypesOnly) + { + family |= AttributeFamily.XmlSerializer; + } + break; + case "System.Runtime.Serialization.DataContractAttribute": + if (!model.AutoAddProtoContractTypesOnly) + { + family |= AttributeFamily.DataContractSerialier; + } + break; + } + } + if (family == AttributeFamily.None) + { // check for obvious tuples + if (ResolveTupleConstructor(type, out MemberInfo[] mapping) != null) + { + family |= AttributeFamily.AutoTuple; + } + } + return family; + } + internal static ConstructorInfo ResolveTupleConstructor(Type type, out MemberInfo[] mappedMembers) + { + mappedMembers = null; + if (type == null) throw new ArgumentNullException(nameof(type)); +#if COREFX || PROFILE259 + TypeInfo typeInfo = type.GetTypeInfo(); + if (typeInfo.IsAbstract) return null; // as if! + ConstructorInfo[] ctors = Helpers.GetConstructors(typeInfo, false); +#else + if (type.IsAbstract) return null; // as if! + ConstructorInfo[] ctors = Helpers.GetConstructors(type, false); +#endif + // need to have an interesting constructor to bother even checking this stuff + if (ctors.Length == 0 || (ctors.Length == 1 && ctors[0].GetParameters().Length == 0)) return null; + + MemberInfo[] fieldsPropsUnfiltered = Helpers.GetInstanceFieldsAndProperties(type, true); + BasicList memberList = new BasicList(); + // for most types we'll enforce that you need readonly, because that is what protobuf-net + // always did historically; but: if you smell so much like a Tuple that it is *in your name*, + // we'll let you past that + bool demandReadOnly = type.Name.IndexOf("Tuple", StringComparison.OrdinalIgnoreCase) < 0; + for (int i = 0; i < fieldsPropsUnfiltered.Length; i++) + { + if (fieldsPropsUnfiltered[i] is PropertyInfo prop) + { + if (!prop.CanRead) return null; // no use if can't read + if (demandReadOnly && prop.CanWrite && Helpers.GetSetMethod(prop, false, false) != null) return null; // don't allow a public set (need to allow non-public to handle Mono's KeyValuePair<,>) + memberList.Add(prop); + } + else + { + if (fieldsPropsUnfiltered[i] is FieldInfo field) + { + if (demandReadOnly && !field.IsInitOnly) return null; // all public fields must be readonly to be counted a tuple + memberList.Add(field); + } + } + } + if (memberList.Count == 0) + { + return null; + } + + MemberInfo[] members = new MemberInfo[memberList.Count]; + memberList.CopyTo(members, 0); + + int[] mapping = new int[members.Length]; + int found = 0; + ConstructorInfo result = null; + mappedMembers = new MemberInfo[mapping.Length]; + for (int i = 0; i < ctors.Length; i++) + { + ParameterInfo[] parameters = ctors[i].GetParameters(); + + if (parameters.Length != members.Length) continue; + + // reset the mappings to test + for (int j = 0; j < mapping.Length; j++) mapping[j] = -1; + + for (int j = 0; j < parameters.Length; j++) + { + for (int k = 0; k < members.Length; k++) + { + if (string.Compare(parameters[j].Name, members[k].Name, StringComparison.OrdinalIgnoreCase) != 0) continue; + Type memberType = Helpers.GetMemberType(members[k]); + if (memberType != parameters[j].ParameterType) continue; + + mapping[j] = k; + } + } + // did we map all? + bool notMapped = false; + for (int j = 0; j < mapping.Length; j++) + { + if (mapping[j] < 0) + { + notMapped = true; + break; + } + mappedMembers[j] = members[mapping[j]]; + } + + if (notMapped) continue; + found++; + result = ctors[i]; + + } + return found == 1 ? result : null; + } + + private static void CheckForCallback(MethodInfo method, AttributeMap[] attributes, string callbackTypeName, ref MethodInfo[] callbacks, int index) + { + for (int i = 0; i < attributes.Length; i++) + { + if (attributes[i].AttributeType.FullName == callbackTypeName) + { + if (callbacks == null) { callbacks = new MethodInfo[8]; } + else if (callbacks[index] != null) + { +#if COREFX || PROFILE259 + Type reflected = method.DeclaringType; +#else + Type reflected = method.ReflectedType; +#endif + throw new ProtoException("Duplicate " + callbackTypeName + " callbacks on " + reflected.FullName); + } + callbacks[index] = method; + } + } + } + private static bool HasFamily(AttributeFamily value, AttributeFamily required) + { + return (value & required) == required; + } + + private static ProtoMemberAttribute NormalizeProtoMember(TypeModel model, MemberInfo member, AttributeFamily family, bool forced, bool isEnum, BasicList partialMembers, int dataMemberOffset, bool inferByTagName, ref bool hasConflictingEnumValue, MemberInfo backingMember = null) + { + if (member == null || (family == AttributeFamily.None && !isEnum)) return null; // nix + int fieldNumber = int.MinValue, minAcceptFieldNumber = inferByTagName ? -1 : 1; + string name = null; + bool isPacked = false, ignore = false, done = false, isRequired = false, asReference = false, asReferenceHasValue = false, dynamicType = false, tagIsPinned = false, overwriteList = false; + DataFormat dataFormat = DataFormat.Default; + if (isEnum) forced = true; + AttributeMap[] attribs = AttributeMap.Create(model, member, true); + AttributeMap attrib; + + if (isEnum) + { + attrib = GetAttribute(attribs, "ProtoBuf.ProtoIgnoreAttribute"); + if (attrib != null) + { + ignore = true; + } + else + { + attrib = GetAttribute(attribs, "ProtoBuf.ProtoEnumAttribute"); +#if PORTABLE || CF || COREFX || PROFILE259 + fieldNumber = Convert.ToInt32(((FieldInfo)member).GetValue(null)); +#else + fieldNumber = Convert.ToInt32(((FieldInfo)member).GetRawConstantValue()); +#endif + if (attrib != null) + { + GetFieldName(ref name, attrib, nameof(ProtoEnumAttribute.Name)); + + if ((bool)Helpers.GetInstanceMethod(attrib.AttributeType +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + , nameof(ProtoEnumAttribute.HasValue)).Invoke(attrib.Target, null)) + { + if (attrib.TryGet(nameof(ProtoEnumAttribute.Value), out object tmp)) + { + if (fieldNumber != (int)tmp) + { + hasConflictingEnumValue = true; + } + fieldNumber = (int)tmp; + } + } + } + + } + done = true; + } + + if (!ignore && !done) // always consider ProtoMember + { + attrib = GetAttribute(attribs, "ProtoBuf.ProtoMemberAttribute"); + GetIgnore(ref ignore, attrib, attribs, "ProtoBuf.ProtoIgnoreAttribute"); + + if (!ignore && attrib != null) + { + GetFieldNumber(ref fieldNumber, attrib, "Tag"); + GetFieldName(ref name, attrib, "Name"); + GetFieldBoolean(ref isRequired, attrib, "IsRequired"); + GetFieldBoolean(ref isPacked, attrib, "IsPacked"); + GetFieldBoolean(ref overwriteList, attrib, "OverwriteList"); + GetDataFormat(ref dataFormat, attrib, "DataFormat"); + GetFieldBoolean(ref asReferenceHasValue, attrib, "AsReferenceHasValue", false); + + if (asReferenceHasValue) + { + asReferenceHasValue = GetFieldBoolean(ref asReference, attrib, "AsReference", true); + } + GetFieldBoolean(ref dynamicType, attrib, "DynamicType"); + done = tagIsPinned = fieldNumber > 0; // note minAcceptFieldNumber only applies to non-proto + } + + if (!done && partialMembers != null) + { + foreach (AttributeMap ppma in partialMembers) + { + if (ppma.TryGet("MemberName", out object tmp) && (string)tmp == member.Name) + { + GetFieldNumber(ref fieldNumber, ppma, "Tag"); + GetFieldName(ref name, ppma, "Name"); + GetFieldBoolean(ref isRequired, ppma, "IsRequired"); + GetFieldBoolean(ref isPacked, ppma, "IsPacked"); + GetFieldBoolean(ref overwriteList, attrib, "OverwriteList"); + GetDataFormat(ref dataFormat, ppma, "DataFormat"); + GetFieldBoolean(ref asReferenceHasValue, attrib, "AsReferenceHasValue", false); + + if (asReferenceHasValue) + { + asReferenceHasValue = GetFieldBoolean(ref asReference, ppma, "AsReference", true); + } + GetFieldBoolean(ref dynamicType, ppma, "DynamicType"); + if (done = tagIsPinned = fieldNumber > 0) break; // note minAcceptFieldNumber only applies to non-proto + } + } + } + } + + if (!ignore && !done && HasFamily(family, AttributeFamily.DataContractSerialier)) + { + attrib = GetAttribute(attribs, "System.Runtime.Serialization.DataMemberAttribute"); + if (attrib != null) + { + GetFieldNumber(ref fieldNumber, attrib, "Order"); + GetFieldName(ref name, attrib, "Name"); + GetFieldBoolean(ref isRequired, attrib, "IsRequired"); + done = fieldNumber >= minAcceptFieldNumber; + if (done) fieldNumber += dataMemberOffset; // dataMemberOffset only applies to DCS flags, to allow us to "bump" WCF by a notch + } + } + if (!ignore && !done && HasFamily(family, AttributeFamily.XmlSerializer)) + { + attrib = GetAttribute(attribs, "System.Xml.Serialization.XmlElementAttribute"); + if (attrib == null) attrib = GetAttribute(attribs, "System.Xml.Serialization.XmlArrayAttribute"); + GetIgnore(ref ignore, attrib, attribs, "System.Xml.Serialization.XmlIgnoreAttribute"); + if (attrib != null && !ignore) + { + GetFieldNumber(ref fieldNumber, attrib, "Order"); + GetFieldName(ref name, attrib, "ElementName"); + done = fieldNumber >= minAcceptFieldNumber; + } + } + if (!ignore && !done) + { + if (GetAttribute(attribs, "System.NonSerializedAttribute") != null) ignore = true; + } + if (ignore || (fieldNumber < minAcceptFieldNumber && !forced)) return null; + ProtoMemberAttribute result = new ProtoMemberAttribute(fieldNumber, forced || inferByTagName) + { + AsReference = asReference, + AsReferenceHasValue = asReferenceHasValue, + DataFormat = dataFormat, + DynamicType = dynamicType, + IsPacked = isPacked, + OverwriteList = overwriteList, + IsRequired = isRequired, + Name = string.IsNullOrEmpty(name) ? member.Name : name, + Member = member, + BackingMember = backingMember, + TagIsPinned = tagIsPinned + }; + return result; + } + + private ValueMember ApplyDefaultBehaviour(bool isEnum, ProtoMemberAttribute normalizedAttribute) + { + MemberInfo member; + if (normalizedAttribute == null || (member = normalizedAttribute.Member) == null) return null; // nix + + Type effectiveType = Helpers.GetMemberType(member); + + + Type itemType = null; + Type defaultType = null; + + // check for list types + ResolveListTypes(model, effectiveType, ref itemType, ref defaultType); + bool ignoreListHandling = false; + // but take it back if it is explicitly excluded + if (itemType != null) + { // looks like a list, but double check for IgnoreListHandling + int idx = model.FindOrAddAuto(effectiveType, false, true, false); + if (idx >= 0 && (ignoreListHandling = model[effectiveType].IgnoreListHandling)) + { + itemType = null; + defaultType = null; + } + } + AttributeMap[] attribs = AttributeMap.Create(model, member, true); + AttributeMap attrib; + + object defaultValue = null; + // implicit zero default + if (model.UseImplicitZeroDefaults) + { + switch (Helpers.GetTypeCode(effectiveType)) + { + case ProtoTypeCode.Boolean: defaultValue = false; break; + case ProtoTypeCode.Decimal: defaultValue = (decimal)0; break; + case ProtoTypeCode.Single: defaultValue = (float)0; break; + case ProtoTypeCode.Double: defaultValue = (double)0; break; + case ProtoTypeCode.Byte: defaultValue = (byte)0; break; + case ProtoTypeCode.Char: defaultValue = (char)0; break; + case ProtoTypeCode.Int16: defaultValue = (short)0; break; + case ProtoTypeCode.Int32: defaultValue = (int)0; break; + case ProtoTypeCode.Int64: defaultValue = (long)0; break; + case ProtoTypeCode.SByte: defaultValue = (sbyte)0; break; + case ProtoTypeCode.UInt16: defaultValue = (ushort)0; break; + case ProtoTypeCode.UInt32: defaultValue = (uint)0; break; + case ProtoTypeCode.UInt64: defaultValue = (ulong)0; break; + case ProtoTypeCode.TimeSpan: defaultValue = TimeSpan.Zero; break; + case ProtoTypeCode.Guid: defaultValue = Guid.Empty; break; + } + } + if ((attrib = GetAttribute(attribs, "System.ComponentModel.DefaultValueAttribute")) != null) + { + if (attrib.TryGet("Value", out object tmp)) defaultValue = tmp; + } + ValueMember vm = ((isEnum || normalizedAttribute.Tag > 0)) + ? new ValueMember(model, type, normalizedAttribute.Tag, member, effectiveType, itemType, defaultType, normalizedAttribute.DataFormat, defaultValue) + : null; + if (vm != null) + { + vm.BackingMember = normalizedAttribute.BackingMember; +#if COREFX || PROFILE259 + TypeInfo finalType = typeInfo; +#else + Type finalType = type; +#endif + PropertyInfo prop = Helpers.GetProperty(finalType, member.Name + "Specified", true); + MethodInfo getMethod = Helpers.GetGetMethod(prop, true, true); + if (getMethod == null || getMethod.IsStatic) prop = null; + if (prop != null) + { + vm.SetSpecified(getMethod, Helpers.GetSetMethod(prop, true, true)); + } + else + { + MethodInfo method = Helpers.GetInstanceMethod(finalType, "ShouldSerialize" + member.Name, Helpers.EmptyTypes); + if (method != null && method.ReturnType == model.MapType(typeof(bool))) + { + vm.SetSpecified(method, null); + } + } + if (!string.IsNullOrEmpty(normalizedAttribute.Name)) vm.SetName(normalizedAttribute.Name); + vm.IsPacked = normalizedAttribute.IsPacked; + vm.IsRequired = normalizedAttribute.IsRequired; + vm.OverwriteList = normalizedAttribute.OverwriteList; + if (normalizedAttribute.AsReferenceHasValue) + { + vm.AsReference = normalizedAttribute.AsReference; + } + vm.DynamicType = normalizedAttribute.DynamicType; + + vm.IsMap = ignoreListHandling ? false : vm.ResolveMapTypes(out var _, out var _, out var _); + if (vm.IsMap) // is it even *allowed* to be a map? + { + if ((attrib = GetAttribute(attribs, "ProtoBuf.ProtoMapAttribute")) != null) + { + if (attrib.TryGet(nameof(ProtoMapAttribute.DisableMap), out object tmp) && (bool)tmp) + { + vm.IsMap = false; + } + else + { + if (attrib.TryGet(nameof(ProtoMapAttribute.KeyFormat), out tmp)) vm.MapKeyFormat = (DataFormat)tmp; + if (attrib.TryGet(nameof(ProtoMapAttribute.ValueFormat), out tmp)) vm.MapValueFormat = (DataFormat)tmp; + } + } + } + + } + return vm; + } + + private static void GetDataFormat(ref DataFormat value, AttributeMap attrib, string memberName) + { + if ((attrib == null) || (value != DataFormat.Default)) return; + if (attrib.TryGet(memberName, out object obj) && obj != null) value = (DataFormat)obj; + } + + private static void GetIgnore(ref bool ignore, AttributeMap attrib, AttributeMap[] attribs, string fullName) + { + if (ignore || attrib == null) return; + ignore = GetAttribute(attribs, fullName) != null; + return; + } + + private static void GetFieldBoolean(ref bool value, AttributeMap attrib, string memberName) + { + GetFieldBoolean(ref value, attrib, memberName, true); + } + private static bool GetFieldBoolean(ref bool value, AttributeMap attrib, string memberName, bool publicOnly) + { + if (attrib == null) return false; + if (value) return true; + if (attrib.TryGet(memberName, publicOnly, out object obj) && obj != null) + { + value = (bool)obj; + return true; + } + return false; + } + + private static void GetFieldNumber(ref int value, AttributeMap attrib, string memberName) + { + if (attrib == null || value > 0) return; + if (attrib.TryGet(memberName, out object obj) && obj != null) value = (int)obj; + } + + private static void GetFieldName(ref string name, AttributeMap attrib, string memberName) + { + if (attrib == null || !string.IsNullOrEmpty(name)) return; + if (attrib.TryGet(memberName, out object obj) && obj != null) name = (string)obj; + } + + private static AttributeMap GetAttribute(AttributeMap[] attribs, string fullName) + { + for (int i = 0; i < attribs.Length; i++) + { + AttributeMap attrib = attribs[i]; + if (attrib != null && attrib.AttributeType.FullName == fullName) return attrib; + } + return null; + } + + /// + /// Adds a member (by name) to the MetaType + /// + public MetaType Add(int fieldNumber, string memberName) + { + AddField(fieldNumber, memberName, null, null, null); + return this; + } + + /// + /// Adds a member (by name) to the MetaType, returning the ValueMember rather than the fluent API. + /// This is otherwise identical to Add. + /// + public ValueMember AddField(int fieldNumber, string memberName) + { + return AddField(fieldNumber, memberName, null, null, null); + } + + /// + /// Gets or sets whether the type should use a parameterless constructor (the default), + /// or whether the type should skip the constructor completely. This option is not supported + /// on compact-framework. + /// + public bool UseConstructor + { // negated to have defaults as flat zero + get { return !HasFlag(OPTIONS_SkipConstructor); } + set { SetFlag(OPTIONS_SkipConstructor, !value, true); } + } + + /// + /// The concrete type to create when a new instance of this type is needed; this may be useful when dealing + /// with dynamic proxies, or with interface-based APIs + /// + public Type ConstructType + { + get { return constructType; } + set + { + ThrowIfFrozen(); + constructType = value; + } + } + + private Type constructType; + /// + /// Adds a member (by name) to the MetaType + /// + public MetaType Add(string memberName) + { + Add(GetNextFieldNumber(), memberName); + return this; + } + + Type surrogate; + /// + /// Performs serialization of this type via a surrogate; all + /// other serialization options are ignored and handled + /// by the surrogate's configuration. + /// + public void SetSurrogate(Type surrogateType) + { + if (surrogateType == type) surrogateType = null; + if (surrogateType != null) + { + // note that BuildSerializer checks the **CURRENT TYPE** is OK to be surrogated + if (surrogateType != null && Helpers.IsAssignableFrom(model.MapType(typeof(IEnumerable)), surrogateType)) + { + throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be used as a surrogate"); + } + } + ThrowIfFrozen(); + this.surrogate = surrogateType; + // no point in offering chaining; no options are respected + } + + internal MetaType GetSurrogateOrSelf() + { + if (surrogate != null) return model[surrogate]; + return this; + } + + internal MetaType GetSurrogateOrBaseOrSelf(bool deep) + { + if (surrogate != null) return model[surrogate]; + MetaType snapshot = this.baseType; + if (snapshot != null) + { + if (deep) + { + MetaType tmp; + do + { + tmp = snapshot; + snapshot = snapshot.baseType; + } while (snapshot != null); + return tmp; + } + return snapshot; + } + return this; + } + + private int GetNextFieldNumber() + { + int maxField = 0; + foreach (ValueMember member in fields) + { + if (member.FieldNumber > maxField) maxField = member.FieldNumber; + } + if (subTypes != null) + { + foreach (SubType subType in subTypes) + { + if (subType.FieldNumber > maxField) maxField = subType.FieldNumber; + } + } + return maxField + 1; + } + + /// + /// Adds a set of members (by name) to the MetaType + /// + public MetaType Add(params string[] memberNames) + { + if (memberNames == null) throw new ArgumentNullException("memberNames"); + int next = GetNextFieldNumber(); + for (int i = 0; i < memberNames.Length; i++) + { + Add(next++, memberNames[i]); + } + return this; + } + + /// + /// Adds a member (by name) to the MetaType + /// + public MetaType Add(int fieldNumber, string memberName, object defaultValue) + { + AddField(fieldNumber, memberName, null, null, defaultValue); + return this; + } + + /// + /// Adds a member (by name) to the MetaType, including an itemType and defaultType for representing lists + /// + public MetaType Add(int fieldNumber, string memberName, Type itemType, Type defaultType) + { + AddField(fieldNumber, memberName, itemType, defaultType, null); + return this; + } + + /// + /// Adds a member (by name) to the MetaType, including an itemType and defaultType for representing lists, returning the ValueMember rather than the fluent API. + /// This is otherwise identical to Add. + /// + public ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType) + { + return AddField(fieldNumber, memberName, itemType, defaultType, null); + } + + private ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue) + { + MemberInfo mi = null; +#if PROFILE259 + mi = Helpers.IsEnum(type) ? type.GetTypeInfo().GetDeclaredField(memberName) : Helpers.GetInstanceMember(type.GetTypeInfo(), memberName); + +#else + MemberInfo[] members = type.GetMember(memberName, Helpers.IsEnum(type) ? BindingFlags.Static | BindingFlags.Public : BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (members != null && members.Length == 1) mi = members[0]; +#endif + if (mi == null) throw new ArgumentException("Unable to determine member: " + memberName, "memberName"); + + Type miType; + PropertyInfo pi = null; + FieldInfo fi = null; +#if PORTABLE || COREFX || PROFILE259 + pi = mi as PropertyInfo; + if (pi == null) + { + fi = mi as FieldInfo; + if (fi == null) + { + throw new NotSupportedException(mi.GetType().Name); + } + else + { + miType = fi.FieldType; + } + } + else + { + miType = pi.PropertyType; + } +#else + switch (mi.MemberType) + { + case MemberTypes.Field: + fi = (FieldInfo)mi; + miType = fi.FieldType; break; + case MemberTypes.Property: + pi = (PropertyInfo)mi; + miType = pi.PropertyType; break; + default: + throw new NotSupportedException(mi.MemberType.ToString()); + } +#endif + ResolveListTypes(model, miType, ref itemType, ref defaultType); + + MemberInfo backingField = null; + if (pi?.CanWrite == false) + { + string name = $"<{((PropertyInfo)mi).Name}>k__BackingField"; +#if PROFILE259 + var backingMembers = type.GetTypeInfo().DeclaredMembers; + var memberInfos = backingMembers as MemberInfo[] ?? backingMembers.ToArray(); + if (memberInfos.Count() == 1) + { + MemberInfo first = memberInfos.FirstOrDefault(); + if (first is FieldInfo) + { + backingField = first; + } + } +#else + var backingMembers = type.GetMember($"<{((PropertyInfo)mi).Name}>k__BackingField", Helpers.IsEnum(type) ? BindingFlags.Static | BindingFlags.Public : BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (backingMembers != null && backingMembers.Length == 1 && (backingMembers[0] as FieldInfo) != null) + backingField = backingMembers[0]; +#endif + } + ValueMember newField = new ValueMember(model, type, fieldNumber, backingField ?? mi, miType, itemType, defaultType, DataFormat.Default, defaultValue); + if (backingField != null) + newField.SetName(mi.Name); + Add(newField); + return newField; + } + + internal static void ResolveListTypes(TypeModel model, Type type, ref Type itemType, ref Type defaultType) + { + if (type == null) return; + // handle arrays + if (type.IsArray) + { + if (type.GetArrayRank() != 1) + { + throw new NotSupportedException("Multi-dimensional arrays are not supported"); + } + itemType = type.GetElementType(); + if (itemType == model.MapType(typeof(byte))) + { + defaultType = itemType = null; + } + else + { + defaultType = type; + } + } + // handle lists + if (itemType == null) { itemType = TypeModel.GetListItemType(model, type); } + + // check for nested data (not allowed) + if (itemType != null) + { + Type nestedItemType = null, nestedDefaultType = null; + ResolveListTypes(model, itemType, ref nestedItemType, ref nestedDefaultType); + if (nestedItemType != null) + { + throw TypeModel.CreateNestedListsNotSupported(type); + } + } + + if (itemType != null && defaultType == null) + { +#if COREFX || PROFILE259 + TypeInfo typeInfo = type.GetTypeInfo(); + if (typeInfo.IsClass && !typeInfo.IsAbstract && Helpers.GetConstructor(typeInfo, Helpers.EmptyTypes, true) != null) +#else + if (type.IsClass && !type.IsAbstract && Helpers.GetConstructor(type, Helpers.EmptyTypes, true) != null) +#endif + { + defaultType = type; + } + if (defaultType == null) + { +#if COREFX || PROFILE259 + if (typeInfo.IsInterface) +#else + if (type.IsInterface) +#endif + { + + Type[] genArgs; +#if COREFX || PROFILE259 + if (typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>) + && itemType == typeof(System.Collections.Generic.KeyValuePair<,>).MakeGenericType(genArgs = typeInfo.GenericTypeArguments)) +#else + if (type.IsGenericType && type.GetGenericTypeDefinition() == model.MapType(typeof(System.Collections.Generic.IDictionary<,>)) + && itemType == model.MapType(typeof(System.Collections.Generic.KeyValuePair<,>)).MakeGenericType(genArgs = type.GetGenericArguments())) +#endif + { + defaultType = model.MapType(typeof(System.Collections.Generic.Dictionary<,>)).MakeGenericType(genArgs); + } + else + { + defaultType = model.MapType(typeof(System.Collections.Generic.List<>)).MakeGenericType(itemType); + } + } + } + // verify that the default type is appropriate + if (defaultType != null && !Helpers.IsAssignableFrom(type, defaultType)) { defaultType = null; } + } + } + + private void Add(ValueMember member) + { + int opaqueToken = 0; + try + { + model.TakeLock(ref opaqueToken); + ThrowIfFrozen(); + fields.Add(member); + } + finally + { + model.ReleaseLock(opaqueToken); + } + } + + /// + /// Returns the ValueMember that matchs a given field number, or null if not found + /// + public ValueMember this[int fieldNumber] + { + get + { + foreach (ValueMember member in fields) + { + if (member.FieldNumber == fieldNumber) return member; + } + return null; + } + } + /// + /// Returns the ValueMember that matchs a given member (property/field), or null if not found + /// + public ValueMember this[MemberInfo member] + { + get + { + if (member == null) return null; + foreach (ValueMember x in fields) + { + if (x.Member == member || x.BackingMember == member) return x; + } + return null; + } + } + private readonly BasicList fields = new BasicList(); + + /// + /// Returns the ValueMember instances associated with this type + /// + public ValueMember[] GetFields() + { + ValueMember[] arr = new ValueMember[fields.Count]; + fields.CopyTo(arr, 0); + Array.Sort(arr, ValueMember.Comparer.Default); + return arr; + } + + /// + /// Returns the SubType instances associated with this type + /// + public SubType[] GetSubtypes() + { + if (subTypes == null || subTypes.Count == 0) return new SubType[0]; + SubType[] arr = new SubType[subTypes.Count]; + subTypes.CopyTo(arr, 0); + Array.Sort(arr, SubType.Comparer.Default); + return arr; + } + + internal IEnumerable GetAllGenericArguments() + { + return GetAllGenericArguments(type); + } + + private static IEnumerable GetAllGenericArguments(Type type) + { + +#if PROFILE259 + var genericArguments = type.GetGenericTypeDefinition().GenericTypeArguments; +#else + var genericArguments = type.GetGenericArguments(); +#endif + foreach (var arg in genericArguments) + { + yield return arg; + foreach (var inner in GetAllGenericArguments(arg)) + { + yield return inner; + } + } + } + +#if FEAT_COMPILER + /// + /// Compiles the serializer for this type; this is *not* a full + /// standalone compile, but can significantly boost performance + /// while allowing additional types to be added. + /// + /// An in-place compile can access non-public types / members + public void CompileInPlace() + { + serializer = CompiledSerializer.Wrap(Serializer, model); + } +#endif + + internal bool IsDefined(int fieldNumber) + { + foreach (ValueMember field in fields) + { + if (field.FieldNumber == fieldNumber) return true; + } + return false; + } + + internal int GetKey(bool demand, bool getBaseKey) + { + return model.GetKey(type, demand, getBaseKey); + } + + internal EnumSerializer.EnumPair[] GetEnumMap() + { + if (HasFlag(OPTIONS_EnumPassThru)) return null; + EnumSerializer.EnumPair[] result = new EnumSerializer.EnumPair[fields.Count]; + for (int i = 0; i < result.Length; i++) + { + ValueMember member = (ValueMember)fields[i]; + int wireValue = member.FieldNumber; + object value = member.GetRawEnumValue(); + result[i] = new EnumSerializer.EnumPair(wireValue, value, member.MemberType); + } + return result; + } + + /// + /// Gets or sets a value indicating that an enum should be treated directly as an int/short/etc, rather + /// than enforcing .proto enum rules. This is useful *in particul* for [Flags] enums. + /// + public bool EnumPassthru + { + get { return HasFlag(OPTIONS_EnumPassThru); } + set { SetFlag(OPTIONS_EnumPassThru, value, true); } + } + + /// + /// Gets or sets a value indicating that this type should NOT be treated as a list, even if it has + /// familiar list-like characteristics (enumerable, add, etc) + /// + public bool IgnoreListHandling + { + get { return HasFlag(OPTIONS_IgnoreListHandling); } + set { SetFlag(OPTIONS_IgnoreListHandling, value, true); } + } + + internal bool Pending + { + get { return HasFlag(OPTIONS_Pending); } + set { SetFlag(OPTIONS_Pending, value, false); } + } + + private const ushort + OPTIONS_Pending = 1, + OPTIONS_EnumPassThru = 2, + OPTIONS_Frozen = 4, + OPTIONS_PrivateOnApi = 8, + OPTIONS_SkipConstructor = 16, + OPTIONS_AsReferenceDefault = 32, + OPTIONS_AutoTuple = 64, + OPTIONS_IgnoreListHandling = 128, + OPTIONS_IsGroup = 256; + + private volatile ushort flags; + private bool HasFlag(ushort flag) { return (flags & flag) == flag; } + private void SetFlag(ushort flag, bool value, bool throwIfFrozen) + { + if (throwIfFrozen && HasFlag(flag) != value) + { + ThrowIfFrozen(); + } + if (value) + flags |= flag; + else + flags = (ushort)(flags & ~flag); + } + + internal static MetaType GetRootType(MetaType source) + { + while (source.serializer != null) + { + MetaType tmp = source.baseType; + if (tmp == null) return source; + source = tmp; // else loop until we reach something that isn't generated, or is the root + } + + // now we get into uncertain territory + RuntimeTypeModel model = source.model; + int opaqueToken = 0; + try + { + model.TakeLock(ref opaqueToken); + + MetaType tmp; + while ((tmp = source.baseType) != null) source = tmp; + return source; + + } + finally + { + model.ReleaseLock(opaqueToken); + } + } + + internal bool IsPrepared() + { +#if FEAT_COMPILER + return serializer is CompiledSerializer; +#else + return false; +#endif + } + + internal IEnumerable Fields => this.fields; + + internal static StringBuilder NewLine(StringBuilder builder, int indent) + { + return Helpers.AppendLine(builder).Append(' ', indent * 3); + } + + internal bool IsAutoTuple => HasFlag(OPTIONS_AutoTuple); + + /// + /// Indicates whether this type should always be treated as a "group" (rather than a string-prefixed sub-message) + /// + public bool IsGroup + { + get { return HasFlag(OPTIONS_IsGroup); } + set { SetFlag(OPTIONS_IsGroup, value, true); } + } + + internal void WriteSchema(StringBuilder builder, int indent, ref RuntimeTypeModel.CommonImports imports, ProtoSyntax syntax) + { + if (surrogate != null) return; // nothing to write + + ValueMember[] fieldsArr = new ValueMember[fields.Count]; + fields.CopyTo(fieldsArr, 0); + Array.Sort(fieldsArr, ValueMember.Comparer.Default); + + if (IsList) + { + string itemTypeName = model.GetSchemaTypeName(TypeModel.GetListItemType(model, type), DataFormat.Default, false, false, ref imports); + NewLine(builder, indent).Append("message ").Append(GetSchemaTypeName()).Append(" {"); + NewLine(builder, indent + 1).Append("repeated ").Append(itemTypeName).Append(" items = 1;"); + NewLine(builder, indent).Append('}'); + } + else if (IsAutoTuple) + { // key-value-pair etc + + if (ResolveTupleConstructor(type, out MemberInfo[] mapping) != null) + { + NewLine(builder, indent).Append("message ").Append(GetSchemaTypeName()).Append(" {"); + for (int i = 0; i < mapping.Length; i++) + { + Type effectiveType; + if (mapping[i] is PropertyInfo property) + { + effectiveType = property.PropertyType; + } + else if (mapping[i] is FieldInfo field) + { + effectiveType = field.FieldType; + } + else + { + throw new NotSupportedException("Unknown member type: " + mapping[i].GetType().Name); + } + NewLine(builder, indent + 1).Append(syntax == ProtoSyntax.Proto2 ? "optional " : "").Append(model.GetSchemaTypeName(effectiveType, DataFormat.Default, false, false, ref imports).Replace('.', '_')) + .Append(' ').Append(mapping[i].Name).Append(" = ").Append(i + 1).Append(';'); + } + NewLine(builder, indent).Append('}'); + } + } + else if (Helpers.IsEnum(type)) + { + NewLine(builder, indent).Append("enum ").Append(GetSchemaTypeName()).Append(" {"); + if (fieldsArr.Length == 0 && EnumPassthru) + { + if (type +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif +.IsDefined(model.MapType(typeof(FlagsAttribute)), false)) + { + NewLine(builder, indent + 1).Append("// this is a composite/flags enumeration"); + } + else + { + NewLine(builder, indent + 1).Append("// this enumeration will be passed as a raw value"); + } + foreach (FieldInfo field in +#if PROFILE259 + type.GetRuntimeFields() +#else + type.GetFields() +#endif + + ) + { + if (field.IsStatic && field.IsLiteral) + { + object enumVal; +#if PORTABLE || CF || NETSTANDARD1_3 || NETSTANDARD1_4 || PROFILE259 || UAP + enumVal = Convert.ChangeType(field.GetValue(null), Enum.GetUnderlyingType(field.FieldType), System.Globalization.CultureInfo.InvariantCulture); +#else + enumVal = field.GetRawConstantValue(); +#endif + NewLine(builder, indent + 1).Append(field.Name).Append(" = ").Append(enumVal).Append(";"); + } + } + + } + else + { + Dictionary countByField = new Dictionary(fieldsArr.Length); + bool needsAlias = false; + foreach (var field in fieldsArr) + { + if (countByField.ContainsKey(field.FieldNumber)) + { // no point actually counting; that's enough to know we have a problem + needsAlias = true; + break; + } + countByField.Add(field.FieldNumber, 1); + } + if (needsAlias) + { // duplicated value requires allow_alias + NewLine(builder, indent + 1).Append("option allow_alias = true;"); + } + + bool haveWrittenZero = false; + // write zero values **first** + foreach (ValueMember member in fieldsArr) + { + if (member.FieldNumber == 0) + { + NewLine(builder, indent + 1).Append(member.Name).Append(" = ").Append(member.FieldNumber).Append(';'); + haveWrittenZero = true; + } + } + if (syntax == ProtoSyntax.Proto3 && !haveWrittenZero) + { + NewLine(builder, indent + 1).Append("ZERO = 0; // proto3 requires a zero value as the first item (it can be named anything)"); + } + // note array is already sorted, so zero would already be first + foreach (ValueMember member in fieldsArr) + { + if (member.FieldNumber == 0) continue; + NewLine(builder, indent + 1).Append(member.Name).Append(" = ").Append(member.FieldNumber).Append(';'); + } + } + NewLine(builder, indent).Append('}'); + } + else + { + NewLine(builder, indent).Append("message ").Append(GetSchemaTypeName()).Append(" {"); + foreach (ValueMember member in fieldsArr) + { + string schemaTypeName; + bool hasOption = false; + if (member.IsMap) + { + member.ResolveMapTypes(out var _, out var keyType, out var valueType); + + var keyTypeName = model.GetSchemaTypeName(keyType, member.MapKeyFormat, false, false, ref imports); + schemaTypeName = model.GetSchemaTypeName(valueType, member.MapKeyFormat, member.AsReference, member.DynamicType, ref imports); + NewLine(builder, indent + 1).Append("map<").Append(keyTypeName).Append(",").Append(schemaTypeName).Append("> ") + .Append(member.Name).Append(" = ").Append(member.FieldNumber).Append(";"); + } + else + { + string ordinality = member.ItemType != null ? "repeated " : (syntax == ProtoSyntax.Proto2 ? (member.IsRequired ? "required " : "optional ") : ""); + NewLine(builder, indent + 1).Append(ordinality); + if (member.DataFormat == DataFormat.Group) builder.Append("group "); + schemaTypeName = member.GetSchemaTypeName(true, ref imports); + builder.Append(schemaTypeName).Append(" ") + .Append(member.Name).Append(" = ").Append(member.FieldNumber); + + if (syntax == ProtoSyntax.Proto2 && member.DefaultValue != null && member.IsRequired == false) + { + if (member.DefaultValue is string) + { + AddOption(builder, ref hasOption).Append("default = \"").Append(member.DefaultValue).Append("\""); + } + else if (member.DefaultValue is TimeSpan) + { + // ignore + } + else if (member.DefaultValue is bool) + { // need to be lower case (issue 304) + AddOption(builder, ref hasOption).Append((bool)member.DefaultValue ? "default = true" : "default = false"); + } + else + { + AddOption(builder, ref hasOption).Append("default = ").Append(member.DefaultValue); + } + } + if (CanPack(member.ItemType)) + { + if (syntax == ProtoSyntax.Proto2) + { + if (member.IsPacked) AddOption(builder, ref hasOption).Append("packed = true"); // disabled by default + } + else + { + if (!member.IsPacked) AddOption(builder, ref hasOption).Append("packed = false"); // enabled by default + } + } + if (member.AsReference) + { + imports |= RuntimeTypeModel.CommonImports.Protogen; + AddOption(builder, ref hasOption).Append("(.protobuf_net.fieldopt).asRef = true"); + } + if (member.DynamicType) + { + imports |= RuntimeTypeModel.CommonImports.Protogen; + AddOption(builder, ref hasOption).Append("(.protobuf_net.fieldopt).dynamicType = true"); + } + CloseOption(builder, ref hasOption).Append(';'); + if (syntax != ProtoSyntax.Proto2 && member.DefaultValue != null && !member.IsRequired) + { + if (IsImplicitDefault(member.DefaultValue)) + { + // don't emit; we're good + } + else + { + builder.Append(" // default value could not be applied: ").Append(member.DefaultValue); + } + } + } + if (schemaTypeName == ".bcl.NetObjectProxy" && member.AsReference && !member.DynamicType) // we know what it is; tell the user + { + builder.Append(" // reference-tracked ").Append(member.GetSchemaTypeName(false, ref imports)); + } + } + if (subTypes != null && subTypes.Count != 0) + { + SubType[] subTypeArr = new SubType[subTypes.Count]; + subTypes.CopyTo(subTypeArr, 0); + Array.Sort(subTypeArr, SubType.Comparer.Default); + string[] fieldNames = new string[subTypeArr.Length]; + for(int i = 0; i < subTypeArr.Length;i++) + fieldNames[i] = subTypeArr[i].DerivedType.GetSchemaTypeName(); + + string fieldName = "subtype"; + while (Array.IndexOf(fieldNames, fieldName) >= 0) + fieldName = "_" + fieldName; + + NewLine(builder, indent + 1).Append("oneof ").Append(fieldName).Append(" {"); + for(int i = 0; i < subTypeArr.Length; i++) + { + var subTypeName = fieldNames[i]; + NewLine(builder, indent + 2).Append(subTypeName) + .Append(" ").Append(subTypeName).Append(" = ").Append(subTypeArr[i].FieldNumber).Append(';'); + } + NewLine(builder, indent + 1).Append("}"); + } + NewLine(builder, indent).Append('}'); + } + } + + private static StringBuilder AddOption(StringBuilder builder, ref bool hasOption) + { + if (hasOption) + return builder.Append(", "); + hasOption = true; + return builder.Append(" ["); + } + + private static StringBuilder CloseOption(StringBuilder builder, ref bool hasOption) + { + if (hasOption) + { + hasOption = false; + return builder.Append("]"); + } + return builder; + } + + private static bool IsImplicitDefault(object value) + { + try + { + if (value == null) return false; + switch (Helpers.GetTypeCode(value.GetType())) + { + case ProtoTypeCode.Boolean: return ((bool)value) == false; + case ProtoTypeCode.Byte: return ((byte)value) == (byte)0; + case ProtoTypeCode.Char: return ((char)value) == (char)0; + case ProtoTypeCode.DateTime: return ((DateTime)value) == default; + case ProtoTypeCode.Decimal: return ((decimal)value) == 0M; + case ProtoTypeCode.Double: return ((double)value) == (double)0; + case ProtoTypeCode.Int16: return ((short)value) == (short)0; + case ProtoTypeCode.Int32: return ((int)value) == (int)0; + case ProtoTypeCode.Int64: return ((long)value) == (long)0; + case ProtoTypeCode.SByte: return ((sbyte)value) == (sbyte)0; + case ProtoTypeCode.Single: return ((float)value) == (float)0; + case ProtoTypeCode.String: return ((string)value) == ""; + case ProtoTypeCode.TimeSpan: return ((TimeSpan)value) == TimeSpan.Zero; + case ProtoTypeCode.UInt16: return ((ushort)value) == (ushort)0; + case ProtoTypeCode.UInt32: return ((uint)value) == (uint)0; + case ProtoTypeCode.UInt64: return ((ulong)value) == (ulong)0; + } + } + catch { } + return false; + } + + private static bool CanPack(Type type) + { + if (type == null) return false; + switch (Helpers.GetTypeCode(type)) + { + case ProtoTypeCode.Boolean: + case ProtoTypeCode.Byte: + case ProtoTypeCode.Char: + case ProtoTypeCode.Double: + case ProtoTypeCode.Int16: + case ProtoTypeCode.Int32: + case ProtoTypeCode.Int64: + case ProtoTypeCode.SByte: + case ProtoTypeCode.Single: + case ProtoTypeCode.UInt16: + case ProtoTypeCode.UInt32: + case ProtoTypeCode.UInt64: + return true; + } + return false; + } + + /// + /// Apply a shift to all fields (and sub-types) on this type + /// + /// The change in field number to apply + /// The resultant field numbers must still all be considered valid +#if !(NETSTANDARD1_0 || NETSTANDARD1_3 || UAP) + [System.ComponentModel.Browsable(false)] +#endif + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] + public void ApplyFieldOffset(int offset) + { + if (Helpers.IsEnum(type)) throw new InvalidOperationException("Cannot apply field-offset to an enum"); + if (offset == 0) return; // nothing to do + int opaqueToken = 0; + try + { + model.TakeLock(ref opaqueToken); + ThrowIfFrozen(); + + if (fields != null) + { + foreach(ValueMember field in fields) + AssertValidFieldNumber(field.FieldNumber + offset); + } + if (subTypes != null) + { + foreach (SubType subType in subTypes) + AssertValidFieldNumber(subType.FieldNumber + offset); + } + + // we've checked the ranges are all OK; since we're moving everything, we can't overlap ourselves + // so: we can just move + if (fields != null) + { + foreach (ValueMember field in fields) + field.FieldNumber += offset; + } + if (subTypes != null) + { + foreach (SubType subType in subTypes) + subType.FieldNumber += offset; + } + } + finally + { + model.ReleaseLock(opaqueToken); + } + } + + internal static void AssertValidFieldNumber(int fieldNumber) + { + if (fieldNumber < 1) throw new ArgumentOutOfRangeException(nameof(fieldNumber)); + } + } +} +#endif diff --git a/Runtime/Protobuf-net/Meta/MetaType.cs.meta b/Runtime/Protobuf-net/Meta/MetaType.cs.meta new file mode 100644 index 0000000..edc2cad --- /dev/null +++ b/Runtime/Protobuf-net/Meta/MetaType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 170c607ac9d3b9346a8f4197e9e4d86a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/ProtoSyntax.cs b/Runtime/Protobuf-net/Meta/ProtoSyntax.cs new file mode 100644 index 0000000..ab90d5b --- /dev/null +++ b/Runtime/Protobuf-net/Meta/ProtoSyntax.cs @@ -0,0 +1,17 @@ +namespace ProtoBuf.Meta +{ + /// + /// Indiate the variant of the protobuf .proto DSL syntax to use + /// + public enum ProtoSyntax + { + /// + /// https://developers.google.com/protocol-buffers/docs/proto + /// + Proto2 = 0, + /// + /// https://developers.google.com/protocol-buffers/docs/proto3 + /// + Proto3 = 1, + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/ProtoSyntax.cs.meta b/Runtime/Protobuf-net/Meta/ProtoSyntax.cs.meta new file mode 100644 index 0000000..2320025 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/ProtoSyntax.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8df2b30e0bc1f274a8170e86c9d08f96 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs b/Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs new file mode 100644 index 0000000..05dfcf1 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs @@ -0,0 +1,2036 @@ +#if !NO_RUNTIME +using System; +using System.Collections; +using System.Text; +using System.Reflection; +#if FEAT_COMPILER +using System.Reflection.Emit; +#endif + +using ProtoBuf.Serializers; +using System.Threading; +using System.IO; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ProtoBuf.Meta +{ + /// + /// Provides protobuf serialization support for a number of types that can be defined at runtime + /// + public sealed class RuntimeTypeModel : TypeModel + { + private ushort options; + private const ushort + OPTIONS_InferTagFromNameDefault = 1, + OPTIONS_IsDefaultModel = 2, + OPTIONS_Frozen = 4, + OPTIONS_AutoAddMissingTypes = 8, +#if FEAT_COMPILER + OPTIONS_AutoCompile = 16, +#endif + OPTIONS_UseImplicitZeroDefaults = 32, + OPTIONS_AllowParseableTypes = 64, + OPTIONS_AutoAddProtoContractTypesOnly = 128, + OPTIONS_IncludeDateTimeKind = 256, + OPTIONS_DoNotInternStrings = 512; + + private bool GetOption(ushort option) + { + return (options & option) == option; + } + + private void SetOption(ushort option, bool value) + { + if (value) options |= option; + else options &= (ushort)~option; + } + + /// + /// Global default that + /// enables/disables automatic tag generation based on the existing name / order + /// of the defined members. See + /// for usage and important warning / explanation. + /// You must set the global default before attempting to serialize/deserialize any + /// impacted type. + /// + public bool InferTagFromNameDefault + { + get { return GetOption(OPTIONS_InferTagFromNameDefault); } + set { SetOption(OPTIONS_InferTagFromNameDefault, value); } + } + + /// + /// Global default that determines whether types are considered serializable + /// if they have [DataContract] / [XmlType]. With this enabled, ONLY + /// types marked as [ProtoContract] are added automatically. + /// + public bool AutoAddProtoContractTypesOnly + { + get { return GetOption(OPTIONS_AutoAddProtoContractTypesOnly); } + set { SetOption(OPTIONS_AutoAddProtoContractTypesOnly, value); } + } + + /// + /// Global switch that enables or disables the implicit + /// handling of "zero defaults"; meanning: if no other default is specified, + /// it assumes bools always default to false, integers to zero, etc. + /// + /// If this is disabled, no such assumptions are made and only *explicit* + /// default values are processed. This is enabled by default to + /// preserve similar logic to v1. + /// + public bool UseImplicitZeroDefaults + { + get { return GetOption(OPTIONS_UseImplicitZeroDefaults); } + set + { + if (!value && GetOption(OPTIONS_IsDefaultModel)) + { + throw new InvalidOperationException("UseImplicitZeroDefaults cannot be disabled on the default model"); + } + SetOption(OPTIONS_UseImplicitZeroDefaults, value); + } + } + + /// + /// Global switch that determines whether types with a .ToString() and a Parse(string) + /// should be serialized as strings. + /// + public bool AllowParseableTypes + { + get { return GetOption(OPTIONS_AllowParseableTypes); } + set { SetOption(OPTIONS_AllowParseableTypes, value); } + } + + /// + /// Global switch that determines whether DateTime serialization should include the Kind of the date/time. + /// + public bool IncludeDateTimeKind + { + get { return GetOption(OPTIONS_IncludeDateTimeKind); } + set { SetOption(OPTIONS_IncludeDateTimeKind, value); } + } + + /// + /// Global switch that determines whether a single instance of the same string should be used during deserialization. + /// + /// Note this does not use the global .NET string interner + public bool InternStrings + { + get { return !GetOption(OPTIONS_DoNotInternStrings); } + set { SetOption(OPTIONS_DoNotInternStrings, !value); } + } + + /// + /// Should the Kind be included on date/time values? + /// + protected internal override bool SerializeDateTimeKind() + { + return GetOption(OPTIONS_IncludeDateTimeKind); + } + + private sealed class Singleton + { + private Singleton() { } + internal static readonly RuntimeTypeModel Value = new RuntimeTypeModel(true); + } + + /// + /// The default model, used to support ProtoBuf.Serializer + /// + public static RuntimeTypeModel Default => Singleton.Value; + + /// + /// Returns a sequence of the Type instances that can be + /// processed by this model. + /// + public IEnumerable GetTypes() => types; + + /// + /// Suggest a .proto definition for the given type + /// + /// The type to generate a .proto definition for, or null to generate a .proto that represents the entire model + /// The .proto definition as a string + /// The .proto syntax to use + public override string GetSchema(Type type, ProtoSyntax syntax) + { + BasicList requiredTypes = new BasicList(); + MetaType primaryType = null; + bool isInbuiltType = false; + if (type == null) + { // generate for the entire model + foreach (MetaType meta in types) + { + MetaType tmp = meta.GetSurrogateOrBaseOrSelf(false); + if (!requiredTypes.Contains(tmp)) + { // ^^^ note that the type might have been added as a descendent + requiredTypes.Add(tmp); + CascadeDependents(requiredTypes, tmp); + } + } + } + else + { + Type tmp = Helpers.GetUnderlyingType(type); + if (tmp != null) type = tmp; + + WireType defaultWireType; + isInbuiltType = (ValueMember.TryGetCoreSerializer(this, DataFormat.Default, type, out defaultWireType, false, false, false, false) != null); + if (!isInbuiltType) + { + //Agenerate just relative to the supplied type + int index = FindOrAddAuto(type, false, false, false); + if (index < 0) throw new ArgumentException("The type specified is not a contract-type", "type"); + + // get the required types + primaryType = ((MetaType)types[index]).GetSurrogateOrBaseOrSelf(false); + requiredTypes.Add(primaryType); + CascadeDependents(requiredTypes, primaryType); + } + } + + // use the provided type's namespace for the "package" + StringBuilder headerBuilder = new StringBuilder(); + string package = null; + + if (!isInbuiltType) + { + IEnumerable typesForNamespace = primaryType == null ? types : requiredTypes; + foreach (MetaType meta in typesForNamespace) + { + if (meta.IsList) continue; + string tmp = meta.Type.Namespace; + if (!string.IsNullOrEmpty(tmp)) + { + if (tmp.StartsWith("System.")) continue; + if (package == null) + { // haven't seen any suggestions yet + package = tmp; + } + else if (package == tmp) + { // that's fine; a repeat of the one we already saw + } + else + { // something else; have confliucting suggestions; abort + package = null; + break; + } + } + } + } + switch (syntax) + { + case ProtoSyntax.Proto2: + headerBuilder.AppendLine(@"syntax = ""proto2"";"); + break; + case ProtoSyntax.Proto3: + headerBuilder.AppendLine(@"syntax = ""proto3"";"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(syntax)); + } + + if (!string.IsNullOrEmpty(package)) + { + headerBuilder.Append("package ").Append(package).Append(';'); + Helpers.AppendLine(headerBuilder); + } + + var imports = CommonImports.None; + StringBuilder bodyBuilder = new StringBuilder(); + // sort them by schema-name + MetaType[] metaTypesArr = new MetaType[requiredTypes.Count]; + requiredTypes.CopyTo(metaTypesArr, 0); + Array.Sort(metaTypesArr, MetaType.Comparer.Default); + + // write the messages + if (isInbuiltType) + { + Helpers.AppendLine(bodyBuilder).Append("message ").Append(type.Name).Append(" {"); + MetaType.NewLine(bodyBuilder, 1).Append(syntax == ProtoSyntax.Proto2 ? "optional " : "").Append(GetSchemaTypeName(type, DataFormat.Default, false, false, ref imports)) + .Append(" value = 1;"); + Helpers.AppendLine(bodyBuilder).Append('}'); + } + else + { + for (int i = 0; i < metaTypesArr.Length; i++) + { + MetaType tmp = metaTypesArr[i]; + if (tmp.IsList && tmp != primaryType) continue; + tmp.WriteSchema(bodyBuilder, 0, ref imports, syntax); + } + } + if ((imports & CommonImports.Bcl) != 0) + { + headerBuilder.Append("import \"protobuf-net/bcl.proto\"; // schema for protobuf-net's handling of core .NET types"); + Helpers.AppendLine(headerBuilder); + } + if ((imports & CommonImports.Protogen) != 0) + { + headerBuilder.Append("import \"protobuf-net/protogen.proto\"; // custom protobuf-net options"); + Helpers.AppendLine(headerBuilder); + } + if ((imports & CommonImports.Timestamp) != 0) + { + headerBuilder.Append("import \"google/protobuf/timestamp.proto\";"); + Helpers.AppendLine(headerBuilder); + } + if ((imports & CommonImports.Duration) != 0) + { + headerBuilder.Append("import \"google/protobuf/duration.proto\";"); + Helpers.AppendLine(headerBuilder); + } + return Helpers.AppendLine(headerBuilder.Append(bodyBuilder)).ToString(); + } + [Flags] + internal enum CommonImports + { + None = 0, + Bcl = 1, + Timestamp = 2, + Duration = 4, + Protogen = 8 + } + private void CascadeDependents(BasicList list, MetaType metaType) + { + MetaType tmp; + if (metaType.IsList) + { + Type itemType = TypeModel.GetListItemType(this, metaType.Type); + TryGetCoreSerializer(list, itemType); + } + else + { + if (metaType.IsAutoTuple) + { + MemberInfo[] mapping; + if (MetaType.ResolveTupleConstructor(metaType.Type, out mapping) != null) + { + for (int i = 0; i < mapping.Length; i++) + { + Type type = null; + if (mapping[i] is PropertyInfo) type = ((PropertyInfo)mapping[i]).PropertyType; + else if (mapping[i] is FieldInfo) type = ((FieldInfo)mapping[i]).FieldType; + TryGetCoreSerializer(list, type); + } + } + } + else + { + foreach (ValueMember member in metaType.Fields) + { + Type type = member.ItemType; + if (member.IsMap) + { + member.ResolveMapTypes(out _, out _, out type); // don't need key-type + } + if (type == null) type = member.MemberType; + TryGetCoreSerializer(list, type); + } + } + foreach (var genericArgument in metaType.GetAllGenericArguments()) + { + TryGetCoreSerializer(list, genericArgument); + } + if (metaType.HasSubtypes) + { + foreach (SubType subType in metaType.GetSubtypes()) + { + tmp = subType.DerivedType.GetSurrogateOrSelf(); // note: exclude base-types! + if (!list.Contains(tmp)) + { + list.Add(tmp); + CascadeDependents(list, tmp); + } + } + } + tmp = metaType.BaseType; + if (tmp != null) tmp = tmp.GetSurrogateOrSelf(); // note: already walking base-types; exclude base + if (tmp != null && !list.Contains(tmp)) + { + list.Add(tmp); + CascadeDependents(list, tmp); + } + } + } + + private void TryGetCoreSerializer(BasicList list, Type itemType) + { + var coreSerializer = ValueMember.TryGetCoreSerializer(this, DataFormat.Default, itemType, out _, false, false, false, false); + if (coreSerializer != null) + { + return; + } + int index = FindOrAddAuto(itemType, false, false, false); + if (index < 0) + { + return; + } + var temp = ((MetaType)types[index]).GetSurrogateOrBaseOrSelf(false); + if (list.Contains(temp)) + { + return; + } + // could perhaps also implement as a queue, but this should work OK for sane models + list.Add(temp); + CascadeDependents(list, temp); + } + +#if !NO_RUNTIME + /// + /// Creates a new runtime model, to which the caller + /// can add support for a range of types. A model + /// can be used "as is", or can be compiled for + /// optimal performance. + /// + /// not used currently; this is for compatibility with v3 +#pragma warning disable IDE0060 // Remove unused parameter + public static RuntimeTypeModel Create(string name = null) +#pragma warning restore IDE0060 // Remove unused parameter + { + return new RuntimeTypeModel(false); + } +#endif + + private RuntimeTypeModel(bool isDefault) + { + AutoAddMissingTypes = true; + UseImplicitZeroDefaults = true; + SetOption(OPTIONS_IsDefaultModel, isDefault); +#if FEAT_COMPILER && !DEBUG + try + { + AutoCompile = EnableAutoCompile(); + } + catch { } // this is all kinds of brittle on things like UWP +#endif + } + +#if FEAT_COMPILER + [MethodImpl(MethodImplOptions.NoInlining)] + internal static bool EnableAutoCompile() + { + try + { + var dm = new DynamicMethod("CheckCompilerAvailable", typeof(bool), new Type[] { typeof(int) }); + var il = dm.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldc_I4, 42); + il.Emit(OpCodes.Ceq); + il.Emit(OpCodes.Ret); + var func = (Predicate)dm.CreateDelegate(typeof(Predicate)); + return func(42); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return false; + } + } +#endif + + /// + /// Obtains the MetaType associated with a given Type for the current model, + /// allowing additional configuration. + /// + public MetaType this[Type type] { get { return (MetaType)types[FindOrAddAuto(type, true, false, false)]; } } + + internal MetaType FindWithoutAdd(Type type) + { + // this list is thread-safe for reading + foreach (MetaType metaType in types) + { + if (metaType.Type == type) + { + if (metaType.Pending) WaitOnLock(metaType); + return metaType; + } + } + // if that failed, check for a proxy + Type underlyingType = ResolveProxies(type); + return underlyingType == null ? null : FindWithoutAdd(underlyingType); + } + + static readonly BasicList.MatchPredicate + MetaTypeFinder = new BasicList.MatchPredicate(MetaTypeFinderImpl), + BasicTypeFinder = new BasicList.MatchPredicate(BasicTypeFinderImpl); + + static bool MetaTypeFinderImpl(object value, object ctx) + { + return ((MetaType)value).Type == (Type)ctx; + } + + static bool BasicTypeFinderImpl(object value, object ctx) + { + return ((BasicType)value).Type == (Type)ctx; + } + + private void WaitOnLock(MetaType type) + { + int opaqueToken = 0; + try + { + TakeLock(ref opaqueToken); + } + finally + { + ReleaseLock(opaqueToken); + } + } + + BasicList basicTypes = new BasicList(); + + sealed class BasicType + { + private readonly Type type; + public Type Type => type; + private readonly IProtoSerializer serializer; + public IProtoSerializer Serializer => serializer; + + public BasicType(Type type, IProtoSerializer serializer) + { + this.type = type; + this.serializer = serializer; + } + } + internal IProtoSerializer TryGetBasicTypeSerializer(Type type) + { + int idx = basicTypes.IndexOf(BasicTypeFinder, type); + + if (idx >= 0) return ((BasicType)basicTypes[idx]).Serializer; + + lock (basicTypes) + { // don't need a full model lock for this + + // double-checked + idx = basicTypes.IndexOf(BasicTypeFinder, type); + if (idx >= 0) return ((BasicType)basicTypes[idx]).Serializer; + + MetaType.AttributeFamily family = MetaType.GetContractFamily(this, type, null); + IProtoSerializer ser = family == MetaType.AttributeFamily.None + ? ValueMember.TryGetCoreSerializer(this, DataFormat.Default, type, out WireType defaultWireType, false, false, false, false) + : null; + + if (ser != null) basicTypes.Add(new BasicType(type, ser)); + return ser; + } + } + + internal int FindOrAddAuto(Type type, bool demand, bool addWithContractOnly, bool addEvenIfAutoDisabled) + { + int key = types.IndexOf(MetaTypeFinder, type); + MetaType metaType; + + // the fast happy path: meta-types we've already seen + if (key >= 0) + { + metaType = (MetaType)types[key]; + if (metaType.Pending) + { + WaitOnLock(metaType); + } + return key; + } + + // the fast fail path: types that will never have a meta-type + bool shouldAdd = AutoAddMissingTypes || addEvenIfAutoDisabled; + + if (!Helpers.IsEnum(type) && TryGetBasicTypeSerializer(type) != null) + { + if (shouldAdd && !addWithContractOnly) throw MetaType.InbuiltType(type); + return -1; // this will never be a meta-type + } + + // otherwise: we don't yet know + + // check for proxy types + Type underlyingType = ResolveProxies(type); + if (underlyingType != null && underlyingType != type) + { + key = types.IndexOf(MetaTypeFinder, underlyingType); + type = underlyingType; // if new added, make it reflect the underlying type + } + + if (key < 0) + { + int opaqueToken = 0; + Type origType = type; + bool weAdded = false; + try + { + TakeLock(ref opaqueToken); + // try to recognise a few familiar patterns... + if ((metaType = RecogniseCommonTypes(type)) == null) + { // otherwise, check if it is a contract + MetaType.AttributeFamily family = MetaType.GetContractFamily(this, type, null); + if (family == MetaType.AttributeFamily.AutoTuple) + { + shouldAdd = addEvenIfAutoDisabled = true; // always add basic tuples, such as KeyValuePair + } + + if (!shouldAdd || ( + !Helpers.IsEnum(type) && addWithContractOnly && family == MetaType.AttributeFamily.None) + ) + { + if (demand) ThrowUnexpectedType(type); + return key; + } + metaType = Create(type); + } + + metaType.Pending = true; + + // double-checked + int winner = types.IndexOf(MetaTypeFinder, type); + if (winner < 0) + { + ThrowIfFrozen(); + key = types.Add(metaType); + weAdded = true; + } + else + { + key = winner; + } + if (weAdded) + { + metaType.ApplyDefaultBehaviour(); + metaType.Pending = false; + } + } + finally + { + ReleaseLock(opaqueToken); + if (weAdded) + { + ResetKeyCache(); + } + } + } + return key; + } + + private MetaType RecogniseCommonTypes(Type type) + { + // if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>)) + // { + // MetaType mt = new MetaType(this, type); + + // Type surrogate = typeof (KeyValuePairSurrogate<,>).MakeGenericType(type.GetGenericArguments()); + + // mt.SetSurrogate(surrogate); + // mt.IncludeSerializerMethod = false; + // mt.Freeze(); + + // MetaType surrogateMeta = (MetaType)types[FindOrAddAuto(surrogate, true, true, true)]; // this forcibly adds it if needed + // if(surrogateMeta.IncludeSerializerMethod) + // { // don't blindly set - it might be frozen + // surrogateMeta.IncludeSerializerMethod = false; + // } + // surrogateMeta.Freeze(); + // return mt; + // } + return null; + } + private MetaType Create(Type type) + { + ThrowIfFrozen(); + return new MetaType(this, type, defaultFactory); + } + + /// + /// Adds support for an additional type in this model, optionally + /// applying inbuilt patterns. If the type is already known to the + /// model, the existing type is returned **without** applying + /// any additional behaviour. + /// + /// Inbuilt patterns include: + /// [ProtoContract]/[ProtoMember(n)] + /// [DataContract]/[DataMember(Order=n)] + /// [XmlType]/[XmlElement(Order=n)] + /// [On{Des|S}erializ{ing|ed}] + /// ShouldSerialize*/*Specified + /// + /// The type to be supported + /// Whether to apply the inbuilt configuration patterns (via attributes etc), or + /// just add the type with no additional configuration (the type must then be manually configured). + /// The MetaType representing this type, allowing + /// further configuration. + public MetaType Add(Type type, bool applyDefaultBehaviour) + { + if (type == null) throw new ArgumentNullException("type"); + MetaType newType = FindWithoutAdd(type); + if (newType != null) return newType; // return existing + int opaqueToken = 0; + +#if COREFX || PROFILE259 + TypeInfo typeInfo = IntrospectionExtensions.GetTypeInfo(type); + if (typeInfo.IsInterface && MetaType.ienumerable.IsAssignableFrom(typeInfo) +#else + if (type.IsInterface && MapType(MetaType.ienumerable).IsAssignableFrom(type) +#endif + && GetListItemType(this, type) == null) + { + throw new ArgumentException("IEnumerable[] data cannot be used as a meta-type unless an Add method can be resolved"); + } + try + { + newType = RecogniseCommonTypes(type); + if (newType != null) + { + if (!applyDefaultBehaviour) + { + throw new ArgumentException( + "Default behaviour must be observed for certain types with special handling; " + type.FullName, + "applyDefaultBehaviour"); + } + // we should assume that type is fully configured, though; no need to re-run: + applyDefaultBehaviour = false; + } + if (newType == null) newType = Create(type); + newType.Pending = true; + TakeLock(ref opaqueToken); + // double checked + if (FindWithoutAdd(type) != null) throw new ArgumentException("Duplicate type", "type"); + ThrowIfFrozen(); + types.Add(newType); + if (applyDefaultBehaviour) { newType.ApplyDefaultBehaviour(); } + newType.Pending = false; + } + finally + { + ReleaseLock(opaqueToken); + ResetKeyCache(); + } + + return newType; + } + +#if FEAT_COMPILER + /// + /// Should serializers be compiled on demand? It may be useful + /// to disable this for debugging purposes. + /// + public bool AutoCompile + { + get { return GetOption(OPTIONS_AutoCompile); } + set { SetOption(OPTIONS_AutoCompile, value); } + } +#endif + /// + /// Should support for unexpected types be added automatically? + /// If false, an exception is thrown when unexpected types + /// are encountered. + /// + public bool AutoAddMissingTypes + { + get { return GetOption(OPTIONS_AutoAddMissingTypes); } + set + { + if (!value && GetOption(OPTIONS_IsDefaultModel)) + { + throw new InvalidOperationException("The default model must allow missing types"); + } + ThrowIfFrozen(); + SetOption(OPTIONS_AutoAddMissingTypes, value); + } + } + /// + /// Verifies that the model is still open to changes; if not, an exception is thrown + /// + private void ThrowIfFrozen() + { + if (GetOption(OPTIONS_Frozen)) throw new InvalidOperationException("The model cannot be changed once frozen"); + } + + /// + /// Prevents further changes to this model + /// + public void Freeze() + { + if (GetOption(OPTIONS_IsDefaultModel)) throw new InvalidOperationException("The default model cannot be frozen"); + SetOption(OPTIONS_Frozen, true); + } + + private readonly BasicList types = new BasicList(); + + /// + /// Provides the key that represents a given type in the current model. + /// + protected override int GetKeyImpl(Type type) + { + return GetKey(type, false, true); + } + + internal int GetKey(Type type, bool demand, bool getBaseKey) + { + Helpers.DebugAssert(type != null); + try + { + int typeIndex = FindOrAddAuto(type, demand, true, false); + if (typeIndex >= 0) + { + MetaType mt = (MetaType)types[typeIndex]; + if (getBaseKey) + { + mt = MetaType.GetRootType(mt); + typeIndex = FindOrAddAuto(mt.Type, true, true, false); + } + } + return typeIndex; + } + catch (NotSupportedException) + { + throw; // re-surface "as-is" + } + catch (Exception ex) + { + if (ex.Message.IndexOf(type.FullName) >= 0) throw; // already enough info + throw new ProtoException(ex.Message + " (" + type.FullName + ")", ex); + } + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream. + /// + /// Represents the type (including inheritance) to consider. + /// The existing instance to be serialized (cannot be null). + /// The destination stream to write to. + protected internal override void Serialize(int key, object value, ProtoWriter dest) + { + //Helpers.DebugWriteLine("Serialize", value); + ((MetaType)types[key]).Serializer.Write(value, dest); + } + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// Represents the type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + protected internal override object Deserialize(int key, object value, ProtoReader source) + { + //Helpers.DebugWriteLine("Deserialize", value); + IProtoSerializer ser = ((MetaType)types[key]).Serializer; + if (value == null && Helpers.IsValueType(ser.ExpectedType)) + { + if (ser.RequiresOldValue) value = Activator.CreateInstance(ser.ExpectedType); + return ser.Read(value, source); + } + else + { + return ser.Read(value, source); + } + } + +#if FEAT_COMPILER + // this is used by some unit-tests; do not remove + internal Compiler.ProtoSerializer GetSerializer(IProtoSerializer serializer, bool compiled) + { + if (serializer == null) throw new ArgumentNullException("serializer"); +#if FEAT_COMPILER + if (compiled) return Compiler.CompilerContext.BuildSerializer(serializer, this); +#endif + return new Compiler.ProtoSerializer(serializer.Write); + } + + /// + /// Compiles the serializers individually; this is *not* a full + /// standalone compile, but can significantly boost performance + /// while allowing additional types to be added. + /// + /// An in-place compile can access non-public types / members + public void CompileInPlace() + { + foreach (MetaType type in types) + { + type.CompileInPlace(); + } + } + +#endif + //internal override IProtoSerializer GetTypeSerializer(Type type) + //{ // this list is thread-safe for reading + // .Serializer; + //} + //internal override IProtoSerializer GetTypeSerializer(int key) + //{ // this list is thread-safe for reading + // MetaType type = (MetaType)types.TryGet(key); + // if (type != null) return type.Serializer; + // throw new KeyNotFoundException(); + + //} + +#if FEAT_COMPILER + private void BuildAllSerializers() + { + // note that types.Count may increase during this operation, as some serializers + // bring other types into play + for (int i = 0; i < types.Count; i++) + { + // the primary purpose of this is to force the creation of the Serializer + MetaType mt = (MetaType)types[i]; + if (mt.Serializer == null) + throw new InvalidOperationException("No serializer available for " + mt.Type.Name); + } + } + + internal sealed class SerializerPair : IComparable + { + int IComparable.CompareTo(object obj) + { + if (obj == null) throw new ArgumentException("obj"); + SerializerPair other = (SerializerPair)obj; + + // we want to bunch all the items with the same base-type together, but we need the items with a + // different base **first**. + if (this.BaseKey == this.MetaKey) + { + if (other.BaseKey == other.MetaKey) + { // neither is a subclass + return this.MetaKey.CompareTo(other.MetaKey); + } + else + { // "other" (only) is involved in inheritance; "other" should be first + return 1; + } + } + else + { + if (other.BaseKey == other.MetaKey) + { // "this" (only) is involved in inheritance; "this" should be first + return -1; + } + else + { // both are involved in inheritance + int result = this.BaseKey.CompareTo(other.BaseKey); + if (result == 0) result = this.MetaKey.CompareTo(other.MetaKey); + return result; + } + } + } + public readonly int MetaKey, BaseKey; + public readonly MetaType Type; + public readonly MethodBuilder Serialize, Deserialize; + public readonly ILGenerator SerializeBody, DeserializeBody; + public SerializerPair(int metaKey, int baseKey, MetaType type, MethodBuilder serialize, MethodBuilder deserialize, + ILGenerator serializeBody, ILGenerator deserializeBody) + { + this.MetaKey = metaKey; + this.BaseKey = baseKey; + this.Serialize = serialize; + this.Deserialize = deserialize; + this.SerializeBody = serializeBody; + this.DeserializeBody = deserializeBody; + this.Type = type; + } + } + + /// + /// Fully compiles the current model into a static-compiled model instance + /// + /// A full compilation is restricted to accessing public types / members + /// An instance of the newly created compiled type-model + public TypeModel Compile() + { + CompilerOptions options = new CompilerOptions(); + return Compile(options); + } + + static ILGenerator Override(TypeBuilder type, string name) + { + MethodInfo baseMethod = type.BaseType.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance); + + ParameterInfo[] parameters = baseMethod.GetParameters(); + Type[] paramTypes = new Type[parameters.Length]; + for (int i = 0; i < paramTypes.Length; i++) + { + paramTypes[i] = parameters[i].ParameterType; + } + MethodBuilder newMethod = type.DefineMethod(baseMethod.Name, + (baseMethod.Attributes & ~MethodAttributes.Abstract) | MethodAttributes.Final, baseMethod.CallingConvention, baseMethod.ReturnType, paramTypes); + ILGenerator il = newMethod.GetILGenerator(); + type.DefineMethodOverride(newMethod, baseMethod); + return il; + } + + /// + /// Represents configuration options for compiling a model to + /// a standalone assembly. + /// + public sealed class CompilerOptions + { + /// + /// Import framework options from an existing type + /// + public void SetFrameworkOptions(MetaType from) + { + if (from == null) throw new ArgumentNullException("from"); + AttributeMap[] attribs = AttributeMap.Create(from.Model, Helpers.GetAssembly(from.Type)); + foreach (AttributeMap attrib in attribs) + { + if (attrib.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute") + { + object tmp; + if (attrib.TryGet("FrameworkName", out tmp)) TargetFrameworkName = (string)tmp; + if (attrib.TryGet("FrameworkDisplayName", out tmp)) TargetFrameworkDisplayName = (string)tmp; + break; + } + } + } + + private string targetFrameworkName, targetFrameworkDisplayName, typeName, outputPath, imageRuntimeVersion; + private int metaDataVersion; + /// + /// The TargetFrameworkAttribute FrameworkName value to burn into the generated assembly + /// + public string TargetFrameworkName { get { return targetFrameworkName; } set { targetFrameworkName = value; } } + + /// + /// The TargetFrameworkAttribute FrameworkDisplayName value to burn into the generated assembly + /// + public string TargetFrameworkDisplayName { get { return targetFrameworkDisplayName; } set { targetFrameworkDisplayName = value; } } + /// + /// The name of the TypeModel class to create + /// + public string TypeName { get { return typeName; } set { typeName = value; } } + +#if COREFX + internal const string NoPersistence = "Assembly persistence not supported on this runtime"; +#endif + /// + /// The path for the new dll + /// +#if COREFX + [Obsolete(NoPersistence)] +#endif + public string OutputPath { get { return outputPath; } set { outputPath = value; } } + /// + /// The runtime version for the generated assembly + /// + public string ImageRuntimeVersion { get { return imageRuntimeVersion; } set { imageRuntimeVersion = value; } } + /// + /// The runtime version for the generated assembly + /// + public int MetaDataVersion { get { return metaDataVersion; } set { metaDataVersion = value; } } + + + private Accessibility accessibility = Accessibility.Public; + /// + /// The acecssibility of the generated serializer + /// + public Accessibility Accessibility { get { return accessibility; } set { accessibility = value; } } + } + + /// + /// Type accessibility + /// + public enum Accessibility + { + /// + /// Available to all callers + /// + Public, + /// + /// Available to all callers in the same assembly, or assemblies specified via [InternalsVisibleTo(...)] + /// + Internal + } + +#if !COREFX + /// + /// Fully compiles the current model into a static-compiled serialization dll + /// (the serialization dll still requires protobuf-net for support services). + /// + /// A full compilation is restricted to accessing public types / members + /// The name of the TypeModel class to create + /// The path for the new dll + /// An instance of the newly created compiled type-model + public TypeModel Compile(string name, string path) + { + CompilerOptions options = new CompilerOptions(); + options.TypeName = name; + options.OutputPath = path; + return Compile(options); + } +#endif + /// + /// Fully compiles the current model into a static-compiled serialization dll + /// (the serialization dll still requires protobuf-net for support services). + /// + /// A full compilation is restricted to accessing public types / members + /// An instance of the newly created compiled type-model + public TypeModel Compile(CompilerOptions options) + { + if (options == null) throw new ArgumentNullException("options"); + string typeName = options.TypeName; +#pragma warning disable 0618 + string path = options.OutputPath; +#pragma warning restore 0618 + BuildAllSerializers(); + Freeze(); + bool save = !string.IsNullOrEmpty(path); + if (string.IsNullOrEmpty(typeName)) + { + if (save) throw new ArgumentNullException("typeName"); + typeName = Guid.NewGuid().ToString(); + } + + + string assemblyName, moduleName; + if (path == null) + { + assemblyName = typeName; + moduleName = assemblyName + ".dll"; + } + else + { + assemblyName = new System.IO.FileInfo(System.IO.Path.GetFileNameWithoutExtension(path)).Name; + moduleName = assemblyName + System.IO.Path.GetExtension(path); + } + +#if COREFX + AssemblyName an = new AssemblyName(); + an.Name = assemblyName; + AssemblyBuilder asm = AssemblyBuilder.DefineDynamicAssembly(an, + AssemblyBuilderAccess.Run); + ModuleBuilder module = asm.DefineDynamicModule(moduleName); +#else + AssemblyName an = new AssemblyName(); + an.Name = assemblyName; + AssemblyBuilder asm = AppDomain.CurrentDomain.DefineDynamicAssembly(an, + (save ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run) + ); + ModuleBuilder module = save ? asm.DefineDynamicModule(moduleName, path) + : asm.DefineDynamicModule(moduleName); +#endif + + WriteAssemblyAttributes(options, assemblyName, asm); + + TypeBuilder type = WriteBasicTypeModel(options, typeName, module); + + int index; + bool hasInheritance; + SerializerPair[] methodPairs; + Compiler.CompilerContext.ILVersion ilVersion; + WriteSerializers(options, assemblyName, type, out index, out hasInheritance, out methodPairs, out ilVersion); + + ILGenerator il; + int knownTypesCategory; + FieldBuilder knownTypes; + Type knownTypesLookupType; + WriteGetKeyImpl(type, hasInheritance, methodPairs, ilVersion, assemblyName, out il, out knownTypesCategory, out knownTypes, out knownTypesLookupType); + + // trivial flags + il = Override(type, "SerializeDateTimeKind"); + il.Emit(IncludeDateTimeKind ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ret); + // end: trivial flags + + Compiler.CompilerContext ctx = WriteSerializeDeserialize(assemblyName, type, methodPairs, ilVersion, ref il); + + WriteConstructors(type, ref index, methodPairs, ref il, knownTypesCategory, knownTypes, knownTypesLookupType, ctx); + + +#if COREFX + Type finalType = type.CreateTypeInfo().AsType(); +#else + Type finalType = type.CreateType(); +#endif + if (!string.IsNullOrEmpty(path)) + { +#if COREFX + throw new NotSupportedException(CompilerOptions.NoPersistence); +#else + try + { + asm.Save(path); + } + catch (IOException ex) + { + // advertise the file info + throw new IOException(path + ", " + ex.Message, ex); + } + Helpers.DebugWriteLine("Wrote dll:" + path); +#endif + } + return (TypeModel)Activator.CreateInstance(finalType); + } + + private void WriteConstructors(TypeBuilder type, ref int index, SerializerPair[] methodPairs, ref ILGenerator il, int knownTypesCategory, FieldBuilder knownTypes, Type knownTypesLookupType, Compiler.CompilerContext ctx) + { + type.DefineDefaultConstructor(MethodAttributes.Public); + il = type.DefineTypeInitializer().GetILGenerator(); + switch (knownTypesCategory) + { + case KnownTypes_Array: + { + Compiler.CompilerContext.LoadValue(il, types.Count); + il.Emit(OpCodes.Newarr, ctx.MapType(typeof(System.Type))); + index = 0; + foreach (SerializerPair pair in methodPairs) + { + il.Emit(OpCodes.Dup); + Compiler.CompilerContext.LoadValue(il, index); + il.Emit(OpCodes.Ldtoken, pair.Type.Type); + il.EmitCall(OpCodes.Call, ctx.MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"), null); + il.Emit(OpCodes.Stelem_Ref); + index++; + } + il.Emit(OpCodes.Stsfld, knownTypes); + il.Emit(OpCodes.Ret); + } + break; + case KnownTypes_Dictionary: + { + Compiler.CompilerContext.LoadValue(il, types.Count); + //LocalBuilder loc = il.DeclareLocal(knownTypesLookupType); + il.Emit(OpCodes.Newobj, knownTypesLookupType.GetConstructor(new Type[] { MapType(typeof(int)) })); + il.Emit(OpCodes.Stsfld, knownTypes); + int typeIndex = 0; + foreach (SerializerPair pair in methodPairs) + { + il.Emit(OpCodes.Ldsfld, knownTypes); + il.Emit(OpCodes.Ldtoken, pair.Type.Type); + il.EmitCall(OpCodes.Call, ctx.MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"), null); + int keyIndex = typeIndex++, lastKey = pair.BaseKey; + if (lastKey != pair.MetaKey) // not a base-type; need to give the index of the base-type + { + keyIndex = -1; // assume epic fail + for (int j = 0; j < methodPairs.Length; j++) + { + if (methodPairs[j].BaseKey == lastKey && methodPairs[j].MetaKey == lastKey) + { + keyIndex = j; + break; + } + } + } + Compiler.CompilerContext.LoadValue(il, keyIndex); + il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetMethod("Add", new Type[] { MapType(typeof(System.Type)), MapType(typeof(int)) }), null); + } + il.Emit(OpCodes.Ret); + } + break; + case KnownTypes_Hashtable: + { + Compiler.CompilerContext.LoadValue(il, types.Count); + il.Emit(OpCodes.Newobj, knownTypesLookupType.GetConstructor(new Type[] { MapType(typeof(int)) })); + il.Emit(OpCodes.Stsfld, knownTypes); + int typeIndex = 0; + foreach (SerializerPair pair in methodPairs) + { + il.Emit(OpCodes.Ldsfld, knownTypes); + il.Emit(OpCodes.Ldtoken, pair.Type.Type); + il.EmitCall(OpCodes.Call, ctx.MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"), null); + int keyIndex = typeIndex++, lastKey = pair.BaseKey; + if (lastKey != pair.MetaKey) // not a base-type; need to give the index of the base-type + { + keyIndex = -1; // assume epic fail + for (int j = 0; j < methodPairs.Length; j++) + { + if (methodPairs[j].BaseKey == lastKey && methodPairs[j].MetaKey == lastKey) + { + keyIndex = j; + break; + } + } + } + Compiler.CompilerContext.LoadValue(il, keyIndex); + il.Emit(OpCodes.Box, MapType(typeof(int))); + il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetMethod("Add", new Type[] { MapType(typeof(object)), MapType(typeof(object)) }), null); + } + il.Emit(OpCodes.Ret); + } + break; + default: + throw new InvalidOperationException(); + } + } + + private Compiler.CompilerContext WriteSerializeDeserialize(string assemblyName, TypeBuilder type, SerializerPair[] methodPairs, Compiler.CompilerContext.ILVersion ilVersion, ref ILGenerator il) + { + il = Override(type, "Serialize"); + Compiler.CompilerContext ctx = new Compiler.CompilerContext(il, false, true, methodPairs, this, ilVersion, assemblyName, MapType(typeof(object)), "Serialize " + type.Name); + // arg0 = this, arg1 = key, arg2=obj, arg3=dest + Compiler.CodeLabel[] jumpTable = new Compiler.CodeLabel[types.Count]; + for (int i = 0; i < jumpTable.Length; i++) + { + jumpTable[i] = ctx.DefineLabel(); + } + il.Emit(OpCodes.Ldarg_1); + ctx.Switch(jumpTable); + ctx.Return(); + for (int i = 0; i < jumpTable.Length; i++) + { + SerializerPair pair = methodPairs[i]; + ctx.MarkLabel(jumpTable[i]); + il.Emit(OpCodes.Ldarg_2); + ctx.CastFromObject(pair.Type.Type); + il.Emit(OpCodes.Ldarg_3); + il.EmitCall(OpCodes.Call, pair.Serialize, null); + ctx.Return(); + } + + il = Override(type, "Deserialize"); + ctx = new Compiler.CompilerContext(il, false, false, methodPairs, this, ilVersion, assemblyName, MapType(typeof(object)), "Deserialize " + type.Name); + // arg0 = this, arg1 = key, arg2=obj, arg3=source + for (int i = 0; i < jumpTable.Length; i++) + { + jumpTable[i] = ctx.DefineLabel(); + } + il.Emit(OpCodes.Ldarg_1); + ctx.Switch(jumpTable); + ctx.LoadNullRef(); + ctx.Return(); + for (int i = 0; i < jumpTable.Length; i++) + { + SerializerPair pair = methodPairs[i]; + ctx.MarkLabel(jumpTable[i]); + Type keyType = pair.Type.Type; + if (Helpers.IsValueType(keyType)) + { + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldarg_3); + il.EmitCall(OpCodes.Call, EmitBoxedSerializer(type, i, keyType, methodPairs, this, ilVersion, assemblyName), null); + ctx.Return(); + } + else + { + il.Emit(OpCodes.Ldarg_2); + ctx.CastFromObject(keyType); + il.Emit(OpCodes.Ldarg_3); + il.EmitCall(OpCodes.Call, pair.Deserialize, null); + ctx.Return(); + } + } + return ctx; + } + + private const int KnownTypes_Array = 1, KnownTypes_Dictionary = 2, KnownTypes_Hashtable = 3, KnownTypes_ArrayCutoff = 20; + private void WriteGetKeyImpl(TypeBuilder type, bool hasInheritance, SerializerPair[] methodPairs, Compiler.CompilerContext.ILVersion ilVersion, string assemblyName, out ILGenerator il, out int knownTypesCategory, out FieldBuilder knownTypes, out Type knownTypesLookupType) + { + + il = Override(type, "GetKeyImpl"); + Compiler.CompilerContext ctx = new Compiler.CompilerContext(il, false, false, methodPairs, this, ilVersion, assemblyName, MapType(typeof(System.Type), true), "GetKeyImpl"); + + + if (types.Count <= KnownTypes_ArrayCutoff) + { + knownTypesCategory = KnownTypes_Array; + knownTypesLookupType = MapType(typeof(System.Type[]), true); + } + else + { + knownTypesLookupType = MapType(typeof(System.Collections.Generic.Dictionary), false); + +#if !COREFX + if (knownTypesLookupType == null) + { + knownTypesLookupType = MapType(typeof(Hashtable), true); + knownTypesCategory = KnownTypes_Hashtable; + } + else +#endif + { + knownTypesCategory = KnownTypes_Dictionary; + } + } + knownTypes = type.DefineField("knownTypes", knownTypesLookupType, FieldAttributes.Private | FieldAttributes.InitOnly | FieldAttributes.Static); + + switch (knownTypesCategory) + { + case KnownTypes_Array: + { + il.Emit(OpCodes.Ldsfld, knownTypes); + il.Emit(OpCodes.Ldarg_1); + // note that Array.IndexOf is not supported under CF + il.EmitCall(OpCodes.Callvirt, MapType(typeof(IList)).GetMethod( + "IndexOf", new Type[] { MapType(typeof(object)) }), null); + if (hasInheritance) + { + il.DeclareLocal(MapType(typeof(int))); // loc-0 + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc_0); + + BasicList getKeyLabels = new BasicList(); + int lastKey = -1; + for (int i = 0; i < methodPairs.Length; i++) + { + if (methodPairs[i].MetaKey == methodPairs[i].BaseKey) break; + if (lastKey == methodPairs[i].BaseKey) + { // add the last label again + getKeyLabels.Add(getKeyLabels[getKeyLabels.Count - 1]); + } + else + { // add a new unique label + getKeyLabels.Add(ctx.DefineLabel()); + lastKey = methodPairs[i].BaseKey; + } + } + Compiler.CodeLabel[] subtypeLabels = new Compiler.CodeLabel[getKeyLabels.Count]; + getKeyLabels.CopyTo(subtypeLabels, 0); + + ctx.Switch(subtypeLabels); + il.Emit(OpCodes.Ldloc_0); // not a sub-type; use the original value + il.Emit(OpCodes.Ret); + + lastKey = -1; + // now output the different branches per sub-type (not derived type) + for (int i = subtypeLabels.Length - 1; i >= 0; i--) + { + if (lastKey != methodPairs[i].BaseKey) + { + lastKey = methodPairs[i].BaseKey; + // find the actual base-index for this base-key (i.e. the index of + // the base-type) + int keyIndex = -1; + for (int j = subtypeLabels.Length; j < methodPairs.Length; j++) + { + if (methodPairs[j].BaseKey == lastKey && methodPairs[j].MetaKey == lastKey) + { + keyIndex = j; + break; + } + } + ctx.MarkLabel(subtypeLabels[i]); + Compiler.CompilerContext.LoadValue(il, keyIndex); + il.Emit(OpCodes.Ret); + } + } + } + else + { + il.Emit(OpCodes.Ret); + } + } + break; + case KnownTypes_Dictionary: + { + LocalBuilder result = il.DeclareLocal(MapType(typeof(int))); + Label otherwise = il.DefineLabel(); + il.Emit(OpCodes.Ldsfld, knownTypes); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloca_S, result); + il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetMethod("TryGetValue", BindingFlags.Instance | BindingFlags.Public), null); + il.Emit(OpCodes.Brfalse_S, otherwise); + il.Emit(OpCodes.Ldloc_S, result); + il.Emit(OpCodes.Ret); + il.MarkLabel(otherwise); + il.Emit(OpCodes.Ldc_I4_M1); + il.Emit(OpCodes.Ret); + } + break; + case KnownTypes_Hashtable: + { + Label otherwise = il.DefineLabel(); + il.Emit(OpCodes.Ldsfld, knownTypes); + il.Emit(OpCodes.Ldarg_1); + il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetProperty("Item").GetGetMethod(), null); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Brfalse_S, otherwise); + if (ilVersion == Compiler.CompilerContext.ILVersion.Net1) + { + il.Emit(OpCodes.Unbox, MapType(typeof(int))); + il.Emit(OpCodes.Ldobj, MapType(typeof(int))); + } + else + { + il.Emit(OpCodes.Unbox_Any, MapType(typeof(int))); + } + il.Emit(OpCodes.Ret); + il.MarkLabel(otherwise); + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ldc_I4_M1); + il.Emit(OpCodes.Ret); + } + break; + default: + throw new InvalidOperationException(); + } + } + + private void WriteSerializers(CompilerOptions options, string assemblyName, TypeBuilder type, out int index, out bool hasInheritance, out SerializerPair[] methodPairs, out Compiler.CompilerContext.ILVersion ilVersion) + { + Compiler.CompilerContext ctx; + + index = 0; + hasInheritance = false; + methodPairs = new SerializerPair[types.Count]; + foreach (MetaType metaType in types) + { + MethodBuilder writeMethod = type.DefineMethod("Write" +#if DEBUG + + metaType.Type.Name +#endif +, + MethodAttributes.Private | MethodAttributes.Static, CallingConventions.Standard, + MapType(typeof(void)), new Type[] { metaType.Type, MapType(typeof(ProtoWriter)) }); + + MethodBuilder readMethod = type.DefineMethod("Read" +#if DEBUG + + metaType.Type.Name +#endif +, + MethodAttributes.Private | MethodAttributes.Static, CallingConventions.Standard, + metaType.Type, new Type[] { metaType.Type, MapType(typeof(ProtoReader)) }); + + SerializerPair pair = new SerializerPair( + GetKey(metaType.Type, true, false), GetKey(metaType.Type, true, true), metaType, + writeMethod, readMethod, writeMethod.GetILGenerator(), readMethod.GetILGenerator()); + methodPairs[index++] = pair; + if (pair.MetaKey != pair.BaseKey) hasInheritance = true; + } + + if (hasInheritance) + { + Array.Sort(methodPairs); + } + + ilVersion = Compiler.CompilerContext.ILVersion.Net2; + if (options.MetaDataVersion == 0x10000) + { + ilVersion = Compiler.CompilerContext.ILVersion.Net1; // old-school! + } + for (index = 0; index < methodPairs.Length; index++) + { + SerializerPair pair = methodPairs[index]; + ctx = new Compiler.CompilerContext(pair.SerializeBody, true, true, methodPairs, this, ilVersion, assemblyName, pair.Type.Type, "SerializeImpl " + pair.Type.Type.Name); + MemberInfo returnType = pair.Deserialize.ReturnType +#if COREFX + .GetTypeInfo() +#endif + ; + ctx.CheckAccessibility(ref returnType); + pair.Type.Serializer.EmitWrite(ctx, ctx.InputValue); + ctx.Return(); + + ctx = new Compiler.CompilerContext(pair.DeserializeBody, true, false, methodPairs, this, ilVersion, assemblyName, pair.Type.Type, "DeserializeImpl " + pair.Type.Type.Name); + pair.Type.Serializer.EmitRead(ctx, ctx.InputValue); + if (!pair.Type.Serializer.ReturnsValue) + { + ctx.LoadValue(ctx.InputValue); + } + ctx.Return(); + } + } + + private TypeBuilder WriteBasicTypeModel(CompilerOptions options, string typeName, ModuleBuilder module) + { + Type baseType = MapType(typeof(TypeModel)); +#if COREFX + TypeAttributes typeAttributes = (baseType.GetTypeInfo().Attributes & ~TypeAttributes.Abstract) | TypeAttributes.Sealed; +#else + TypeAttributes typeAttributes = (baseType.Attributes & ~TypeAttributes.Abstract) | TypeAttributes.Sealed; +#endif + if (options.Accessibility == Accessibility.Internal) + { + typeAttributes &= ~TypeAttributes.Public; + } + + TypeBuilder type = module.DefineType(typeName, typeAttributes, baseType); + return type; + } + + private void WriteAssemblyAttributes(CompilerOptions options, string assemblyName, AssemblyBuilder asm) + { + if (!string.IsNullOrEmpty(options.TargetFrameworkName)) + { + // get [TargetFramework] from mscorlib/equivalent and burn into the new assembly + Type versionAttribType = null; + try + { // this is best-endeavours only + versionAttribType = GetType("System.Runtime.Versioning.TargetFrameworkAttribute", Helpers.GetAssembly(MapType(typeof(string)))); + } + catch { /* don't stress */ } + if (versionAttribType != null) + { + PropertyInfo[] props; + object[] propValues; + if (string.IsNullOrEmpty(options.TargetFrameworkDisplayName)) + { + props = new PropertyInfo[0]; + propValues = new object[0]; + } + else + { + props = new PropertyInfo[1] { versionAttribType.GetProperty("FrameworkDisplayName") }; + propValues = new object[1] { options.TargetFrameworkDisplayName }; + } + CustomAttributeBuilder builder = new CustomAttributeBuilder( + versionAttribType.GetConstructor(new Type[] { MapType(typeof(string)) }), + new object[] { options.TargetFrameworkName }, + props, + propValues); + asm.SetCustomAttribute(builder); + } + } + + // copy assembly:InternalsVisibleTo + Type internalsVisibleToAttribType = null; + + try + { + internalsVisibleToAttribType = MapType(typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute)); + } + catch { /* best endeavors only */ } + + if (internalsVisibleToAttribType != null) + { + BasicList internalAssemblies = new BasicList(), consideredAssemblies = new BasicList(); + foreach (MetaType metaType in types) + { + Assembly assembly = Helpers.GetAssembly(metaType.Type); + if (consideredAssemblies.IndexOfReference(assembly) >= 0) continue; + consideredAssemblies.Add(assembly); + + AttributeMap[] assemblyAttribsMap = AttributeMap.Create(this, assembly); + for (int i = 0; i < assemblyAttribsMap.Length; i++) + { + + if (assemblyAttribsMap[i].AttributeType != internalsVisibleToAttribType) continue; + + object privelegedAssemblyObj; + assemblyAttribsMap[i].TryGet("AssemblyName", out privelegedAssemblyObj); + string privelegedAssemblyName = privelegedAssemblyObj as string; + if (privelegedAssemblyName == assemblyName || string.IsNullOrEmpty(privelegedAssemblyName)) continue; // ignore + + if (internalAssemblies.IndexOfString(privelegedAssemblyName) >= 0) continue; // seen it before + internalAssemblies.Add(privelegedAssemblyName); + + CustomAttributeBuilder builder = new CustomAttributeBuilder( + internalsVisibleToAttribType.GetConstructor(new Type[] { MapType(typeof(string)) }), + new object[] { privelegedAssemblyName }); + asm.SetCustomAttribute(builder); + } + } + } + } + + private static MethodBuilder EmitBoxedSerializer(TypeBuilder type, int i, Type valueType, SerializerPair[] methodPairs, TypeModel model, Compiler.CompilerContext.ILVersion ilVersion, string assemblyName) + { + MethodInfo dedicated = methodPairs[i].Deserialize; + MethodBuilder boxedSerializer = type.DefineMethod("_" + i.ToString(), MethodAttributes.Static, CallingConventions.Standard, + model.MapType(typeof(object)), new Type[] { model.MapType(typeof(object)), model.MapType(typeof(ProtoReader)) }); + Compiler.CompilerContext ctx = new Compiler.CompilerContext(boxedSerializer.GetILGenerator(), true, false, methodPairs, model, ilVersion, assemblyName, model.MapType(typeof(object)), "BoxedSerializer " + valueType.Name); + ctx.LoadValue(ctx.InputValue); + Compiler.CodeLabel @null = ctx.DefineLabel(); + ctx.BranchIfFalse(@null, true); + + Type mappedValueType = valueType; + ctx.LoadValue(ctx.InputValue); + ctx.CastFromObject(mappedValueType); + ctx.LoadReaderWriter(); + ctx.EmitCall(dedicated); + ctx.CastToObject(mappedValueType); + ctx.Return(); + + ctx.MarkLabel(@null); + using (Compiler.Local typedVal = new Compiler.Local(ctx, mappedValueType)) + { + // create a new valueType + ctx.LoadAddress(typedVal, mappedValueType); + ctx.EmitCtor(mappedValueType); + ctx.LoadValue(typedVal); + ctx.LoadReaderWriter(); + ctx.EmitCall(dedicated); + ctx.CastToObject(mappedValueType); + ctx.Return(); + } + return boxedSerializer; + } + +#endif + //internal bool IsDefined(Type type, int fieldNumber) + //{ + // return FindWithoutAdd(type).IsDefined(fieldNumber); + //} + + // note that this is used by some of the unit tests + internal bool IsPrepared(Type type) + { + MetaType meta = FindWithoutAdd(type); + return meta != null && meta.IsPrepared(); + } + + internal EnumSerializer.EnumPair[] GetEnumMap(Type type) + { + int index = FindOrAddAuto(type, false, false, false); + return index < 0 ? null : ((MetaType)types[index]).GetEnumMap(); + } + + private int metadataTimeoutMilliseconds = 5000; + /// + /// The amount of time to wait if there are concurrent metadata access operations + /// + public int MetadataTimeoutMilliseconds + { + get { return metadataTimeoutMilliseconds; } + set + { + if (value <= 0) throw new ArgumentOutOfRangeException("MetadataTimeoutMilliseconds"); + metadataTimeoutMilliseconds = value; + } + } + +#if DEBUG + int lockCount; + /// + /// Gets how many times a model lock was taken + /// + public int LockCount { get { return lockCount; } } +#endif + internal void TakeLock(ref int opaqueToken) + { + const string message = "Timeout while inspecting metadata; this may indicate a deadlock. This can often be avoided by preparing necessary serializers during application initialization, rather than allowing multiple threads to perform the initial metadata inspection; please also see the LockContended event"; + opaqueToken = 0; +#if PORTABLE + if(!Monitor.TryEnter(types, metadataTimeoutMilliseconds)) throw new TimeoutException(message); + opaqueToken = Interlocked.CompareExchange(ref contentionCounter, 0, 0); // just fetch current value (starts at 1) +#elif CF2 || CF35 + int remaining = metadataTimeoutMilliseconds; + bool lockTaken; + do { + lockTaken = Monitor.TryEnter(types); + if(!lockTaken) + { + if(remaining <= 0) throw new TimeoutException(message); + remaining -= 50; + Thread.Sleep(50); + } + } while(!lockTaken); + opaqueToken = Interlocked.CompareExchange(ref contentionCounter, 0, 0); // just fetch current value (starts at 1) +#else + if (Monitor.TryEnter(types, metadataTimeoutMilliseconds)) + { + opaqueToken = GetContention(); // just fetch current value (starts at 1) + } + else + { + AddContention(); + + throw new TimeoutException(message); + } +#endif + +#if DEBUG // note that here, through all code-paths: we have the lock + lockCount++; +#endif + } + + private int contentionCounter = 1; +#if PLAT_NO_INTERLOCKED + private readonly object contentionLock = new object(); +#endif + private int GetContention() + { +#if PLAT_NO_INTERLOCKED + lock(contentionLock) + { + return contentionCounter; + } +#else + return Interlocked.CompareExchange(ref contentionCounter, 0, 0); +#endif + } + private void AddContention() + { +#if PLAT_NO_INTERLOCKED + lock(contentionLock) + { + contentionCounter++; + } +#else + Interlocked.Increment(ref contentionCounter); +#endif + } + + internal void ReleaseLock(int opaqueToken) + { + if (opaqueToken != 0) + { + Monitor.Exit(types); + if (opaqueToken != GetContention()) // contention-count changes since we looked! + { + LockContentedEventHandler handler = LockContended; + if (handler != null) + { + // not hugely elegant, but this is such a far-corner-case that it doesn't need to be slick - I'll settle for cross-platform + string stackTrace; + try + { + throw new ProtoException(); + } + catch (Exception ex) + { + stackTrace = ex.StackTrace; + } + + handler(this, new LockContentedEventArgs(stackTrace)); + } + } + } + } + /// + /// If a lock-contention is detected, this event signals the *owner* of the lock responsible for the blockage, indicating + /// what caused the problem; this is only raised if the lock-owning code successfully completes. + /// + public event LockContentedEventHandler LockContended; + + internal void ResolveListTypes(Type type, ref Type itemType, ref Type defaultType) + { + if (type == null) return; + if (Helpers.GetTypeCode(type) != ProtoTypeCode.Unknown) return; // don't try this[type] for inbuilts + + // handle arrays + if (type.IsArray) + { + if (type.GetArrayRank() != 1) + { + throw new NotSupportedException("Multi-dimension arrays are supported"); + } + itemType = type.GetElementType(); + if (itemType == MapType(typeof(byte))) + { + defaultType = itemType = null; + } + else + { + defaultType = type; + } + } + else + { + // if not an array, first check it isn't explicitly opted out + if (this[type].IgnoreListHandling) return; + } + + // handle lists + if (itemType == null) { itemType = TypeModel.GetListItemType(this, type); } + + // check for nested data (not allowed) + if (itemType != null) + { + Type nestedItemType = null, nestedDefaultType = null; + ResolveListTypes(itemType, ref nestedItemType, ref nestedDefaultType); + if (nestedItemType != null) + { + throw TypeModel.CreateNestedListsNotSupported(type); + } + } + + if (itemType != null && defaultType == null) + { +#if COREFX || PROFILE259 + TypeInfo typeInfo = IntrospectionExtensions.GetTypeInfo(type); + if (typeInfo.IsClass && !typeInfo.IsAbstract && Helpers.GetConstructor(typeInfo, Helpers.EmptyTypes, true) != null) +#else + if (type.IsClass && !type.IsAbstract && Helpers.GetConstructor(type, Helpers.EmptyTypes, true) != null) +#endif + { + defaultType = type; + } + if (defaultType == null) + { +#if COREFX || PROFILE259 + if (typeInfo.IsInterface) +#else + if (type.IsInterface) +#endif + { + + Type[] genArgs; +#if COREFX || PROFILE259 + if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>) + && itemType == typeof(System.Collections.Generic.KeyValuePair<,>).MakeGenericType(genArgs = typeInfo.GenericTypeArguments)) +#else + if (type.IsGenericType && type.GetGenericTypeDefinition() == MapType(typeof(System.Collections.Generic.IDictionary<,>)) + && itemType == MapType(typeof(System.Collections.Generic.KeyValuePair<,>)).MakeGenericType(genArgs = type.GetGenericArguments())) +#endif + { + defaultType = MapType(typeof(System.Collections.Generic.Dictionary<,>)).MakeGenericType(genArgs); + } + else + { + defaultType = MapType(typeof(System.Collections.Generic.List<>)).MakeGenericType(itemType); + } + } + } + // verify that the default type is appropriate + if (defaultType != null && !Helpers.IsAssignableFrom(type, defaultType)) { defaultType = null; } + } + } + + internal string GetSchemaTypeName(Type effectiveType, DataFormat dataFormat, bool asReference, bool dynamicType, ref CommonImports imports) + { + Type tmp = Helpers.GetUnderlyingType(effectiveType); + if (tmp != null) effectiveType = tmp; + + if (effectiveType == this.MapType(typeof(byte[]))) return "bytes"; + + WireType wireType; + IProtoSerializer ser = ValueMember.TryGetCoreSerializer(this, dataFormat, effectiveType, out wireType, false, false, false, false); + if (ser == null) + { // model type + if (asReference || dynamicType) + { + imports |= CommonImports.Bcl; + return ".bcl.NetObjectProxy"; + } + return this[effectiveType].GetSurrogateOrBaseOrSelf(true).GetSchemaTypeName(); + } + else + { + if (ser is ParseableSerializer) + { + if (asReference) imports |= CommonImports.Bcl; + return asReference ? ".bcl.NetObjectProxy" : "string"; + } + + switch (Helpers.GetTypeCode(effectiveType)) + { + case ProtoTypeCode.Boolean: return "bool"; + case ProtoTypeCode.Single: return "float"; + case ProtoTypeCode.Double: return "double"; + case ProtoTypeCode.String: + if (asReference) imports |= CommonImports.Bcl; + return asReference ? ".bcl.NetObjectProxy" : "string"; + case ProtoTypeCode.Byte: + case ProtoTypeCode.Char: + case ProtoTypeCode.UInt16: + case ProtoTypeCode.UInt32: + switch (dataFormat) + { + case DataFormat.FixedSize: return "fixed32"; + default: return "uint32"; + } + case ProtoTypeCode.SByte: + case ProtoTypeCode.Int16: + case ProtoTypeCode.Int32: + switch (dataFormat) + { + case DataFormat.ZigZag: return "sint32"; + case DataFormat.FixedSize: return "sfixed32"; + default: return "int32"; + } + case ProtoTypeCode.UInt64: + switch (dataFormat) + { + case DataFormat.FixedSize: return "fixed64"; + default: return "uint64"; + } + case ProtoTypeCode.Int64: + switch (dataFormat) + { + case DataFormat.ZigZag: return "sint64"; + case DataFormat.FixedSize: return "sfixed64"; + default: return "int64"; + } + case ProtoTypeCode.DateTime: + switch (dataFormat) + { + case DataFormat.FixedSize: return "sint64"; + case DataFormat.WellKnown: + imports |= CommonImports.Timestamp; + return ".google.protobuf.Timestamp"; + default: + imports |= CommonImports.Bcl; + return ".bcl.DateTime"; + } + case ProtoTypeCode.TimeSpan: + switch (dataFormat) + { + case DataFormat.FixedSize: return "sint64"; + case DataFormat.WellKnown: + imports |= CommonImports.Duration; + return ".google.protobuf.Duration"; + default: + imports |= CommonImports.Bcl; + return ".bcl.TimeSpan"; + } + case ProtoTypeCode.Decimal: imports |= CommonImports.Bcl; return ".bcl.Decimal"; + case ProtoTypeCode.Guid: imports |= CommonImports.Bcl; return ".bcl.Guid"; + case ProtoTypeCode.Type: return "string"; + default: throw new NotSupportedException("No .proto map found for: " + effectiveType.FullName); + } + } + + } + + /// + /// Designate a factory-method to use to create instances of any type; note that this only affect types seen by the serializer *after* setting the factory. + /// + public void SetDefaultFactory(MethodInfo methodInfo) + { + VerifyFactory(methodInfo, null); + defaultFactory = methodInfo; + } + private MethodInfo defaultFactory; + + internal void VerifyFactory(MethodInfo factory, Type type) + { + if (factory != null) + { + if (type != null && Helpers.IsValueType(type)) throw new InvalidOperationException(); + if (!factory.IsStatic) throw new ArgumentException("A factory-method must be static", "factory"); + if ((type != null && factory.ReturnType != type) && factory.ReturnType != MapType(typeof(object))) throw new ArgumentException("The factory-method must return object" + (type == null ? "" : (" or " + type.FullName)), "factory"); + + if (!CallbackSet.CheckCallbackParameters(this, factory)) throw new ArgumentException("Invalid factory signature in " + factory.DeclaringType.FullName + "." + factory.Name, "factory"); + } + } + + /// + /// Raised before a type is auto-configured; this allows the auto-configuration to be electively suppressed + /// + /// This callback should be fast and not involve complex external calls, as it may block the model + public event EventHandler BeforeApplyDefaultBehaviour; + + /// + /// Raised after a type is auto-configured; this allows additional external customizations + /// + /// This callback should be fast and not involve complex external calls, as it may block the model + public event EventHandler AfterApplyDefaultBehaviour; + + internal static void OnBeforeApplyDefaultBehaviour(MetaType metaType, ref TypeAddedEventArgs args) + => OnApplyDefaultBehaviour((metaType?.Model as RuntimeTypeModel)?.BeforeApplyDefaultBehaviour, metaType, ref args); + + internal static void OnAfterApplyDefaultBehaviour(MetaType metaType, ref TypeAddedEventArgs args) + => OnApplyDefaultBehaviour((metaType?.Model as RuntimeTypeModel)?.AfterApplyDefaultBehaviour, metaType, ref args); + + private static void OnApplyDefaultBehaviour( + EventHandler handler, MetaType metaType, ref TypeAddedEventArgs args) + { + if (handler != null) + { + if (args == null) args = new TypeAddedEventArgs(metaType); + handler(metaType.Model, args); + } + } + } + + /// + /// Contains the stack-trace of the owning code when a lock-contention scenario is detected + /// + public sealed class LockContentedEventArgs : EventArgs + { + private readonly string ownerStackTrace; + internal LockContentedEventArgs(string ownerStackTrace) + { + this.ownerStackTrace = ownerStackTrace; + } + + /// + /// The stack-trace of the code that owned the lock when a lock-contention scenario occurred + /// + public string OwnerStackTrace => ownerStackTrace; + } + /// + /// Event-type that is raised when a lock-contention scenario is detected + /// + public delegate void LockContentedEventHandler(object sender, LockContentedEventArgs args); +} +#endif diff --git a/Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs.meta b/Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs.meta new file mode 100644 index 0000000..231a028 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/RuntimeTypeModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e4440bfa9e92f84d81d48e6c5b0022e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/SubType.cs b/Runtime/Protobuf-net/Meta/SubType.cs new file mode 100644 index 0000000..72c8126 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/SubType.cs @@ -0,0 +1,97 @@ +#if !NO_RUNTIME +using System; +using System.Collections.Generic; +using ProtoBuf.Serializers; + +namespace ProtoBuf.Meta +{ + /// + /// Represents an inherited type in a type hierarchy. + /// + public sealed class SubType + { + internal sealed class Comparer : System.Collections.IComparer, IComparer + { + public static readonly Comparer Default = new Comparer(); + + public int Compare(object x, object y) + { + return Compare(x as SubType, y as SubType); + } + + public int Compare(SubType x, SubType y) + { + if (ReferenceEquals(x, y)) return 0; + if (x == null) return -1; + if (y == null) return 1; + + return x.FieldNumber.CompareTo(y.FieldNumber); + } + } + + private int _fieldNumber; + + /// + /// The field-number that is used to encapsulate the data (as a nested + /// message) for the derived dype. + /// + public int FieldNumber + { + get => _fieldNumber; + internal set + { + if (_fieldNumber != value) + { + MetaType.AssertValidFieldNumber(value); + ThrowIfFrozen(); + _fieldNumber = value; + } + } + } + + private void ThrowIfFrozen() + { + if (serializer != null) throw new InvalidOperationException("The type cannot be changed once a serializer has been generated"); + } + + + /// + /// The sub-type to be considered. + /// + public MetaType DerivedType => derivedType; + private readonly MetaType derivedType; + + /// + /// Creates a new SubType instance. + /// + /// The field-number that is used to encapsulate the data (as a nested + /// message) for the derived dype. + /// The sub-type to be considered. + /// Specific encoding style to use; in particular, Grouped can be used to avoid buffering, but is not the default. + public SubType(int fieldNumber, MetaType derivedType, DataFormat format) + { + if (derivedType == null) throw new ArgumentNullException(nameof(derivedType)); + if (fieldNumber <= 0) throw new ArgumentOutOfRangeException(nameof(fieldNumber)); + _fieldNumber = fieldNumber; + this.derivedType = derivedType; + this.dataFormat = format; + } + + private readonly DataFormat dataFormat; + + private IProtoSerializer serializer; + + internal IProtoSerializer Serializer => serializer ?? (serializer = BuildSerializer()); + + private IProtoSerializer BuildSerializer() + { + // note the caller here is MetaType.BuildSerializer, which already has the sync-lock + WireType wireType = WireType.String; + if(dataFormat == DataFormat.Group) wireType = WireType.StartGroup; // only one exception + + IProtoSerializer ser = new SubItemSerializer(derivedType.Type, derivedType.GetKey(false, false), derivedType, false); + return new TagDecorator(_fieldNumber, wireType, false, ser); + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/SubType.cs.meta b/Runtime/Protobuf-net/Meta/SubType.cs.meta new file mode 100644 index 0000000..fb7fe45 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/SubType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2912d37917b74846bdcffe3daa174d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs b/Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs new file mode 100644 index 0000000..399c638 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs @@ -0,0 +1,33 @@ +using System; + +namespace ProtoBuf.Meta +{ + /// + /// Event data associated with new types being added to a model + /// + public sealed class TypeAddedEventArgs : EventArgs + { + internal TypeAddedEventArgs(MetaType metaType) + { + MetaType = metaType; + ApplyDefaultBehaviour = true; + } + + /// + /// Whether or not to apply the default mapping behavior + /// + public bool ApplyDefaultBehaviour { get; set; } + /// + /// The configuration of the type being added + /// + public MetaType MetaType { get; } + /// + /// The type that was added to the model + /// + public Type Type => MetaType.Type; + /// + /// The model that is being changed + /// + public RuntimeTypeModel Model => MetaType.Model as RuntimeTypeModel; + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta b/Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta new file mode 100644 index 0000000..8ac9b8f --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1500030a10d2168408f75fe907ce0568 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs b/Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs new file mode 100644 index 0000000..3db0999 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs @@ -0,0 +1,64 @@ +using System; + +namespace ProtoBuf.Meta +{ + /// + /// Event arguments needed to perform type-formatting functions; this could be resolving a Type to a string suitable for serialization, or could + /// be requesting a Type from a string. If no changes are made, a default implementation will be used (from the assembly-qualified names). + /// + public class TypeFormatEventArgs : EventArgs + { + private Type type; + private string formattedName; + private readonly bool typeFixed; + /// + /// The type involved in this map; if this is initially null, a Type is expected to be provided for the string in FormattedName. + /// + public Type Type + { + get { return type; } + set + { + if (type != value) + { + if (typeFixed) throw new InvalidOperationException("The type is fixed and cannot be changed"); + type = value; + } + } + } + + /// + /// The formatted-name involved in this map; if this is initially null, a formatted-name is expected from the type in Type. + /// + public string FormattedName + { + get { return formattedName; } + set + { + if (formattedName != value) + { + if (!typeFixed) throw new InvalidOperationException("The formatted-name is fixed and cannot be changed"); + formattedName = value; + } + } + } + + internal TypeFormatEventArgs(string formattedName) + { + if (string.IsNullOrEmpty(formattedName)) throw new ArgumentNullException("formattedName"); + this.formattedName = formattedName; + // typeFixed = false; <== implicit + } + + internal TypeFormatEventArgs(Type type) + { + this.type = type ?? throw new ArgumentNullException(nameof(type)); + typeFixed = true; + } + } + + /// + /// Delegate type used to perform type-formatting functions; the sender originates as the type-model. + /// + public delegate void TypeFormatEventHandler(object sender, TypeFormatEventArgs args); +} diff --git a/Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta b/Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta new file mode 100644 index 0000000..a21c2ab --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d27afe6e96660d1418a49cf374e84ad0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs b/Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs new file mode 100644 index 0000000..9b023a6 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; + +namespace ProtoBuf.Meta +{ + partial class TypeModel : + IProtoInput, + IProtoInput>, + IProtoInput, + IProtoOutput + { + static SerializationContext CreateContext(object userState) + { + if (userState == null) + return SerializationContext.Default; + if (userState is SerializationContext ctx) + return ctx; + + var obj = new SerializationContext { Context = userState }; + obj.Freeze(); + return obj; + } + T IProtoInput.Deserialize(Stream source, T value, object userState) + => (T)Deserialize(source, value, typeof(T), CreateContext(userState)); + + T IProtoInput>.Deserialize(ArraySegment source, T value, object userState) + { + using (var ms = new MemoryStream(source.Array, source.Offset, source.Count)) + { + return (T)Deserialize(ms, value, typeof(T), CreateContext(userState)); + } + } + + T IProtoInput.Deserialize(byte[] source, T value, object userState) + { + using (var ms = new MemoryStream(source)) + { + return (T)Deserialize(ms, value, typeof(T), CreateContext(userState)); + } + } + + void IProtoOutput.Serialize(Stream destination, T value, object userState) + => Serialize(destination, value, CreateContext(userState)); + } +} diff --git a/Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta b/Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta new file mode 100644 index 0000000..80015e5 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d683bc55be70e8e46824012108beb15f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/TypeModel.cs b/Runtime/Protobuf-net/Meta/TypeModel.cs new file mode 100644 index 0000000..1867cf2 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeModel.cs @@ -0,0 +1,1696 @@ +using System; +using System.IO; + +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace ProtoBuf.Meta +{ + /// + /// Provides protobuf serialization support for a number of types + /// + public abstract partial class TypeModel + { +#if COREFX + internal TypeInfo MapType(TypeInfo type) + { + return type; + } +#endif + + /// + /// Should the Kind be included on date/time values? + /// + protected internal virtual bool SerializeDateTimeKind() { return false; } + + /// + /// Resolve a System.Type to the compiler-specific type + /// + protected internal Type MapType(Type type) + { + return MapType(type, true); + } + /// + /// Resolve a System.Type to the compiler-specific type + /// + protected internal virtual Type MapType(Type type, bool demand) + { + return type; + } + + private WireType GetWireType(ProtoTypeCode code, DataFormat format, ref Type type, out int modelKey) + { + modelKey = -1; + if (Helpers.IsEnum(type)) + { + modelKey = GetKey(ref type); + return WireType.Variant; + } + switch (code) + { + case ProtoTypeCode.Int64: + case ProtoTypeCode.UInt64: + return format == DataFormat.FixedSize ? WireType.Fixed64 : WireType.Variant; + case ProtoTypeCode.Int16: + case ProtoTypeCode.Int32: + case ProtoTypeCode.UInt16: + case ProtoTypeCode.UInt32: + case ProtoTypeCode.Boolean: + case ProtoTypeCode.SByte: + case ProtoTypeCode.Byte: + case ProtoTypeCode.Char: + return format == DataFormat.FixedSize ? WireType.Fixed32 : WireType.Variant; + case ProtoTypeCode.Double: + return WireType.Fixed64; + case ProtoTypeCode.Single: + return WireType.Fixed32; + case ProtoTypeCode.String: + case ProtoTypeCode.DateTime: + case ProtoTypeCode.Decimal: + case ProtoTypeCode.ByteArray: + case ProtoTypeCode.TimeSpan: + case ProtoTypeCode.Guid: + case ProtoTypeCode.Uri: + return WireType.String; + } + + if ((modelKey = GetKey(ref type)) >= 0) + { + return WireType.String; + } + return WireType.None; + } + + + /// + /// This is the more "complete" version of Serialize, which handles single instances of mapped types. + /// The value is written as a complete field, including field-header and (for sub-objects) a + /// length-prefix + /// In addition to that, this provides support for: + /// - basic values; individual int / string / Guid / etc + /// - IEnumerable sequences of any type handled by TrySerializeAuxiliaryType + /// + /// + internal bool TrySerializeAuxiliaryType(ProtoWriter writer, Type type, DataFormat format, int tag, object value, bool isInsideList, object parentList) + { + if (type == null) { type = value.GetType(); } + + ProtoTypeCode typecode = Helpers.GetTypeCode(type); + // note the "ref type" here normalizes against proxies + WireType wireType = GetWireType(typecode, format, ref type, out int modelKey); + + + if (modelKey >= 0) + { // write the header, but defer to the model + if (Helpers.IsEnum(type)) + { // no header + Serialize(modelKey, value, writer); + return true; + } + else + { + ProtoWriter.WriteFieldHeader(tag, wireType, writer); + switch (wireType) + { + case WireType.None: + throw ProtoWriter.CreateException(writer); + case WireType.StartGroup: + case WireType.String: + // needs a wrapping length etc + SubItemToken token = ProtoWriter.StartSubItem(value, writer); + Serialize(modelKey, value, writer); + ProtoWriter.EndSubItem(token, writer); + return true; + default: + Serialize(modelKey, value, writer); + return true; + } + } + } + + if (wireType != WireType.None) + { + ProtoWriter.WriteFieldHeader(tag, wireType, writer); + } + switch (typecode) + { + case ProtoTypeCode.Int16: ProtoWriter.WriteInt16((short)value, writer); return true; + case ProtoTypeCode.Int32: ProtoWriter.WriteInt32((int)value, writer); return true; + case ProtoTypeCode.Int64: ProtoWriter.WriteInt64((long)value, writer); return true; + case ProtoTypeCode.UInt16: ProtoWriter.WriteUInt16((ushort)value, writer); return true; + case ProtoTypeCode.UInt32: ProtoWriter.WriteUInt32((uint)value, writer); return true; + case ProtoTypeCode.UInt64: ProtoWriter.WriteUInt64((ulong)value, writer); return true; + case ProtoTypeCode.Boolean: ProtoWriter.WriteBoolean((bool)value, writer); return true; + case ProtoTypeCode.SByte: ProtoWriter.WriteSByte((sbyte)value, writer); return true; + case ProtoTypeCode.Byte: ProtoWriter.WriteByte((byte)value, writer); return true; + case ProtoTypeCode.Char: ProtoWriter.WriteUInt16((ushort)(char)value, writer); return true; + case ProtoTypeCode.Double: ProtoWriter.WriteDouble((double)value, writer); return true; + case ProtoTypeCode.Single: ProtoWriter.WriteSingle((float)value, writer); return true; + case ProtoTypeCode.DateTime: + if (SerializeDateTimeKind()) + BclHelpers.WriteDateTimeWithKind((DateTime)value, writer); + else + BclHelpers.WriteDateTime((DateTime)value, writer); + return true; + case ProtoTypeCode.Decimal: BclHelpers.WriteDecimal((decimal)value, writer); return true; + case ProtoTypeCode.String: ProtoWriter.WriteString((string)value, writer); return true; + case ProtoTypeCode.ByteArray: ProtoWriter.WriteBytes((byte[])value, writer); return true; + case ProtoTypeCode.TimeSpan: BclHelpers.WriteTimeSpan((TimeSpan)value, writer); return true; + case ProtoTypeCode.Guid: BclHelpers.WriteGuid((Guid)value, writer); return true; + case ProtoTypeCode.Uri: ProtoWriter.WriteString(((Uri)value).OriginalString, writer); return true; + } + + // by now, we should have covered all the simple cases; if we wrote a field-header, we have + // forgotten something! + Helpers.DebugAssert(wireType == WireType.None); + + // now attempt to handle sequences (including arrays and lists) + if (value is IEnumerable sequence) + { + if (isInsideList) throw CreateNestedListsNotSupported(parentList?.GetType()); + foreach (object item in sequence) + { + if (item == null) { throw new NullReferenceException(); } + if (!TrySerializeAuxiliaryType(writer, null, format, tag, item, true, sequence)) + { + ThrowUnexpectedType(item.GetType()); + } + } + return true; + } + return false; + } + + private void SerializeCore(ProtoWriter writer, object value) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + Type type = value.GetType(); + int key = GetKey(ref type); + if (key >= 0) + { + Serialize(key, value, writer); + } + else if (!TrySerializeAuxiliaryType(writer, type, DataFormat.Default, Serializer.ListItemTag, value, false, null)) + { + ThrowUnexpectedType(type); + } + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream. + /// + /// The existing instance to be serialized (cannot be null). + /// The destination stream to write to. + public void Serialize(Stream dest, object value) + { + Serialize(dest, value, null); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream. + /// + /// The existing instance to be serialized (cannot be null). + /// The destination stream to write to. + /// Additional information about this serialization operation. + public void Serialize(Stream dest, object value, SerializationContext context) + { + using (ProtoWriter writer = ProtoWriter.Create(dest, this, context)) + { + writer.SetRootObject(value); + SerializeCore(writer, value); + writer.Close(); + } + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied writer. + /// + /// The existing instance to be serialized (cannot be null). + /// The destination writer to write to. + public void Serialize(ProtoWriter dest, object value) + { + if (dest == null) throw new ArgumentNullException(nameof(dest)); + dest.CheckDepthFlushlock(); + dest.SetRootObject(value); + SerializeCore(dest, value); + dest.CheckDepthFlushlock(); + ProtoWriter.Flush(dest); + } + + /// + /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed + /// data - useful with network IO. + /// + /// The type being merged. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// How to encode the length prefix. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int fieldNumber) + => DeserializeWithLengthPrefix(source, value, type, style, fieldNumber, null, out long bytesRead); + + /// + /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed + /// data - useful with network IO. + /// + /// The type being merged. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// How to encode the length prefix. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + /// Used to resolve types on a per-field basis. + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver) + => DeserializeWithLengthPrefix(source, value, type, style, expectedField, resolver, out long bytesRead); + + /// + /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed + /// data - useful with network IO. + /// + /// The type being merged. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// How to encode the length prefix. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + /// Used to resolve types on a per-field basis. + /// Returns the number of bytes consumed by this operation (includes length-prefix overheads and any skipped data). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, out int bytesRead) + { + object result = DeserializeWithLengthPrefix(source, value, type, style, expectedField, resolver, out long bytesRead64, out bool haveObject, null); + bytesRead = checked((int)bytesRead64); + return result; + } + + /// + /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed + /// data - useful with network IO. + /// + /// The type being merged. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// How to encode the length prefix. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + /// Used to resolve types on a per-field basis. + /// Returns the number of bytes consumed by this operation (includes length-prefix overheads and any skipped data). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, out long bytesRead) => DeserializeWithLengthPrefix(source, value, type, style, expectedField, resolver, out bytesRead, out bool haveObject, null); + + private object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, out long bytesRead, out bool haveObject, SerializationContext context) + { + haveObject = false; + bool skip; + long len; + bytesRead = 0; + if (type == null && (style != PrefixStyle.Base128 || resolver == null)) + { + throw new InvalidOperationException("A type must be provided unless base-128 prefixing is being used in combination with a resolver"); + } + do + { + + bool expectPrefix = expectedField > 0 || resolver != null; + len = ProtoReader.ReadLongLengthPrefix(source, expectPrefix, style, out int actualField, out int tmpBytesRead); + if (tmpBytesRead == 0) return value; + bytesRead += tmpBytesRead; + if (len < 0) return value; + + switch (style) + { + case PrefixStyle.Base128: + if (expectPrefix && expectedField == 0 && type == null && resolver != null) + { + type = resolver(actualField); + skip = type == null; + } + else { skip = expectedField != actualField; } + break; + default: + skip = false; + break; + } + + if (skip) + { + if (len == long.MaxValue) throw new InvalidOperationException(); + ProtoReader.Seek(source, len, null); + bytesRead += len; + } + } while (skip); + + ProtoReader reader = null; + try + { + reader = ProtoReader.Create(source, this, context, len); + int key = GetKey(ref type); + if (key >= 0 && !Helpers.IsEnum(type)) + { + value = Deserialize(key, value, reader); + } + else + { + if (!(TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, true, false, null) || len == 0)) + { + TypeModel.ThrowUnexpectedType(type); // throws + } + } + bytesRead += reader.LongPosition; + haveObject = true; + return value; + } + finally + { + ProtoReader.Recycle(reader); + } + } + + /// + /// Reads a sequence of consecutive length-prefixed items from a stream, using + /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag + /// are directly comparable to serializing multiple items in succession + /// (use the tag to emulate the implicit behavior + /// when serializing a list/array). When a tag is + /// specified, any records with different tags are silently omitted. The + /// tag is ignored. The tag is ignores for fixed-length prefixes. + /// + /// The binary stream containing the serialized records. + /// The prefix style used in the data. + /// The tag of records to return (if non-positive, then no tag is + /// expected and all records are returned). + /// On a field-by-field basis, the type of object to deserialize (can be null if "type" is specified). + /// The type of object to deserialize (can be null if "resolver" is specified). + /// The sequence of deserialized objects. + public IEnumerable DeserializeItems(System.IO.Stream source, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver) + { + return DeserializeItems(source, type, style, expectedField, resolver, null); + } + /// + /// Reads a sequence of consecutive length-prefixed items from a stream, using + /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag + /// are directly comparable to serializing multiple items in succession + /// (use the tag to emulate the implicit behavior + /// when serializing a list/array). When a tag is + /// specified, any records with different tags are silently omitted. The + /// tag is ignored. The tag is ignores for fixed-length prefixes. + /// + /// The binary stream containing the serialized records. + /// The prefix style used in the data. + /// The tag of records to return (if non-positive, then no tag is + /// expected and all records are returned). + /// On a field-by-field basis, the type of object to deserialize (can be null if "type" is specified). + /// The type of object to deserialize (can be null if "resolver" is specified). + /// The sequence of deserialized objects. + /// Additional information about this serialization operation. + public IEnumerable DeserializeItems(System.IO.Stream source, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, SerializationContext context) + { + return new DeserializeItemsIterator(this, source, type, style, expectedField, resolver, context); + } + + /// + /// Reads a sequence of consecutive length-prefixed items from a stream, using + /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag + /// are directly comparable to serializing multiple items in succession + /// (use the tag to emulate the implicit behavior + /// when serializing a list/array). When a tag is + /// specified, any records with different tags are silently omitted. The + /// tag is ignored. The tag is ignores for fixed-length prefixes. + /// + /// The type of object to deserialize. + /// The binary stream containing the serialized records. + /// The prefix style used in the data. + /// The tag of records to return (if non-positive, then no tag is + /// expected and all records are returned). + /// The sequence of deserialized objects. + public IEnumerable DeserializeItems(Stream source, PrefixStyle style, int expectedField) + { + return DeserializeItems(source, style, expectedField, null); + } + /// + /// Reads a sequence of consecutive length-prefixed items from a stream, using + /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag + /// are directly comparable to serializing multiple items in succession + /// (use the tag to emulate the implicit behavior + /// when serializing a list/array). When a tag is + /// specified, any records with different tags are silently omitted. The + /// tag is ignored. The tag is ignores for fixed-length prefixes. + /// + /// The type of object to deserialize. + /// The binary stream containing the serialized records. + /// The prefix style used in the data. + /// The tag of records to return (if non-positive, then no tag is + /// expected and all records are returned). + /// The sequence of deserialized objects. + /// Additional information about this serialization operation. + public IEnumerable DeserializeItems(Stream source, PrefixStyle style, int expectedField, SerializationContext context) + { + return new DeserializeItemsIterator(this, source, style, expectedField, context); + } + + private sealed class DeserializeItemsIterator : DeserializeItemsIterator, + IEnumerator, + IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() { return this; } + public new T Current { get { return (T)base.Current; } } + void IDisposable.Dispose() { } + public DeserializeItemsIterator(TypeModel model, Stream source, PrefixStyle style, int expectedField, SerializationContext context) + : base(model, source, model.MapType(typeof(T)), style, expectedField, null, context) { } + } + + private class DeserializeItemsIterator : IEnumerator, IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() { return this; } + private bool haveObject; + private object current; + public bool MoveNext() + { + if (haveObject) + { + current = model.DeserializeWithLengthPrefix(source, null, type, style, expectedField, resolver, out long bytesRead, out haveObject, context); + } + return haveObject; + } + void IEnumerator.Reset() { throw new NotSupportedException(); } + public object Current { get { return current; } } + private readonly Stream source; + private readonly Type type; + private readonly PrefixStyle style; + private readonly int expectedField; + private readonly Serializer.TypeResolver resolver; + private readonly TypeModel model; + private readonly SerializationContext context; + public DeserializeItemsIterator(TypeModel model, Stream source, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, SerializationContext context) + { + haveObject = true; + this.source = source; + this.type = type; + this.style = style; + this.expectedField = expectedField; + this.resolver = resolver; + this.model = model; + this.context = context; + } + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream, + /// with a length-prefix. This is useful for socket programming, + /// as DeserializeWithLengthPrefix can be used to read the single object back + /// from an ongoing stream. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// How to encode the length prefix. + /// The destination stream to write to. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + public void SerializeWithLengthPrefix(Stream dest, object value, Type type, PrefixStyle style, int fieldNumber) + { + SerializeWithLengthPrefix(dest, value, type, style, fieldNumber, null); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream, + /// with a length-prefix. This is useful for socket programming, + /// as DeserializeWithLengthPrefix can be used to read the single object back + /// from an ongoing stream. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// How to encode the length prefix. + /// The destination stream to write to. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + /// Additional information about this serialization operation. + public void SerializeWithLengthPrefix(Stream dest, object value, Type type, PrefixStyle style, int fieldNumber, SerializationContext context) + { + if (type == null) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + type = MapType(value.GetType()); + } + int key = GetKey(ref type); + using (ProtoWriter writer = ProtoWriter.Create(dest, this, context)) + { + switch (style) + { + case PrefixStyle.None: + Serialize(key, value, writer); + break; + case PrefixStyle.Base128: + case PrefixStyle.Fixed32: + case PrefixStyle.Fixed32BigEndian: + ProtoWriter.WriteObject(value, key, writer, style, fieldNumber); + break; + default: + throw new ArgumentOutOfRangeException("style"); + } + writer.Close(); + } + } + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object Deserialize(Stream source, object value, Type type) + { + return Deserialize(source, value, type, null); + } + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + /// Additional information about this serialization operation. + public object Deserialize(Stream source, object value, Type type, SerializationContext context) + { + bool autoCreate = PrepareDeserialize(value, ref type); + ProtoReader reader = null; + try + { + reader = ProtoReader.Create(source, this, context, ProtoReader.TO_EOF); + if (value != null) reader.SetRootObject(value); + object obj = DeserializeCore(reader, type, value, autoCreate); + reader.CheckFullyConsumed(); + return obj; + } + finally + { + ProtoReader.Recycle(reader); + } + } + + private bool PrepareDeserialize(object value, ref Type type) + { + if (type == null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(type)); + } + else + { + type = MapType(value.GetType()); + } + } + + bool autoCreate = true; + Type underlyingType = Helpers.GetUnderlyingType(type); + if (underlyingType != null) + { + type = underlyingType; + autoCreate = false; + } + return autoCreate; + } + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The number of bytes to consume. + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object Deserialize(Stream source, object value, System.Type type, int length) + => Deserialize(source, value, type, length, null); + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The number of bytes to consume. + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object Deserialize(Stream source, object value, System.Type type, long length) + => Deserialize(source, value, type, length, null); + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The number of bytes to consume (or -1 to read to the end of the stream). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + /// Additional information about this serialization operation. + public object Deserialize(Stream source, object value, System.Type type, int length, SerializationContext context) + => Deserialize(source, value, type, length == int.MaxValue ? long.MaxValue : (long)length, context); + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The number of bytes to consume (or -1 to read to the end of the stream). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + /// Additional information about this serialization operation. + public object Deserialize(Stream source, object value, System.Type type, long length, SerializationContext context) + { + bool autoCreate = PrepareDeserialize(value, ref type); + ProtoReader reader = null; + try + { + reader = ProtoReader.Create(source, this, context, length); + if (value != null) reader.SetRootObject(value); + object obj = DeserializeCore(reader, type, value, autoCreate); + reader.CheckFullyConsumed(); + return obj; + } + finally + { + ProtoReader.Recycle(reader); + } + } + + /// + /// Applies a protocol-buffer reader to an existing instance (which may be null). + /// + /// The type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The reader to apply to the instance (cannot be null). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public object Deserialize(ProtoReader source, object value, System.Type type) + { + if (source == null) throw new ArgumentNullException("source"); + bool autoCreate = PrepareDeserialize(value, ref type); + if (value != null) source.SetRootObject(value); + object obj = DeserializeCore(source, type, value, autoCreate); + source.CheckFullyConsumed(); + return obj; + } + + private object DeserializeCore(ProtoReader reader, Type type, object value, bool noAutoCreate) + { + int key = GetKey(ref type); + if (key >= 0 && !Helpers.IsEnum(type)) + { + return Deserialize(key, value, reader); + } + // this returns true to say we actively found something, but a value is assigned either way (or throws) + TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, noAutoCreate, false, null); + return value; + } + +#if COREFX + private static readonly System.Reflection.TypeInfo ilist = typeof(IList).GetTypeInfo(); +#else + private static readonly System.Type ilist = typeof(IList); +#endif + internal static MethodInfo ResolveListAdd(TypeModel model, Type listType, Type itemType, out bool isList) + { +#if COREFX || PROFILE259 + TypeInfo listTypeInfo = listType.GetTypeInfo(); +#else + Type listTypeInfo = listType; +#endif +#if PROFILE259 + isList = model.MapType(ilist).GetTypeInfo().IsAssignableFrom(listTypeInfo); +#else + isList = model.MapType(ilist).IsAssignableFrom(listTypeInfo); +#endif + Type[] types = { itemType }; + MethodInfo add = Helpers.GetInstanceMethod(listTypeInfo, "Add", types); + +#if !NO_GENERICS + if (add == null) + { // fallback: look for ICollection's Add(typedObject) method + + bool forceList = listTypeInfo.IsInterface && + model.MapType(typeof(System.Collections.Generic.IEnumerable<>)).MakeGenericType(types) +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + .IsAssignableFrom(listTypeInfo); + +#if COREFX || PROFILE259 + TypeInfo constuctedListType = typeof(System.Collections.Generic.ICollection<>).MakeGenericType(types).GetTypeInfo(); +#else + Type constuctedListType = model.MapType(typeof(System.Collections.Generic.ICollection<>)).MakeGenericType(types); +#endif + if (forceList || constuctedListType.IsAssignableFrom(listTypeInfo)) + { + add = Helpers.GetInstanceMethod(constuctedListType, "Add", types); + } + } + + if (add == null) + { + +#if COREFX || PROFILE259 + foreach (Type tmpType in listTypeInfo.ImplementedInterfaces) +#else + foreach (Type interfaceType in listTypeInfo.GetInterfaces()) +#endif + { +#if COREFX || PROFILE259 + TypeInfo interfaceType = tmpType.GetTypeInfo(); +#endif + if (interfaceType.Name == "IProducerConsumerCollection`1" && interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition().FullName == "System.Collections.Concurrent.IProducerConsumerCollection`1") + { + add = Helpers.GetInstanceMethod(interfaceType, "TryAdd", types); + if (add != null) break; + } + } + } +#endif + + if (add == null) + { // fallback: look for a public list.Add(object) method + types[0] = model.MapType(typeof(object)); + add = Helpers.GetInstanceMethod(listTypeInfo, "Add", types); + } + if (add == null && isList) + { // fallback: look for IList's Add(object) method + add = Helpers.GetInstanceMethod(model.MapType(ilist), "Add", types); + } + return add; + } + internal static Type GetListItemType(TypeModel model, Type listType) + { + Helpers.DebugAssert(listType != null); + +#if PROFILE259 + TypeInfo listTypeInfo = listType.GetTypeInfo(); + if (listType == typeof(string) || listType.IsArray + || !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(listTypeInfo)) return null; +#else + if (listType == model.MapType(typeof(string)) || listType.IsArray + || !model.MapType(typeof(IEnumerable)).IsAssignableFrom(listType)) return null; +#endif + + BasicList candidates = new BasicList(); +#if PROFILE259 + foreach (MethodInfo method in listType.GetRuntimeMethods()) +#else + foreach (MethodInfo method in listType.GetMethods()) +#endif + { + if (method.IsStatic || method.Name != "Add") continue; + ParameterInfo[] parameters = method.GetParameters(); + Type paramType; + if (parameters.Length == 1 && !candidates.Contains(paramType = parameters[0].ParameterType)) + { + candidates.Add(paramType); + } + } + + string name = listType.Name; + bool isQueueStack = name != null && (name.IndexOf("Queue") >= 0 || name.IndexOf("Stack") >= 0); + + if (!isQueueStack) + { + TestEnumerableListPatterns(model, candidates, listType); +#if PROFILE259 + foreach (Type iType in listTypeInfo.ImplementedInterfaces) + { + TestEnumerableListPatterns(model, candidates, iType); + } +#else + foreach (Type iType in listType.GetInterfaces()) + { + TestEnumerableListPatterns(model, candidates, iType); + } +#endif + } + +#if PROFILE259 + // more convenient GetProperty overload not supported on all platforms + foreach (PropertyInfo indexer in listType.GetRuntimeProperties()) + { + if (indexer.Name != "Item" || candidates.Contains(indexer.PropertyType)) continue; + ParameterInfo[] args = indexer.GetIndexParameters(); + if (args.Length != 1 || args[0].ParameterType != typeof(int)) continue; + MethodInfo getter = indexer.GetMethod; + if (getter == null || getter.IsStatic) continue; + candidates.Add(indexer.PropertyType); + } +#else + // more convenient GetProperty overload not supported on all platforms + foreach (PropertyInfo indexer in listType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (indexer.Name != "Item" || candidates.Contains(indexer.PropertyType)) continue; + ParameterInfo[] args = indexer.GetIndexParameters(); + if (args.Length != 1 || args[0].ParameterType != model.MapType(typeof(int))) continue; + candidates.Add(indexer.PropertyType); + } +#endif + + switch (candidates.Count) + { + case 0: + return null; + case 1: + if ((Type)candidates[0] == listType) return null; // recursive + return (Type)candidates[0]; + case 2: + if ((Type)candidates[0] != listType && CheckDictionaryAccessors(model, (Type)candidates[0], (Type)candidates[1])) return (Type)candidates[0]; + if ((Type)candidates[1] != listType && CheckDictionaryAccessors(model, (Type)candidates[1], (Type)candidates[0])) return (Type)candidates[1]; + break; + } + + return null; + } + + private static void TestEnumerableListPatterns(TypeModel model, BasicList candidates, Type iType) + { + +#if COREFX || PROFILE259 + TypeInfo iTypeInfo = iType.GetTypeInfo(); + if (iTypeInfo.IsGenericType) + { + Type typeDef = iTypeInfo.GetGenericTypeDefinition(); + if( + typeDef == model.MapType(typeof(System.Collections.Generic.IEnumerable<>)) + || typeDef == model.MapType(typeof(System.Collections.Generic.ICollection<>)) + || typeDef.GetTypeInfo().FullName == "System.Collections.Concurrent.IProducerConsumerCollection`1") + { + + Type[] iTypeArgs = iTypeInfo.GenericTypeArguments; + if (!candidates.Contains(iTypeArgs[0])) + { + candidates.Add(iTypeArgs[0]); + } + } + } +#else + if (iType.IsGenericType) + { + Type typeDef = iType.GetGenericTypeDefinition(); + if (typeDef == model.MapType(typeof(System.Collections.Generic.IEnumerable<>)) + || typeDef == model.MapType(typeof(System.Collections.Generic.ICollection<>)) + || typeDef.FullName == "System.Collections.Concurrent.IProducerConsumerCollection`1") + { + Type[] iTypeArgs = iType.GetGenericArguments(); + if (!candidates.Contains(iTypeArgs[0])) + { + candidates.Add(iTypeArgs[0]); + } + } + } +#endif + } + + private static bool CheckDictionaryAccessors(TypeModel model, Type pair, Type value) + { +#if COREFX || PROFILE259 + TypeInfo finalType = pair.GetTypeInfo(); + return finalType.IsGenericType && finalType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>) + && finalType.GenericTypeArguments[1] == value; +#else + return pair.IsGenericType && pair.GetGenericTypeDefinition() == model.MapType(typeof(System.Collections.Generic.KeyValuePair<,>)) + && pair.GetGenericArguments()[1] == value; +#endif + } + + private bool TryDeserializeList(TypeModel model, ProtoReader reader, DataFormat format, int tag, Type listType, Type itemType, ref object value) + { + MethodInfo addMethod = TypeModel.ResolveListAdd(model, listType, itemType, out bool isList); + if (addMethod == null) throw new NotSupportedException("Unknown list variant: " + listType.FullName); + bool found = false; + object nextItem = null; + IList list = value as IList; + object[] args = isList ? null : new object[1]; + BasicList arraySurrogate = listType.IsArray ? new BasicList() : null; + + while (TryDeserializeAuxiliaryType(reader, format, tag, itemType, ref nextItem, true, true, true, true, value ?? listType)) + { + found = true; + if (value == null && arraySurrogate == null) + { + value = CreateListInstance(listType, itemType); + list = value as IList; + } + if (list != null) + { + list.Add(nextItem); + } + else if (arraySurrogate != null) + { + arraySurrogate.Add(nextItem); + } + else + { + args[0] = nextItem; + addMethod.Invoke(value, args); + } + nextItem = null; + } + if (arraySurrogate != null) + { + Array newArray; + if (value != null) + { + if (arraySurrogate.Count == 0) + { // we'll stay with what we had, thanks + } + else + { + Array existing = (Array)value; + newArray = Array.CreateInstance(itemType, existing.Length + arraySurrogate.Count); + Array.Copy(existing, newArray, existing.Length); + arraySurrogate.CopyTo(newArray, existing.Length); + value = newArray; + } + } + else + { + newArray = Array.CreateInstance(itemType, arraySurrogate.Count); + arraySurrogate.CopyTo(newArray, 0); + value = newArray; + } + } + return found; + } + + private static object CreateListInstance(Type listType, Type itemType) + { + Type concreteListType = listType; + + if (listType.IsArray) + { + return Array.CreateInstance(itemType, 0); + } + +#if COREFX || PROFILE259 + TypeInfo listTypeInfo = listType.GetTypeInfo(); + if (!listTypeInfo.IsClass || listTypeInfo.IsAbstract || + Helpers.GetConstructor(listTypeInfo, Helpers.EmptyTypes, true) == null) +#else + if (!listType.IsClass || listType.IsAbstract || + Helpers.GetConstructor(listType, Helpers.EmptyTypes, true) == null) +#endif + { + string fullName; + bool handled = false; +#if COREFX || PROFILE259 + if (listTypeInfo.IsInterface && +#else + if (listType.IsInterface && +#endif + (fullName = listType.FullName) != null && fullName.IndexOf("Dictionary") >= 0) // have to try to be frugal here... + { +#if COREFX || PROFILE259 + TypeInfo finalType = listType.GetTypeInfo(); + if (finalType.IsGenericType && finalType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>)) + { + Type[] genericTypes = listType.GenericTypeArguments; + concreteListType = typeof(System.Collections.Generic.Dictionary<,>).MakeGenericType(genericTypes); + handled = true; + } +#else + if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>)) + { + Type[] genericTypes = listType.GetGenericArguments(); + concreteListType = typeof(System.Collections.Generic.Dictionary<,>).MakeGenericType(genericTypes); + handled = true; + } +#endif + +#if !PORTABLE && !COREFX && !PROFILE259 + if (!handled && listType == typeof(IDictionary)) + { + concreteListType = typeof(Hashtable); + handled = true; + } +#endif + } + + if (!handled) + { + concreteListType = typeof(System.Collections.Generic.List<>).MakeGenericType(itemType); + handled = true; + } + +#if !PORTABLE && !COREFX && !PROFILE259 + if (!handled) + { + concreteListType = typeof(ArrayList); + handled = true; + } +#endif + } + return Activator.CreateInstance(concreteListType); + } + + /// + /// This is the more "complete" version of Deserialize, which handles single instances of mapped types. + /// The value is read as a complete field, including field-header and (for sub-objects) a + /// length-prefix..kmc + /// + /// In addition to that, this provides support for: + /// - basic values; individual int / string / Guid / etc + /// - IList sets of any type handled by TryDeserializeAuxiliaryType + /// + internal bool TryDeserializeAuxiliaryType(ProtoReader reader, DataFormat format, int tag, Type type, ref object value, bool skipOtherFields, bool asListItem, bool autoCreate, bool insideList, object parentListOrType) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + Type itemType = null; + ProtoTypeCode typecode = Helpers.GetTypeCode(type); + WireType wiretype = GetWireType(typecode, format, ref type, out int modelKey); + + bool found = false; + if (wiretype == WireType.None) + { + itemType = GetListItemType(this, type); + if (itemType == null && type.IsArray && type.GetArrayRank() == 1 && type != typeof(byte[])) + { + itemType = type.GetElementType(); + } + if (itemType != null) + { + if (insideList) throw TypeModel.CreateNestedListsNotSupported((parentListOrType as Type) ?? (parentListOrType?.GetType())); + found = TryDeserializeList(this, reader, format, tag, type, itemType, ref value); + if (!found && autoCreate) + { + value = CreateListInstance(type, itemType); + } + return found; + } + + // otherwise, not a happy bunny... + ThrowUnexpectedType(type); + } + + // to treat correctly, should read all values + + while (true) + { + // for convenience (re complex exit conditions), additional exit test here: + // if we've got the value, are only looking for one, and we aren't a list - then exit + if (found && asListItem) break; + + + // read the next item + int fieldNumber = reader.ReadFieldHeader(); + if (fieldNumber <= 0) break; + if (fieldNumber != tag) + { + if (skipOtherFields) + { + reader.SkipField(); + continue; + } + throw ProtoReader.AddErrorData(new InvalidOperationException( + "Expected field " + tag.ToString() + ", but found " + fieldNumber.ToString()), reader); + } + found = true; + reader.Hint(wiretype); // handle signed data etc + + if (modelKey >= 0) + { + switch (wiretype) + { + case WireType.String: + case WireType.StartGroup: + SubItemToken token = ProtoReader.StartSubItem(reader); + value = Deserialize(modelKey, value, reader); + ProtoReader.EndSubItem(token, reader); + continue; + default: + value = Deserialize(modelKey, value, reader); + continue; + } + } + switch (typecode) + { + case ProtoTypeCode.Int16: value = reader.ReadInt16(); continue; + case ProtoTypeCode.Int32: value = reader.ReadInt32(); continue; + case ProtoTypeCode.Int64: value = reader.ReadInt64(); continue; + case ProtoTypeCode.UInt16: value = reader.ReadUInt16(); continue; + case ProtoTypeCode.UInt32: value = reader.ReadUInt32(); continue; + case ProtoTypeCode.UInt64: value = reader.ReadUInt64(); continue; + case ProtoTypeCode.Boolean: value = reader.ReadBoolean(); continue; + case ProtoTypeCode.SByte: value = reader.ReadSByte(); continue; + case ProtoTypeCode.Byte: value = reader.ReadByte(); continue; + case ProtoTypeCode.Char: value = (char)reader.ReadUInt16(); continue; + case ProtoTypeCode.Double: value = reader.ReadDouble(); continue; + case ProtoTypeCode.Single: value = reader.ReadSingle(); continue; + case ProtoTypeCode.DateTime: value = BclHelpers.ReadDateTime(reader); continue; + case ProtoTypeCode.Decimal: value = BclHelpers.ReadDecimal(reader); continue; + case ProtoTypeCode.String: value = reader.ReadString(); continue; + case ProtoTypeCode.ByteArray: value = ProtoReader.AppendBytes((byte[])value, reader); continue; + case ProtoTypeCode.TimeSpan: value = BclHelpers.ReadTimeSpan(reader); continue; + case ProtoTypeCode.Guid: value = BclHelpers.ReadGuid(reader); continue; + case ProtoTypeCode.Uri: value = new Uri(reader.ReadString(), UriKind.RelativeOrAbsolute); continue; + } + + } + if (!found && !asListItem && autoCreate) + { + if (type != typeof(string)) + { + value = Activator.CreateInstance(type); + } + } + return found; + } + +#if !NO_RUNTIME + /// + /// Creates a new runtime model, to which the caller + /// can add support for a range of types. A model + /// can be used "as is", or can be compiled for + /// optimal performance. + /// + [Obsolete("Please use RuntimeTypeModel.Create", false)] + public static RuntimeTypeModel Create() + { + return RuntimeTypeModel.Create(); + } +#endif + + /// + /// Applies common proxy scenarios, resolving the actual type to consider + /// + protected internal static Type ResolveProxies(Type type) + { + if (type == null) return null; +#if !NO_GENERICS + if (type.IsGenericParameter) return null; + // Nullable + Type tmp = Helpers.GetUnderlyingType(type); + if (tmp != null) return tmp; +#endif + +#if !CF + // EF POCO + string fullName = type.FullName; + if (fullName != null && fullName.StartsWith("System.Data.Entity.DynamicProxies.")) + { +#if COREFX || PROFILE259 + return type.GetTypeInfo().BaseType; +#else + return type.BaseType; +#endif + } + + // NHibernate +#if PROFILE259 + IEnumerable interfaces = type.GetTypeInfo().ImplementedInterfaces; +#else + Type[] interfaces = type.GetInterfaces(); +#endif + foreach (Type t in interfaces) + { + switch (t.FullName) + { + case "NHibernate.Proxy.INHibernateProxy": + case "NHibernate.Proxy.DynamicProxy.IProxy": + case "NHibernate.Intercept.IFieldInterceptorAccessor": +#if COREFX || PROFILE259 + return type.GetTypeInfo().BaseType; +#else + return type.BaseType; +#endif + } + } +#endif + return null; + } + + /// + /// Indicates whether the supplied type is explicitly modelled by the model + /// + public bool IsDefined(Type type) => GetKey(ref type) >= 0; + + readonly Dictionary knownKeys = new Dictionary(); + + // essentially just a ValueTuple - I just don't want the extra dependency + private readonly struct KnownTypeKey + { + public KnownTypeKey(Type type, int key) + { + Type = type; + Key = key; + } + + public int Key { get; } + + public Type Type { get; } + } + + /// + /// Provides the key that represents a given type in the current model. + /// The type is also normalized for proxies at the same time. + /// + protected internal int GetKey(ref Type type) + { + if (type == null) return -1; + int key; + lock (knownKeys) + { + if (knownKeys.TryGetValue(type, out var tuple)) + { + // the type can be changed via ResolveProxies etc +#if DEBUG + var actualKey = GetKeyImpl(type); + if(actualKey != tuple.Key) + { + throw new InvalidOperationException( + $"Key cache failure; got {tuple.Key} instead of {actualKey} for '{type.Name}'"); + } +#endif + type = tuple.Type; + return tuple.Key; + } + } + key = GetKeyImpl(type); + Type originalType = type; + if (key < 0) + { + Type normalized = ResolveProxies(type); + if (normalized != null && normalized != type) + { + type = normalized; // hence ref + key = GetKeyImpl(type); + } + } + lock (knownKeys) + { + knownKeys[originalType] = new KnownTypeKey(type, key); + } + return key; + } + + /// + /// Advertise that a type's key can have changed + /// + internal void ResetKeyCache() + { + // clear *everything* (think: multi-level - can be many descendents) + lock(knownKeys) + { + knownKeys.Clear(); + } + } + + /// + /// Provides the key that represents a given type in the current model. + /// + protected abstract int GetKeyImpl(Type type); + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream. + /// + /// Represents the type (including inheritance) to consider. + /// The existing instance to be serialized (cannot be null). + /// The destination stream to write to. + protected internal abstract void Serialize(int key, object value, ProtoWriter dest); + + /// + /// Applies a protocol-buffer stream to an existing instance (which may be null). + /// + /// Represents the type (including inheritance) to consider. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + protected internal abstract object Deserialize(int key, object value, ProtoReader source); + + //internal ProtoSerializer Create(IProtoSerializer head) + //{ + // return new RuntimeSerializer(head, this); + //} + //internal ProtoSerializer Compile + + /// + /// Indicates the type of callback to be used + /// + protected internal enum CallbackType + { + /// + /// Invoked before an object is serialized + /// + BeforeSerialize, + /// + /// Invoked after an object is serialized + /// + AfterSerialize, + /// + /// Invoked before an object is deserialized (or when a new instance is created) + /// + BeforeDeserialize, + /// + /// Invoked after an object is deserialized + /// + AfterDeserialize + } + + /// + /// Create a deep clone of the supplied instance; any sub-items are also cloned. + /// + public object DeepClone(object value) + { + if (value == null) return null; + Type type = value.GetType(); + int key = GetKey(ref type); + + if (key >= 0 && !Helpers.IsEnum(type)) + { + using (MemoryStream ms = new MemoryStream()) + { + using (ProtoWriter writer = ProtoWriter.Create(ms, this, null)) + { + writer.SetRootObject(value); + Serialize(key, value, writer); + writer.Close(); + } + ms.Position = 0; + ProtoReader reader = null; + try + { + reader = ProtoReader.Create(ms, this, null, ProtoReader.TO_EOF); + return Deserialize(key, null, reader); + } + finally + { + ProtoReader.Recycle(reader); + } + } + } + if (type == typeof(byte[])) + { + byte[] orig = (byte[])value, clone = new byte[orig.Length]; + Buffer.BlockCopy(orig, 0, clone, 0, orig.Length); + return clone; + } + else if (GetWireType(Helpers.GetTypeCode(type), DataFormat.Default, ref type, out int modelKey) != WireType.None && modelKey < 0) + { // immutable; just return the original value + return value; + } + using (MemoryStream ms = new MemoryStream()) + { + using (ProtoWriter writer = ProtoWriter.Create(ms, this, null)) + { + if (!TrySerializeAuxiliaryType(writer, type, DataFormat.Default, Serializer.ListItemTag, value, false, null)) ThrowUnexpectedType(type); + writer.Close(); + } + ms.Position = 0; + ProtoReader reader = null; + try + { + reader = ProtoReader.Create(ms, this, null, ProtoReader.TO_EOF); + value = null; // start from scratch! + TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, true, false, null); + return value; + } + finally + { + ProtoReader.Recycle(reader); + } + } + } + + /// + /// Indicates that while an inheritance tree exists, the exact type encountered was not + /// specified in that hierarchy and cannot be processed. + /// + protected internal static void ThrowUnexpectedSubtype(Type expected, Type actual) + { + if (expected != TypeModel.ResolveProxies(actual)) + { + throw new InvalidOperationException("Unexpected sub-type: " + actual.FullName); + } + } + + /// + /// Indicates that the given type was not expected, and cannot be processed. + /// + protected internal static void ThrowUnexpectedType(Type type) + { + string fullName = type == null ? "(unknown)" : type.FullName; + + if (type != null) + { + Type baseType = type +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + .BaseType; + if (baseType != null && baseType +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + .IsGenericType && baseType.GetGenericTypeDefinition().Name == "GeneratedMessage`2") + { + throw new InvalidOperationException( + "Are you mixing protobuf-net and protobuf-csharp-port? See https://stackoverflow.com/q/11564914/23354; type: " + fullName); + } + } + + throw new InvalidOperationException("Type is not expected, and no contract can be inferred: " + fullName); + } + + internal static Exception CreateNestedListsNotSupported(Type type) + { + return new NotSupportedException("Nested or jagged lists and arrays are not supported: " + (type?.FullName ?? "(null)")); + } + + /// + /// Indicates that the given type cannot be constructed; it may still be possible to + /// deserialize into existing instances. + /// + public static void ThrowCannotCreateInstance(Type type) + { + throw new ProtoException("No parameterless constructor found for " + (type?.FullName ?? "(null)")); + } + + internal static string SerializeType(TypeModel model, System.Type type) + { + if (model != null) + { + TypeFormatEventHandler handler = model.DynamicTypeFormatting; + if (handler != null) + { + TypeFormatEventArgs args = new TypeFormatEventArgs(type); + handler(model, args); + if (!string.IsNullOrEmpty(args.FormattedName)) return args.FormattedName; + } + } + return type.AssemblyQualifiedName; + } + + internal static Type DeserializeType(TypeModel model, string value) + { + + if (model != null) + { + TypeFormatEventHandler handler = model.DynamicTypeFormatting; + if (handler != null) + { + TypeFormatEventArgs args = new TypeFormatEventArgs(value); + handler(model, args); + if (args.Type != null) return args.Type; + } + } + return Type.GetType(value); + } + + /// + /// Returns true if the type supplied is either a recognised contract type, + /// or a *list* of a recognised contract type. + /// + /// Note that primitives always return false, even though the engine + /// will, if forced, try to serialize such + /// True if this type is recognised as a serializable entity, else false + public bool CanSerializeContractType(Type type) => CanSerialize(type, false, true, true); + + /// + /// Returns true if the type supplied is a basic type with inbuilt handling, + /// a recognised contract type, or a *list* of a basic / contract type. + /// + public bool CanSerialize(Type type) => CanSerialize(type, true, true, true); + + /// + /// Returns true if the type supplied is a basic type with inbuilt handling, + /// or a *list* of a basic type with inbuilt handling + /// + public bool CanSerializeBasicType(Type type) => CanSerialize(type, true, false, true); + + private bool CanSerialize(Type type, bool allowBasic, bool allowContract, bool allowLists) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + Type tmp = Helpers.GetUnderlyingType(type); + if (tmp != null) type = tmp; + + // is it a basic type? + ProtoTypeCode typeCode = Helpers.GetTypeCode(type); + switch (typeCode) + { + case ProtoTypeCode.Empty: + case ProtoTypeCode.Unknown: + break; + default: + return allowBasic; // well-known basic type + } + int modelKey = GetKey(ref type); + if (modelKey >= 0) return allowContract; // known contract type + + // is it a list? + if (allowLists) + { + Type itemType = null; + if (type.IsArray) + { // note we don't need to exclude byte[], as that is handled by GetTypeCode already + if (type.GetArrayRank() == 1) itemType = type.GetElementType(); + } + else + { + itemType = GetListItemType(this, type); + } + if (itemType != null) return CanSerialize(itemType, allowBasic, allowContract, false); + } + return false; + } + + /// + /// Suggest a .proto definition for the given type + /// + /// The type to generate a .proto definition for, or null to generate a .proto that represents the entire model + /// The .proto definition as a string + public virtual string GetSchema(Type type) => GetSchema(type, ProtoSyntax.Proto2); + + /// + /// Suggest a .proto definition for the given type + /// + /// The type to generate a .proto definition for, or null to generate a .proto that represents the entire model + /// The .proto definition as a string + /// The .proto syntax to use for the operation + public virtual string GetSchema(Type type, ProtoSyntax syntax) + { + throw new NotSupportedException(); + } + + /// + /// Used to provide custom services for writing and parsing type names when using dynamic types. Both parsing and formatting + /// are provided on a single API as it is essential that both are mapped identically at all times. + /// + public event TypeFormatEventHandler DynamicTypeFormatting; + +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + /// + /// Creates a new IFormatter that uses protocol-buffer [de]serialization. + /// + /// A new IFormatter to be used during [de]serialization. + /// The type of object to be [de]deserialized by the formatter. + public System.Runtime.Serialization.IFormatter CreateFormatter(Type type) + { + return new Formatter(this, type); + } + + internal sealed class Formatter : System.Runtime.Serialization.IFormatter + { + private readonly TypeModel model; + private readonly Type type; + internal Formatter(TypeModel model, Type type) + { + this.model = model ?? throw new ArgumentNullException(nameof(model)); + this.type = type ?? throw new ArgumentNullException(nameof(type)); + } + private System.Runtime.Serialization.SerializationBinder binder; + public System.Runtime.Serialization.SerializationBinder Binder + { + get { return binder; } + set { binder = value; } + } + + private System.Runtime.Serialization.StreamingContext context; + public System.Runtime.Serialization.StreamingContext Context + { + get { return context; } + set { context = value; } + } + + public object Deserialize(Stream source) + { + return model.Deserialize(source, null, type, (long)-1, Context); + } + + public void Serialize(Stream destination, object graph) + { + model.Serialize(destination, graph, Context); + } + + private System.Runtime.Serialization.ISurrogateSelector surrogateSelector; + public System.Runtime.Serialization.ISurrogateSelector SurrogateSelector + { + get { return surrogateSelector; } + set { surrogateSelector = value; } + } + } +#endif + +#if DEBUG // this is used by some unit tests only, to ensure no buffering when buffering is disabled + private bool forwardsOnly; + /// + /// If true, buffering of nested objects is disabled + /// + public bool ForwardsOnly + { + get { return forwardsOnly; } + set { forwardsOnly = value; } + } +#endif + + internal virtual Type GetType(string fullName, Assembly context) + { + return ResolveKnownType(fullName, this, context); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + internal static Type ResolveKnownType(string name, TypeModel model, Assembly assembly) + { + if (string.IsNullOrEmpty(name)) return null; + try + { + Type type = Type.GetType(name); + + if (type != null) return type; + } + catch { } + try + { + int i = name.IndexOf(','); + string fullName = (i > 0 ? name.Substring(0, i) : name).Trim(); +#if !(COREFX || PROFILE259) + if (assembly == null) assembly = Assembly.GetCallingAssembly(); +#endif + Type type = assembly?.GetType(fullName); + if (type != null) return type; + } + catch { } + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/TypeModel.cs.meta b/Runtime/Protobuf-net/Meta/TypeModel.cs.meta new file mode 100644 index 0000000..cc869c3 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/TypeModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5eb182ec8bc8c5469c7819c0e3f7fb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Meta/ValueMember.cs b/Runtime/Protobuf-net/Meta/ValueMember.cs new file mode 100644 index 0000000..9566312 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/ValueMember.cs @@ -0,0 +1,855 @@ +#if !NO_RUNTIME +using System; + +using ProtoBuf.Serializers; +using System.Globalization; +using System.Collections.Generic; + +#if PROFILE259 +using System.Reflection; +using System.Linq; +#else +using System.Reflection; +#endif + +namespace ProtoBuf.Meta +{ + /// + /// Represents a member (property/field) that is mapped to a protobuf field + /// + public class ValueMember + { + private int _fieldNumber; + /// + /// The number that identifies this member in a protobuf stream + /// + public int FieldNumber + { + get => _fieldNumber; + internal set + { + if (_fieldNumber != value) + { + MetaType.AssertValidFieldNumber(value); + ThrowIfFrozen(); + _fieldNumber = value; + } + } + } + + private readonly MemberInfo originalMember; + private MemberInfo backingMember; + /// + /// Gets the member (field/property) which this member relates to. + /// + public MemberInfo Member { get { return originalMember; } } + /// + /// Gets the backing member (field/property) which this member relates to + /// + public MemberInfo BackingMember + { + get { return backingMember; } + set + { + if (backingMember != value) + { + ThrowIfFrozen(); + backingMember = value; + } + } + } + + private readonly Type parentType, itemType, defaultType, memberType; + private object defaultValue; + + /// + /// Within a list / array / etc, the type of object for each item in the list (especially useful with ArrayList) + /// + public Type ItemType => itemType; + + /// + /// The underlying type of the member + /// + public Type MemberType => memberType; + + /// + /// For abstract types (IList etc), the type of concrete object to create (if required) + /// + public Type DefaultType => defaultType; + + /// + /// The type the defines the member + /// + public Type ParentType => parentType; + + /// + /// The default value of the item (members with this value will not be serialized) + /// + public object DefaultValue + { + get { return defaultValue; } + set + { + if (defaultValue != value) + { + ThrowIfFrozen(); + defaultValue = value; + } + } + } + + private readonly RuntimeTypeModel model; + /// + /// Creates a new ValueMember instance + /// + public ValueMember(RuntimeTypeModel model, Type parentType, int fieldNumber, MemberInfo member, Type memberType, Type itemType, Type defaultType, DataFormat dataFormat, object defaultValue) + : this(model, fieldNumber, memberType, itemType, defaultType, dataFormat) + { + if (parentType == null) throw new ArgumentNullException("parentType"); + if (fieldNumber < 1 && !Helpers.IsEnum(parentType)) throw new ArgumentOutOfRangeException("fieldNumber"); + + this.originalMember = member ?? throw new ArgumentNullException("member"); + this.parentType = parentType; + if (fieldNumber < 1 && !Helpers.IsEnum(parentType)) throw new ArgumentOutOfRangeException("fieldNumber"); + //#if WINRT + if (defaultValue != null && model.MapType(defaultValue.GetType()) != memberType) + //#else + // if (defaultValue != null && !memberType.IsInstanceOfType(defaultValue)) + //#endif + { + defaultValue = ParseDefaultValue(memberType, defaultValue); + } + this.defaultValue = defaultValue; + + MetaType type = model.FindWithoutAdd(memberType); + if (type != null) + { + AsReference = type.AsReferenceDefault; + } + else + { // we need to scan the hard way; can't risk recursion by fully walking it + AsReference = MetaType.GetAsReferenceDefault(model, memberType); + } + } + /// + /// Creates a new ValueMember instance + /// + internal ValueMember(RuntimeTypeModel model, int fieldNumber, Type memberType, Type itemType, Type defaultType, DataFormat dataFormat) + { + _fieldNumber = fieldNumber; + this.memberType = memberType ?? throw new ArgumentNullException(nameof(memberType)); + this.itemType = itemType; + this.defaultType = defaultType; + + this.model = model ?? throw new ArgumentNullException(nameof(model)); + this.dataFormat = dataFormat; + } + internal object GetRawEnumValue() + { +#if PORTABLE || CF || COREFX || PROFILE259 + object value = ((FieldInfo)originalMember).GetValue(null); + switch(Helpers.GetTypeCode(Enum.GetUnderlyingType(((FieldInfo)originalMember).FieldType))) + { + case ProtoTypeCode.SByte: return (sbyte)value; + case ProtoTypeCode.Byte: return (byte)value; + case ProtoTypeCode.Int16: return (short)value; + case ProtoTypeCode.UInt16: return (ushort)value; + case ProtoTypeCode.Int32: return (int)value; + case ProtoTypeCode.UInt32: return (uint)value; + case ProtoTypeCode.Int64: return (long)value; + case ProtoTypeCode.UInt64: return (ulong)value; + default: + throw new InvalidOperationException(); + } +#else + return ((FieldInfo)originalMember).GetRawConstantValue(); +#endif + } + private static object ParseDefaultValue(Type type, object value) + { + { + Type tmp = Helpers.GetUnderlyingType(type); + if (tmp != null) type = tmp; + } + if (value is string s) + { + if (Helpers.IsEnum(type)) return Helpers.ParseEnum(type, s); + + switch (Helpers.GetTypeCode(type)) + { + case ProtoTypeCode.Boolean: return bool.Parse(s); + case ProtoTypeCode.Byte: return byte.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture); + case ProtoTypeCode.Char: // char.Parse missing on CF/phone7 + if (s.Length == 1) return s[0]; + throw new FormatException("Single character expected: \"" + s + "\""); + case ProtoTypeCode.DateTime: return DateTime.Parse(s, CultureInfo.InvariantCulture); + case ProtoTypeCode.Decimal: return decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.Double: return double.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.Int16: return short.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.Int32: return int.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.Int64: return long.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.SByte: return sbyte.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture); + case ProtoTypeCode.Single: return float.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.String: return s; + case ProtoTypeCode.UInt16: return ushort.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.UInt32: return uint.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.UInt64: return ulong.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture); + case ProtoTypeCode.TimeSpan: return TimeSpan.Parse(s); + case ProtoTypeCode.Uri: return s; // Uri is decorated as string + case ProtoTypeCode.Guid: return new Guid(s); + } + } + + if (Helpers.IsEnum(type)) return Enum.ToObject(type, value); + return Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + + } + + private IProtoSerializer serializer; + internal IProtoSerializer Serializer + { + get + { + return serializer ?? (serializer = BuildSerializer()); + } + } + + private DataFormat dataFormat; + /// + /// Specifies the rules used to process the field; this is used to determine the most appropriate + /// wite-type, but also to describe subtypes within that wire-type (such as SignedVariant) + /// + public DataFormat DataFormat + { + get { return dataFormat; } + set + { + if (value != dataFormat) + { + ThrowIfFrozen(); + this.dataFormat = value; + } + } + } + + /// + /// Indicates whether this field should follow strict encoding rules; this means (for example) that if a "fixed32" + /// is encountered when "variant" is defined, then it will fail (throw an exception) when parsing. Note that + /// when serializing the defined type is always used. + /// + public bool IsStrict + { + get { return HasFlag(OPTIONS_IsStrict); } + set { SetFlag(OPTIONS_IsStrict, value, true); } + } + + /// + /// Indicates whether this field should use packed encoding (which can save lots of space for repeated primitive values). + /// This option only applies to list/array data of primitive types (int, double, etc). + /// + public bool IsPacked + { + get { return HasFlag(OPTIONS_IsPacked); } + set { SetFlag(OPTIONS_IsPacked, value, true); } + } + + /// + /// Indicates whether this field should *repace* existing values (the default is false, meaning *append*). + /// This option only applies to list/array data. + /// + public bool OverwriteList + { + get { return HasFlag(OPTIONS_OverwriteList); } + set { SetFlag(OPTIONS_OverwriteList, value, true); } + } + + /// + /// Indicates whether this field is mandatory. + /// + public bool IsRequired + { + get { return HasFlag(OPTIONS_IsRequired); } + set { SetFlag(OPTIONS_IsRequired, value, true); } + } + + /// + /// Enables full object-tracking/full-graph support. + /// + public bool AsReference + { + get { return HasFlag(OPTIONS_AsReference); } + set { SetFlag(OPTIONS_AsReference, value, true); } + } + + /// + /// Embeds the type information into the stream, allowing usage with types not known in advance. + /// + public bool DynamicType + { + get { return HasFlag(OPTIONS_DynamicType); } + set { SetFlag(OPTIONS_DynamicType, value, true); } + } + + /// + /// Indicates that the member should be treated as a protobuf Map + /// + public bool IsMap + { + get { return HasFlag(OPTIONS_IsMap); } + set { SetFlag(OPTIONS_IsMap, value, true); } + } + + private DataFormat mapKeyFormat, mapValueFormat; + /// + /// Specifies the data-format that should be used for the key, when IsMap is enabled + /// + public DataFormat MapKeyFormat + { + get { return mapKeyFormat; } + set + { + if (mapKeyFormat != value) + { + ThrowIfFrozen(); + mapKeyFormat = value; + } + } + } + /// + /// Specifies the data-format that should be used for the value, when IsMap is enabled + /// + public DataFormat MapValueFormat + { + get { return mapValueFormat; } + set + { + if (mapValueFormat != value) + { + ThrowIfFrozen(); + mapValueFormat = value; + } + } + } + + private MethodInfo getSpecified, setSpecified; + /// + /// Specifies methods for working with optional data members. + /// + /// Provides a method (null for none) to query whether this member should + /// be serialized; it must be of the form "bool {Method}()". The member is only serialized if the + /// method returns true. + /// Provides a method (null for none) to indicate that a member was + /// deserialized; it must be of the form "void {Method}(bool)", and will be called with "true" + /// when data is found. + public void SetSpecified(MethodInfo getSpecified, MethodInfo setSpecified) + { + if (this.getSpecified != getSpecified || this.setSpecified != setSpecified) + { + if (getSpecified != null) + { + if (getSpecified.ReturnType != model.MapType(typeof(bool)) + || getSpecified.IsStatic + || getSpecified.GetParameters().Length != 0) + { + throw new ArgumentException("Invalid pattern for checking member-specified", "getSpecified"); + } + } + if (setSpecified != null) + { + ParameterInfo[] args; + if (setSpecified.ReturnType != model.MapType(typeof(void)) + || setSpecified.IsStatic + || (args = setSpecified.GetParameters()).Length != 1 + || args[0].ParameterType != model.MapType(typeof(bool))) + { + throw new ArgumentException("Invalid pattern for setting member-specified", "setSpecified"); + } + } + + ThrowIfFrozen(); + this.getSpecified = getSpecified; + this.setSpecified = setSpecified; + } + } + + private void ThrowIfFrozen() + { + if (serializer != null) throw new InvalidOperationException("The type cannot be changed once a serializer has been generated"); + } + + internal bool ResolveMapTypes(out Type dictionaryType, out Type keyType, out Type valueType) + { + dictionaryType = keyType = valueType = null; + try + { +#if COREFX || PROFILE259 + var info = memberType.GetTypeInfo(); +#else + var info = memberType; +#endif + if (ImmutableCollectionDecorator.IdentifyImmutable(model, MemberType, out _, out _, out _, out _, out _, out _)) + { + return false; + } + if (info.IsInterface && info.IsGenericType && info.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { +#if PROFILE259 + var typeArgs = memberType.GetGenericTypeDefinition().GenericTypeArguments; +#else + var typeArgs = memberType.GetGenericArguments(); +#endif + if (IsValidMapKeyType(typeArgs[0])) + { + keyType = typeArgs[0]; + valueType = typeArgs[1]; + dictionaryType = memberType; + } + return false; + } +#if PROFILE259 + foreach (var iType in memberType.GetTypeInfo().ImplementedInterfaces) +#else + foreach (var iType in memberType.GetInterfaces()) +#endif + { +#if COREFX || PROFILE259 + info = iType.GetTypeInfo(); +#else + info = iType; +#endif + if (info.IsGenericType && info.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { + if (dictionaryType != null) throw new InvalidOperationException("Multiple dictionary interfaces implemented by type: " + memberType.FullName); +#if PROFILE259 + var typeArgs = iType.GetGenericTypeDefinition().GenericTypeArguments; +#else + var typeArgs = iType.GetGenericArguments(); +#endif + if (IsValidMapKeyType(typeArgs[0])) + { + keyType = typeArgs[0]; + valueType = typeArgs[1]; + dictionaryType = memberType; + } + } + } + if (dictionaryType == null) return false; + + // (note we checked the key type already) + // not a map if value is repeated + Type itemType = null, defaultType = null; + model.ResolveListTypes(valueType, ref itemType, ref defaultType); + if (itemType != null) return false; + + return dictionaryType != null; + } + catch + { + // if it isn't a good fit; don't use "map" + return false; + } + } + + static bool IsValidMapKeyType(Type type) + { + if (type == null || Helpers.IsEnum(type)) return false; + switch (Helpers.GetTypeCode(type)) + { + case ProtoTypeCode.Boolean: + case ProtoTypeCode.Byte: + case ProtoTypeCode.Char: + case ProtoTypeCode.Int16: + case ProtoTypeCode.Int32: + case ProtoTypeCode.Int64: + case ProtoTypeCode.String: + + case ProtoTypeCode.SByte: + case ProtoTypeCode.UInt16: + case ProtoTypeCode.UInt32: + case ProtoTypeCode.UInt64: + return true; + } + return false; + } + private IProtoSerializer BuildSerializer() + { + int opaqueToken = 0; + try + { + model.TakeLock(ref opaqueToken);// check nobody is still adding this type + var member = backingMember ?? originalMember; + IProtoSerializer ser; + if (IsMap) + { + ResolveMapTypes(out var dictionaryType, out var keyType, out var valueType); + + if (dictionaryType == null) + { + throw new InvalidOperationException("Unable to resolve map type for type: " + memberType.FullName); + } + var concreteType = defaultType; + if (concreteType == null && Helpers.IsClass(memberType)) + { + concreteType = memberType; + } + var keySer = TryGetCoreSerializer(model, MapKeyFormat, keyType, out var keyWireType, false, false, false, false); + if (!AsReference) + { + AsReference = MetaType.GetAsReferenceDefault(model, valueType); + } + var valueSer = TryGetCoreSerializer(model, MapValueFormat, valueType, out var valueWireType, AsReference, DynamicType, false, true); +#if PROFILE259 + IEnumerable ctors = typeof(MapDecorator<,,>).MakeGenericType(new Type[] { dictionaryType, keyType, valueType }).GetTypeInfo().DeclaredConstructors; + if (ctors.Count() != 1) + { + throw new InvalidOperationException("Unable to resolve MapDecorator constructor"); + } + ser = (IProtoSerializer)ctors.First().Invoke(new object[] {model, concreteType, keySer, valueSer, _fieldNumber, + DataFormat == DataFormat.Group ? WireType.StartGroup : WireType.String, keyWireType, valueWireType, OverwriteList }); +#else + var ctors = typeof(MapDecorator<,,>).MakeGenericType(new Type[] { dictionaryType, keyType, valueType }).GetConstructors( + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + if (ctors.Length != 1) throw new InvalidOperationException("Unable to resolve MapDecorator constructor"); + ser = (IProtoSerializer)ctors[0].Invoke(new object[] {model, concreteType, keySer, valueSer, _fieldNumber, + DataFormat == DataFormat.Group ? WireType.StartGroup : WireType.String, keyWireType, valueWireType, OverwriteList }); +#endif + } + else + { + Type finalType = itemType ?? memberType; + ser = TryGetCoreSerializer(model, dataFormat, finalType, out WireType wireType, AsReference, DynamicType, OverwriteList, true); + if (ser == null) + { + throw new InvalidOperationException("No serializer defined for type: " + finalType.FullName); + } + + // apply tags + if (itemType != null && SupportNull) + { + if (IsPacked) + { + throw new NotSupportedException("Packed encodings cannot support null values"); + } + ser = new TagDecorator(NullDecorator.Tag, wireType, IsStrict, ser); + ser = new NullDecorator(model, ser); + ser = new TagDecorator(_fieldNumber, WireType.StartGroup, false, ser); + } + else + { + ser = new TagDecorator(_fieldNumber, wireType, IsStrict, ser); + } + // apply lists if appropriate + if (itemType != null) + { + Type underlyingItemType = SupportNull ? itemType : Helpers.GetUnderlyingType(itemType) ?? itemType; + + Helpers.DebugAssert(underlyingItemType == ser.ExpectedType + || (ser.ExpectedType == model.MapType(typeof(object)) && !Helpers.IsValueType(underlyingItemType)) + , "Wrong type in the tail; expected {0}, received {1}", ser.ExpectedType, underlyingItemType); + if (memberType.IsArray) + { + ser = new ArrayDecorator(model, ser, _fieldNumber, IsPacked, wireType, memberType, OverwriteList, SupportNull); + } + else + { + ser = ListDecorator.Create(model, memberType, defaultType, ser, _fieldNumber, IsPacked, wireType, member != null && PropertyDecorator.CanWrite(model, member), OverwriteList, SupportNull); + } + } + else if (defaultValue != null && !IsRequired && getSpecified == null) + { // note: "ShouldSerialize*" / "*Specified" / etc ^^^^ take precedence over defaultValue, + // as does "IsRequired" + ser = new DefaultValueDecorator(model, defaultValue, ser); + } + if (memberType == model.MapType(typeof(Uri))) + { + ser = new UriDecorator(model, ser); + } +#if PORTABLE + else if(memberType.FullName == typeof(Uri).FullName) + { + // In PCLs, the Uri type may not match (WinRT uses Internal/Uri, .Net uses System/Uri) + ser = new ReflectedUriDecorator(memberType, model, ser); + } +#endif + } + if (member != null) + { + if (member is PropertyInfo prop) + { + ser = new PropertyDecorator(model, parentType, prop, ser); + } + else if (member is FieldInfo fld) + { + ser = new FieldDecorator(parentType, fld, ser); + } + else + { + throw new InvalidOperationException(); + } + + if (getSpecified != null || setSpecified != null) + { + ser = new MemberSpecifiedDecorator(getSpecified, setSpecified, ser); + } + } + return ser; + } + finally + { + model.ReleaseLock(opaqueToken); + } + } + + private static WireType GetIntWireType(DataFormat format, int width) + { + switch (format) + { + case DataFormat.ZigZag: return WireType.SignedVariant; + case DataFormat.FixedSize: return width == 32 ? WireType.Fixed32 : WireType.Fixed64; + case DataFormat.TwosComplement: + case DataFormat.Default: return WireType.Variant; + default: throw new InvalidOperationException(); + } + } + private static WireType GetDateTimeWireType(DataFormat format) + { + switch (format) + { + + case DataFormat.Group: return WireType.StartGroup; + case DataFormat.FixedSize: return WireType.Fixed64; + case DataFormat.WellKnown: + case DataFormat.Default: + return WireType.String; + default: throw new InvalidOperationException(); + } + } + + internal static IProtoSerializer TryGetCoreSerializer(RuntimeTypeModel model, DataFormat dataFormat, Type type, out WireType defaultWireType, + bool asReference, bool dynamicType, bool overwriteList, bool allowComplexTypes) + { + { + Type tmp = Helpers.GetUnderlyingType(type); + if (tmp != null) type = tmp; + } + if (Helpers.IsEnum(type)) + { + if (allowComplexTypes && model != null) + { + // need to do this before checking the typecode; an int enum will report Int32 etc + defaultWireType = WireType.Variant; + return new EnumSerializer(type, model.GetEnumMap(type)); + } + else + { // enum is fine for adding as a meta-type + defaultWireType = WireType.None; + return null; + } + } + ProtoTypeCode code = Helpers.GetTypeCode(type); + switch (code) + { + case ProtoTypeCode.Int32: + defaultWireType = GetIntWireType(dataFormat, 32); + return new Int32Serializer(model); + case ProtoTypeCode.UInt32: + defaultWireType = GetIntWireType(dataFormat, 32); + return new UInt32Serializer(model); + case ProtoTypeCode.Int64: + defaultWireType = GetIntWireType(dataFormat, 64); + return new Int64Serializer(model); + case ProtoTypeCode.UInt64: + defaultWireType = GetIntWireType(dataFormat, 64); + return new UInt64Serializer(model); + case ProtoTypeCode.String: + defaultWireType = WireType.String; + if (asReference) + { + return new NetObjectSerializer(model, model.MapType(typeof(string)), 0, BclHelpers.NetObjectOptions.AsReference); + } + return new StringSerializer(model); + case ProtoTypeCode.Single: + defaultWireType = WireType.Fixed32; + return new SingleSerializer(model); + case ProtoTypeCode.Double: + defaultWireType = WireType.Fixed64; + return new DoubleSerializer(model); + case ProtoTypeCode.Boolean: + defaultWireType = WireType.Variant; + return new BooleanSerializer(model); + case ProtoTypeCode.DateTime: + defaultWireType = GetDateTimeWireType(dataFormat); + return new DateTimeSerializer(dataFormat, model); + case ProtoTypeCode.Decimal: + defaultWireType = WireType.String; + return new DecimalSerializer(model); + case ProtoTypeCode.Byte: + defaultWireType = GetIntWireType(dataFormat, 32); + return new ByteSerializer(model); + case ProtoTypeCode.SByte: + defaultWireType = GetIntWireType(dataFormat, 32); + return new SByteSerializer(model); + case ProtoTypeCode.Char: + defaultWireType = WireType.Variant; + return new CharSerializer(model); + case ProtoTypeCode.Int16: + defaultWireType = GetIntWireType(dataFormat, 32); + return new Int16Serializer(model); + case ProtoTypeCode.UInt16: + defaultWireType = GetIntWireType(dataFormat, 32); + return new UInt16Serializer(model); + case ProtoTypeCode.TimeSpan: + defaultWireType = GetDateTimeWireType(dataFormat); + return new TimeSpanSerializer(dataFormat, model); + case ProtoTypeCode.Guid: + defaultWireType = dataFormat == DataFormat.Group ? WireType.StartGroup : WireType.String; + return new GuidSerializer(model); + case ProtoTypeCode.Uri: + defaultWireType = WireType.String; + return new StringSerializer(model); + case ProtoTypeCode.ByteArray: + defaultWireType = WireType.String; + return new BlobSerializer(model, overwriteList); + case ProtoTypeCode.Type: + defaultWireType = WireType.String; + return new SystemTypeSerializer(model); + } + IProtoSerializer parseable = model.AllowParseableTypes ? ParseableSerializer.TryCreate(type, model) : null; + if (parseable != null) + { + defaultWireType = WireType.String; + return parseable; + } + if (allowComplexTypes && model != null) + { + int key = model.GetKey(type, false, true); + MetaType meta = null; + if (key >= 0) + { + meta = model[type]; + if (dataFormat == DataFormat.Default && meta.IsGroup) + { + dataFormat = DataFormat.Group; + } + } + + if (asReference || dynamicType) + { + BclHelpers.NetObjectOptions options = BclHelpers.NetObjectOptions.None; + if (asReference) options |= BclHelpers.NetObjectOptions.AsReference; + if (dynamicType) options |= BclHelpers.NetObjectOptions.DynamicType; + if (meta != null) + { // exists + if (asReference && Helpers.IsValueType(type)) + { + string message = "AsReference cannot be used with value-types"; + + if (type.Name == "KeyValuePair`2") + { + message += "; please see https://stackoverflow.com/q/14436606/23354"; + } + else + { + message += ": " + type.FullName; + } + throw new InvalidOperationException(message); + } + + if (asReference && meta.IsAutoTuple) options |= BclHelpers.NetObjectOptions.LateSet; + if (meta.UseConstructor) options |= BclHelpers.NetObjectOptions.UseConstructor; + } + defaultWireType = dataFormat == DataFormat.Group ? WireType.StartGroup : WireType.String; + return new NetObjectSerializer(model, type, key, options); + } + if (key >= 0) + { + defaultWireType = dataFormat == DataFormat.Group ? WireType.StartGroup : WireType.String; + return new SubItemSerializer(type, key, meta, true); + } + } + defaultWireType = WireType.None; + return null; + } + + + private string name; + internal void SetName(string name) + { + if (name != this.name) + { + ThrowIfFrozen(); + this.name = name; + } + } + /// + /// Gets the logical name for this member in the schema (this is not critical for binary serialization, but may be used + /// when inferring a schema). + /// + public string Name + { + get { return string.IsNullOrEmpty(name) ? originalMember.Name : name; } + set { SetName(value); } + } + + private const byte + OPTIONS_IsStrict = 1, + OPTIONS_IsPacked = 2, + OPTIONS_IsRequired = 4, + OPTIONS_OverwriteList = 8, + OPTIONS_SupportNull = 16, + OPTIONS_AsReference = 32, + OPTIONS_IsMap = 64, + OPTIONS_DynamicType = 128; + + private byte flags; + private bool HasFlag(byte flag) { return (flags & flag) == flag; } + private void SetFlag(byte flag, bool value, bool throwIfFrozen) + { + if (throwIfFrozen && HasFlag(flag) != value) + { + ThrowIfFrozen(); + } + if (value) + flags |= flag; + else + flags = (byte)(flags & ~flag); + } + + /// + /// Should lists have extended support for null values? Note this makes the serialization less efficient. + /// + public bool SupportNull + { + get { return HasFlag(OPTIONS_SupportNull); } + set { SetFlag(OPTIONS_SupportNull, value, true); } + } + + internal string GetSchemaTypeName(bool applyNetObjectProxy, ref RuntimeTypeModel.CommonImports imports) + { + Type effectiveType = ItemType; + if (effectiveType == null) effectiveType = MemberType; + return model.GetSchemaTypeName(effectiveType, DataFormat, applyNetObjectProxy && AsReference, applyNetObjectProxy && DynamicType, ref imports); + } + + + internal sealed class Comparer : System.Collections.IComparer, IComparer + { + public static readonly Comparer Default = new Comparer(); + + public int Compare(object x, object y) + { + return Compare(x as ValueMember, y as ValueMember); + } + + public int Compare(ValueMember x, ValueMember y) + { + if (ReferenceEquals(x, y)) return 0; + if (x == null) return -1; + if (y == null) return 1; + + return x.FieldNumber.CompareTo(y.FieldNumber); + } + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Meta/ValueMember.cs.meta b/Runtime/Protobuf-net/Meta/ValueMember.cs.meta new file mode 100644 index 0000000..d3eeb78 --- /dev/null +++ b/Runtime/Protobuf-net/Meta/ValueMember.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dba7fd2d1d1c883469e153f7ac5fdd86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/NetObjectCache.cs b/Runtime/Protobuf-net/NetObjectCache.cs new file mode 100644 index 0000000..8e83549 --- /dev/null +++ b/Runtime/Protobuf-net/NetObjectCache.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using ProtoBuf.Meta; + +namespace ProtoBuf +{ + internal sealed class NetObjectCache + { + internal const int Root = 0; + private MutableList underlyingList; + + private MutableList List => underlyingList ?? (underlyingList = new MutableList()); + + internal object GetKeyedObject(int key) + { + if (key-- == Root) + { + if (rootObject == null) throw new ProtoException("No root object assigned"); + return rootObject; + } + BasicList list = List; + + if (key < 0 || key >= list.Count) + { + Helpers.DebugWriteLine("Missing key: " + key); + throw new ProtoException("Internal error; a missing key occurred"); + } + + object tmp = list[key]; + if (tmp == null) + { + throw new ProtoException("A deferred key does not have a value yet"); + } + return tmp; + } + + internal void SetKeyedObject(int key, object value) + { + if (key-- == Root) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (rootObject != null && ((object)rootObject != (object)value)) throw new ProtoException("The root object cannot be reassigned"); + rootObject = value; + } + else + { + MutableList list = List; + if (key < list.Count) + { + object oldVal = list[key]; + if (oldVal == null) + { + list[key] = value; + } + else if (!ReferenceEquals(oldVal, value)) + { + throw new ProtoException("Reference-tracked objects cannot change reference"); + } // otherwise was the same; nothing to do + } + else if (key != list.Add(value)) + { + throw new ProtoException("Internal error; a key mismatch occurred"); + } + } + } + + private object rootObject; + internal int AddObjectKey(object value, out bool existing) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + + if ((object)value == (object)rootObject) // (object) here is no-op, but should be + { // preserved even if this was typed - needs ref-check + existing = true; + return Root; + } + + string s = value as string; + BasicList list = List; + int index; + + if (s == null) + { +#if CF || PORTABLE // CF has very limited proper object ref-tracking; so instead, we'll search it the hard way + index = list.IndexOfReference(value); +#else + if (objectKeys == null) + { + objectKeys = new Dictionary(ReferenceComparer.Default); + index = -1; + } + else + { + if (!objectKeys.TryGetValue(value, out index)) index = -1; + } +#endif + } + else + { + if (stringKeys == null) + { + stringKeys = new Dictionary(); + index = -1; + } + else + { + if (!stringKeys.TryGetValue(s, out index)) index = -1; + } + } + + if (!(existing = index >= 0)) + { + index = list.Add(value); + + if (s == null) + { +#if !CF && !PORTABLE // CF can't handle the object keys very well + objectKeys.Add(value, index); +#endif + } + else + { + stringKeys.Add(s, index); + } + } + return index + 1; + } + + private int trapStartIndex; // defaults to 0 - optimization for RegisterTrappedObject + // to make it faster at seeking to find deferred-objects + + internal void RegisterTrappedObject(object value) + { + if (rootObject == null) + { + rootObject = value; + } + else + { + if (underlyingList != null) + { + for (int i = trapStartIndex; i < underlyingList.Count; i++) + { + trapStartIndex = i + 1; // things never *become* null; whether or + // not the next item is null, it will never + // need to be checked again + + if (underlyingList[i] == null) + { + underlyingList[i] = value; + break; + } + } + } + } + } + + private Dictionary stringKeys; + +#if !CF && !PORTABLE // CF lacks the ability to get a robust reference-based hash-code, so we'll do it the harder way instead + private System.Collections.Generic.Dictionary objectKeys; + private sealed class ReferenceComparer : IEqualityComparer + { + public readonly static ReferenceComparer Default = new ReferenceComparer(); + private ReferenceComparer() { } + + bool IEqualityComparer.Equals(object x, object y) + { + return x == y; // ref equality + } + + int IEqualityComparer.GetHashCode(object obj) + { + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + } +#endif + + internal void Clear() + { + trapStartIndex = 0; + rootObject = null; + if (underlyingList != null) underlyingList.Clear(); + if (stringKeys != null) stringKeys.Clear(); +#if !CF && !PORTABLE + if (objectKeys != null) objectKeys.Clear(); +#endif + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/NetObjectCache.cs.meta b/Runtime/Protobuf-net/NetObjectCache.cs.meta new file mode 100644 index 0000000..862acc0 --- /dev/null +++ b/Runtime/Protobuf-net/NetObjectCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7ec59f6037764d43b3d585baf2343e2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/PrefixStyle.cs b/Runtime/Protobuf-net/PrefixStyle.cs new file mode 100644 index 0000000..0ebef04 --- /dev/null +++ b/Runtime/Protobuf-net/PrefixStyle.cs @@ -0,0 +1,26 @@ + +namespace ProtoBuf +{ + /// + /// Specifies the type of prefix that should be applied to messages. + /// + public enum PrefixStyle + { + /// + /// No length prefix is applied to the data; the data is terminated only be the end of the stream. + /// + None = 0, + /// + /// A base-128 ("varint", the default prefix format in protobuf) length prefix is applied to the data (efficient for short messages). + /// + Base128 = 1, + /// + /// A fixed-length (little-endian) length prefix is applied to the data (useful for compatibility). + /// + Fixed32 = 2, + /// + /// A fixed-length (big-endian) length prefix is applied to the data (useful for compatibility). + /// + Fixed32BigEndian = 3 + } +} diff --git a/Runtime/Protobuf-net/PrefixStyle.cs.meta b/Runtime/Protobuf-net/PrefixStyle.cs.meta new file mode 100644 index 0000000..a955c1f --- /dev/null +++ b/Runtime/Protobuf-net/PrefixStyle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6f16948bce1f2d4eb805ed31a2bb878 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoContractAttribute.cs b/Runtime/Protobuf-net/ProtoContractAttribute.cs new file mode 100644 index 0000000..e2e8054 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoContractAttribute.cs @@ -0,0 +1,175 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Indicates that a type is defined for protocol-buffer serialization. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface, + AllowMultiple = false, Inherited = false)] + public sealed class ProtoContractAttribute : Attribute + { + /// + /// Gets or sets the defined name of the type. + /// + public string Name { get; set; } + + /// + /// Gets or sets the fist offset to use with implicit field tags; + /// only uesd if ImplicitFields is set. + /// + public int ImplicitFirstTag + { + get { return implicitFirstTag; } + set + { + if (value < 1) throw new ArgumentOutOfRangeException("ImplicitFirstTag"); + implicitFirstTag = value; + } + } + private int implicitFirstTag; + + /// + /// If specified, alternative contract markers (such as markers for XmlSerailizer or DataContractSerializer) are ignored. + /// + public bool UseProtoMembersOnly + { + get { return HasFlag(OPTIONS_UseProtoMembersOnly); } + set { SetFlag(OPTIONS_UseProtoMembersOnly, value); } + } + + /// + /// If specified, do NOT treat this type as a list, even if it looks like one. + /// + public bool IgnoreListHandling + { + get { return HasFlag(OPTIONS_IgnoreListHandling); } + set { SetFlag(OPTIONS_IgnoreListHandling, value); } + } + + /// + /// Gets or sets the mechanism used to automatically infer field tags + /// for members. This option should be used in advanced scenarios only. + /// Please review the important notes against the ImplicitFields enumeration. + /// + public ImplicitFields ImplicitFields { get; set; } + + /// + /// Enables/disables automatic tag generation based on the existing name / order + /// of the defined members. This option is not used for members marked + /// with ProtoMemberAttribute, as intended to provide compatibility with + /// WCF serialization. WARNING: when adding new fields you must take + /// care to increase the Order for new elements, otherwise data corruption + /// may occur. + /// + /// If not explicitly specified, the default is assumed from Serializer.GlobalOptions.InferTagFromName. + public bool InferTagFromName + { + get { return HasFlag(OPTIONS_InferTagFromName); } + set + { + SetFlag(OPTIONS_InferTagFromName, value); + SetFlag(OPTIONS_InferTagFromNameHasValue, true); + } + } + + /// + /// Has a InferTagFromName value been explicitly set? if not, the default from the type-model is assumed. + /// + internal bool InferTagFromNameHasValue + { // note that this property is accessed via reflection and should not be removed + get { return HasFlag(OPTIONS_InferTagFromNameHasValue); } + } + + /// + /// Specifies an offset to apply to [DataMember(Order=...)] markers; + /// this is useful when working with mex-generated classes that have + /// a different origin (usually 1 vs 0) than the original data-contract. + /// + /// This value is added to the Order of each member. + /// + public int DataMemberOffset { get; set; } + + /// + /// If true, the constructor for the type is bypassed during deserialization, meaning any field initializers + /// or other initialization code is skipped. + /// + public bool SkipConstructor + { + get { return HasFlag(OPTIONS_SkipConstructor); } + set { SetFlag(OPTIONS_SkipConstructor, value); } + } + + /// + /// Should this type be treated as a reference by default? Please also see the implications of this, + /// as recorded on ProtoMemberAttribute.AsReference + /// + public bool AsReferenceDefault + { + get { return HasFlag(OPTIONS_AsReferenceDefault); } + set + { + SetFlag(OPTIONS_AsReferenceDefault, value); + } + } + + /// + /// Indicates whether this type should always be treated as a "group" (rather than a string-prefixed sub-message) + /// + public bool IsGroup + { + get { return HasFlag(OPTIONS_IsGroup); } + set + { + SetFlag(OPTIONS_IsGroup, value); + } + } + + private bool HasFlag(ushort flag) { return (flags & flag) == flag; } + private void SetFlag(ushort flag, bool value) + { + if (value) flags |= flag; + else flags = (ushort)(flags & ~flag); + } + + private ushort flags; + + private const ushort + OPTIONS_InferTagFromName = 1, + OPTIONS_InferTagFromNameHasValue = 2, + OPTIONS_UseProtoMembersOnly = 4, + OPTIONS_SkipConstructor = 8, + OPTIONS_IgnoreListHandling = 16, + OPTIONS_AsReferenceDefault = 32, + OPTIONS_EnumPassthru = 64, + OPTIONS_EnumPassthruHasValue = 128, + OPTIONS_IsGroup = 256; + + /// + /// Applies only to enums (not to DTO classes themselves); gets or sets a value indicating that an enum should be treated directly as an int/short/etc, rather + /// than enforcing .proto enum rules. This is useful *in particul* for [Flags] enums. + /// + public bool EnumPassthru + { + get { return HasFlag(OPTIONS_EnumPassthru); } + set + { + SetFlag(OPTIONS_EnumPassthru, value); + SetFlag(OPTIONS_EnumPassthruHasValue, true); + } + } + + /// + /// Allows to define a surrogate type used for serialization/deserialization purpose. + /// + public Type Surrogate { get; set; } + + /// + /// Has a EnumPassthru value been explicitly set? + /// + internal bool EnumPassthruHasValue + { // note that this property is accessed via reflection and should not be removed + get { return HasFlag(OPTIONS_EnumPassthruHasValue); } + } + } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/ProtoContractAttribute.cs.meta b/Runtime/Protobuf-net/ProtoContractAttribute.cs.meta new file mode 100644 index 0000000..d000688 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoContractAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5d57dba877f0854c999b91a6514d93d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoConverterAttribute.cs b/Runtime/Protobuf-net/ProtoConverterAttribute.cs new file mode 100644 index 0000000..b75bb80 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoConverterAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Indicates that a static member should be considered the same as though + /// were an implicit / explicit conversion operator; in particular, this + /// is useful for conversions that operator syntax does not allow, such as + /// to/from interface types. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ProtoConverterAttribute : Attribute { } +} \ No newline at end of file diff --git a/Runtime/Protobuf-net/ProtoConverterAttribute.cs.meta b/Runtime/Protobuf-net/ProtoConverterAttribute.cs.meta new file mode 100644 index 0000000..323a3a4 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoConverterAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 399681000a748834f87d721feda5f459 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoEnumAttribute.cs b/Runtime/Protobuf-net/ProtoEnumAttribute.cs new file mode 100644 index 0000000..1d82645 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoEnumAttribute.cs @@ -0,0 +1,36 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Used to define protocol-buffer specific behavior for + /// enumerated values. + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public sealed class ProtoEnumAttribute : Attribute + { + /// + /// Gets or sets the specific value to use for this enum during serialization. + /// + public int Value + { + get { return enumValue; } + set { this.enumValue = value; hasValue = true; } + } + + /// + /// Indicates whether this instance has a customised value mapping + /// + /// true if a specific value is set + public bool HasValue() => hasValue; + + private bool hasValue; + private int enumValue; + + /// + /// Gets or sets the defined name of the enum, as used in .proto + /// (this name is not used during serialization). + /// + public string Name { get; set; } + } +} diff --git a/Runtime/Protobuf-net/ProtoEnumAttribute.cs.meta b/Runtime/Protobuf-net/ProtoEnumAttribute.cs.meta new file mode 100644 index 0000000..a5cada9 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoEnumAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b3e030ed91e74b49b87bb0cd9acf139 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoException.cs b/Runtime/Protobuf-net/ProtoException.cs new file mode 100644 index 0000000..f502527 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoException.cs @@ -0,0 +1,30 @@ +using System; + +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) +using System.Runtime.Serialization; +#endif +namespace ProtoBuf +{ + /// + /// Indicates an error during serialization/deserialization of a proto stream. + /// +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + [Serializable] +#endif + public class ProtoException : Exception + { + /// Creates a new ProtoException instance. + public ProtoException() { } + + /// Creates a new ProtoException instance. + public ProtoException(string message) : base(message) { } + + /// Creates a new ProtoException instance. + public ProtoException(string message, Exception innerException) : base(message, innerException) { } + +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + /// Creates a new ProtoException instance. + protected ProtoException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif + } +} diff --git a/Runtime/Protobuf-net/ProtoException.cs.meta b/Runtime/Protobuf-net/ProtoException.cs.meta new file mode 100644 index 0000000..28099e5 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8629683d41766534fa00bcb5d1a324e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoIgnoreAttribute.cs b/Runtime/Protobuf-net/ProtoIgnoreAttribute.cs new file mode 100644 index 0000000..775674e --- /dev/null +++ b/Runtime/Protobuf-net/ProtoIgnoreAttribute.cs @@ -0,0 +1,40 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Indicates that a member should be excluded from serialization; this + /// is only normally used when using implict fields. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = false, Inherited = true)] + public class ProtoIgnoreAttribute : Attribute { } + + /// + /// Indicates that a member should be excluded from serialization; this + /// is only normally used when using implict fields. This allows + /// ProtoIgnoreAttribute usage + /// even for partial classes where the individual members are not + /// under direct control. + /// + [AttributeUsage(AttributeTargets.Class, + AllowMultiple = true, Inherited = false)] + public sealed class ProtoPartialIgnoreAttribute : ProtoIgnoreAttribute + { + /// + /// Creates a new ProtoPartialIgnoreAttribute instance. + /// + /// Specifies the member to be ignored. + public ProtoPartialIgnoreAttribute(string memberName) + : base() + { + if (string.IsNullOrEmpty(memberName)) throw new ArgumentNullException(nameof(memberName)); + + MemberName = memberName; + } + /// + /// The name of the member to be ignored. + /// + public string MemberName { get; } + } +} diff --git a/Runtime/Protobuf-net/ProtoIgnoreAttribute.cs.meta b/Runtime/Protobuf-net/ProtoIgnoreAttribute.cs.meta new file mode 100644 index 0000000..00afe26 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoIgnoreAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b012b09d39a7c2445aba79ffee82b117 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoIncludeAttribute.cs b/Runtime/Protobuf-net/ProtoIncludeAttribute.cs new file mode 100644 index 0000000..bb83ef7 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoIncludeAttribute.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; + +using ProtoBuf.Meta; + +namespace ProtoBuf +{ + /// + /// Indicates the known-types to support for an individual + /// message. This serializes each level in the hierarchy as + /// a nested message to retain wire-compatibility with + /// other protocol-buffer implementations. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public sealed class ProtoIncludeAttribute : Attribute + { + /// + /// Creates a new instance of the ProtoIncludeAttribute. + /// + /// The unique index (within the type) that will identify this data. + /// The additional type to serialize/deserialize. + public ProtoIncludeAttribute(int tag, Type knownType) + : this(tag, knownType == null ? "" : knownType.AssemblyQualifiedName) { } + + /// + /// Creates a new instance of the ProtoIncludeAttribute. + /// + /// The unique index (within the type) that will identify this data. + /// The additional type to serialize/deserialize. + public ProtoIncludeAttribute(int tag, string knownTypeName) + { + if (tag <= 0) throw new ArgumentOutOfRangeException(nameof(tag), "Tags must be positive integers"); + if (string.IsNullOrEmpty(knownTypeName)) throw new ArgumentNullException(nameof(knownTypeName), "Known type cannot be blank"); + Tag = tag; + KnownTypeName = knownTypeName; + } + + /// + /// Gets the unique index (within the type) that will identify this data. + /// + public int Tag { get; } + + /// + /// Gets the additional type to serialize/deserialize. + /// + public string KnownTypeName { get; } + + /// + /// Gets the additional type to serialize/deserialize. + /// + public Type KnownType => TypeModel.ResolveKnownType(KnownTypeName, null, null); + + /// + /// Specifies whether the inherited sype's sub-message should be + /// written with a length-prefix (default), or with group markers. + /// + [DefaultValue(DataFormat.Default)] + public DataFormat DataFormat { get; set; } = DataFormat.Default; + } +} diff --git a/Runtime/Protobuf-net/ProtoIncludeAttribute.cs.meta b/Runtime/Protobuf-net/ProtoIncludeAttribute.cs.meta new file mode 100644 index 0000000..edebcb5 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoIncludeAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40d89f2230d5a4f4badf122df4ed9fae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoMapAttribute.cs b/Runtime/Protobuf-net/ProtoMapAttribute.cs new file mode 100644 index 0000000..e85441a --- /dev/null +++ b/Runtime/Protobuf-net/ProtoMapAttribute.cs @@ -0,0 +1,29 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Controls the formatting of elements in a dictionary, and indicates that + /// "map" rules should be used: duplicates *replace* earlier values, rather + /// than throwing an exception + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class ProtoMapAttribute : Attribute + { + /// + /// Describes the data-format used to store the key + /// + public DataFormat KeyFormat { get; set; } + /// + /// Describes the data-format used to store the value + /// + public DataFormat ValueFormat { get; set; } + + /// + /// Disables "map" handling; dictionaries will use ".Add(key,value)" instead of "[key] = value", + /// which means duplicate keys will cause an exception (instead of retaining the final value); if + /// a proto schema is emitted, it will be produced using "repeated" instead of "map" + /// + public bool DisableMap { get; set; } + } +} diff --git a/Runtime/Protobuf-net/ProtoMapAttribute.cs.meta b/Runtime/Protobuf-net/ProtoMapAttribute.cs.meta new file mode 100644 index 0000000..cf765ae --- /dev/null +++ b/Runtime/Protobuf-net/ProtoMapAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d41a983b561e9043a8ce693aeb9c835 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoMemberAttribute.cs b/Runtime/Protobuf-net/ProtoMemberAttribute.cs new file mode 100644 index 0000000..e5ab896 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoMemberAttribute.cs @@ -0,0 +1,228 @@ +using System; +using System.Reflection; + +namespace ProtoBuf +{ + /// + /// Declares a member to be used in protocol-buffer serialization, using + /// the given Tag. A DataFormat may be used to optimise the serialization + /// format (for instance, using zigzag encoding for negative numbers, or + /// fixed-length encoding for large values. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = false, Inherited = true)] + public class ProtoMemberAttribute : Attribute + , IComparable + , IComparable + + { + /// + /// Compare with another ProtoMemberAttribute for sorting purposes + /// + public int CompareTo(object other) => CompareTo(other as ProtoMemberAttribute); + /// + /// Compare with another ProtoMemberAttribute for sorting purposes + /// + public int CompareTo(ProtoMemberAttribute other) + { + if (other == null) return -1; + if ((object)this == (object)other) return 0; + int result = this.tag.CompareTo(other.tag); + if (result == 0) result = string.CompareOrdinal(this.name, other.name); + return result; + } + + /// + /// Creates a new ProtoMemberAttribute instance. + /// + /// Specifies the unique tag used to identify this member within the type. + public ProtoMemberAttribute(int tag) : this(tag, false) + { } + + internal ProtoMemberAttribute(int tag, bool forced) + { + if (tag <= 0 && !forced) throw new ArgumentOutOfRangeException(nameof(tag)); + this.tag = tag; + } + +#if !NO_RUNTIME + internal MemberInfo Member, BackingMember; + internal bool TagIsPinned; +#endif + /// + /// Gets or sets the original name defined in the .proto; not used + /// during serialization. + /// + public string Name { get { return name; } set { name = value; } } + private string name; + + /// + /// Gets or sets the data-format to be used when encoding this value. + /// + public DataFormat DataFormat { get { return dataFormat; } set { dataFormat = value; } } + private DataFormat dataFormat; + + /// + /// Gets the unique tag used to identify this member within the type. + /// + public int Tag { get { return tag; } } + private int tag; + internal void Rebase(int tag) { this.tag = tag; } + + /// + /// Gets or sets a value indicating whether this member is mandatory. + /// + public bool IsRequired + { + get { return (options & MemberSerializationOptions.Required) == MemberSerializationOptions.Required; } + set + { + if (value) options |= MemberSerializationOptions.Required; + else options &= ~MemberSerializationOptions.Required; + } + } + + /// + /// Gets a value indicating whether this member is packed. + /// This option only applies to list/array data of primitive types (int, double, etc). + /// + public bool IsPacked + { + get { return (options & MemberSerializationOptions.Packed) == MemberSerializationOptions.Packed; } + set + { + if (value) options |= MemberSerializationOptions.Packed; + else options &= ~MemberSerializationOptions.Packed; + } + } + + /// + /// Indicates whether this field should *repace* existing values (the default is false, meaning *append*). + /// This option only applies to list/array data. + /// + public bool OverwriteList + { + get { return (options & MemberSerializationOptions.OverwriteList) == MemberSerializationOptions.OverwriteList; } + set + { + if (value) options |= MemberSerializationOptions.OverwriteList; + else options &= ~MemberSerializationOptions.OverwriteList; + } + } + + /// + /// Enables full object-tracking/full-graph support. + /// + public bool AsReference + { + get { return (options & MemberSerializationOptions.AsReference) == MemberSerializationOptions.AsReference; } + set + { + if (value) options |= MemberSerializationOptions.AsReference; + else options &= ~MemberSerializationOptions.AsReference; + + options |= MemberSerializationOptions.AsReferenceHasValue; + } + } + + internal bool AsReferenceHasValue + { + get { return (options & MemberSerializationOptions.AsReferenceHasValue) == MemberSerializationOptions.AsReferenceHasValue; } + set + { + if (value) options |= MemberSerializationOptions.AsReferenceHasValue; + else options &= ~MemberSerializationOptions.AsReferenceHasValue; + } + } + + /// + /// Embeds the type information into the stream, allowing usage with types not known in advance. + /// + public bool DynamicType + { + get { return (options & MemberSerializationOptions.DynamicType) == MemberSerializationOptions.DynamicType; } + set + { + if (value) options |= MemberSerializationOptions.DynamicType; + else options &= ~MemberSerializationOptions.DynamicType; + } + } + + /// + /// Gets or sets a value indicating whether this member is packed (lists/arrays). + /// + public MemberSerializationOptions Options { get { return options; } set { options = value; } } + private MemberSerializationOptions options; + + + } + + /// + /// Additional (optional) settings that control serialization of members + /// + [Flags] + public enum MemberSerializationOptions + { + /// + /// Default; no additional options + /// + None = 0, + /// + /// Indicates that repeated elements should use packed (length-prefixed) encoding + /// + Packed = 1, + /// + /// Indicates that the given item is required + /// + Required = 2, + /// + /// Enables full object-tracking/full-graph support + /// + AsReference = 4, + /// + /// Embeds the type information into the stream, allowing usage with types not known in advance + /// + DynamicType = 8, + /// + /// Indicates whether this field should *repace* existing values (the default is false, meaning *append*). + /// This option only applies to list/array data. + /// + OverwriteList = 16, + /// + /// Determines whether the types AsReferenceDefault value is used, or whether this member's AsReference should be used + /// + AsReferenceHasValue = 32 + } + + /// + /// Declares a member to be used in protocol-buffer serialization, using + /// the given Tag and MemberName. This allows ProtoMemberAttribute usage + /// even for partial classes where the individual members are not + /// under direct control. + /// A DataFormat may be used to optimise the serialization + /// format (for instance, using zigzag encoding for negative numbers, or + /// fixed-length encoding for large values. + /// + [AttributeUsage(AttributeTargets.Class, + AllowMultiple = true, Inherited = false)] + public sealed class ProtoPartialMemberAttribute : ProtoMemberAttribute + { + /// + /// Creates a new ProtoMemberAttribute instance. + /// + /// Specifies the unique tag used to identify this member within the type. + /// Specifies the member to be serialized. + public ProtoPartialMemberAttribute(int tag, string memberName) + : base(tag) + { +#if !NO_RUNTIME + if (string.IsNullOrEmpty(memberName)) throw new ArgumentNullException(nameof(memberName)); +#endif + this.MemberName = memberName; + } + /// + /// The name of the member to be serialized. + /// + public string MemberName { get; private set; } + } +} diff --git a/Runtime/Protobuf-net/ProtoMemberAttribute.cs.meta b/Runtime/Protobuf-net/ProtoMemberAttribute.cs.meta new file mode 100644 index 0000000..2f3dfc9 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoMemberAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 262c0823543b1b3499e2b67ca22f4e62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoReader.cs b/Runtime/Protobuf-net/ProtoReader.cs new file mode 100644 index 0000000..3ea9bf9 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoReader.cs @@ -0,0 +1,1444 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ProtoBuf.Meta; + +namespace ProtoBuf +{ + /// + /// A stateful reader, used to read a protobuf stream. Typical usage would be (sequentially) to call + /// ReadFieldHeader and (after matching the field) an appropriate Read* method. + /// + public sealed class ProtoReader : IDisposable + { + Stream source; + byte[] ioBuffer; + TypeModel model; + int fieldNumber, depth, ioIndex, available; + long position64, blockEnd64, dataRemaining64; + WireType wireType; + bool isFixedLength, internStrings; + private NetObjectCache netCache; + + // this is how many outstanding objects do not currently have + // values for the purposes of reference tracking; we'll default + // to just trapping the root object + // note: objects are trapped (the ref and key mapped) via NoteObject + uint trapCount; // uint is so we can use beq/bne more efficiently than bgt + + /// + /// Gets the number of the field being processed. + /// + public int FieldNumber => fieldNumber; + + /// + /// Indicates the underlying proto serialization format on the wire. + /// + public WireType WireType => wireType; + + /// + /// Creates a new reader against a stream + /// + /// The source stream + /// The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects + /// Additional context about this serialization operation + [Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", error: false)] + public ProtoReader(Stream source, TypeModel model, SerializationContext context) + { + + Init(this, source, model, context, TO_EOF); + } + + internal const long TO_EOF = -1; + + /// + /// Gets / sets a flag indicating whether strings should be checked for repetition; if + /// true, any repeated UTF-8 byte sequence will result in the same String instance, rather + /// than a second instance of the same string. Enabled by default. Note that this uses + /// a custom interner - the system-wide string interner is not used. + /// + public bool InternStrings { get { return internStrings; } set { internStrings = value; } } + + /// + /// Creates a new reader against a stream + /// + /// The source stream + /// The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects + /// Additional context about this serialization operation + /// The number of bytes to read, or -1 to read until the end of the stream + [Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", error: false)] + public ProtoReader(Stream source, TypeModel model, SerializationContext context, int length) + { + Init(this, source, model, context, length); + } + + /// + /// Creates a new reader against a stream + /// + /// The source stream + /// The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects + /// Additional context about this serialization operation + /// The number of bytes to read, or -1 to read until the end of the stream + [Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", error: false)] + public ProtoReader(Stream source, TypeModel model, SerializationContext context, long length) + { + Init(this, source, model, context, length); + } + + private static void Init(ProtoReader reader, Stream source, TypeModel model, SerializationContext context, long length) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) throw new ArgumentException("Cannot read from stream", nameof(source)); + reader.source = source; + reader.ioBuffer = BufferPool.GetBuffer(); + reader.model = model; + bool isFixedLength = length >= 0; + reader.isFixedLength = isFixedLength; + reader.dataRemaining64 = isFixedLength ? length : 0; + + if (context == null) { context = SerializationContext.Default; } + else { context.Freeze(); } + reader.context = context; + reader.position64 = 0; + reader.available = reader.depth = reader.fieldNumber = reader.ioIndex = 0; + reader.blockEnd64 = long.MaxValue; + reader.internStrings = RuntimeTypeModel.Default.InternStrings; + reader.wireType = WireType.None; + reader.trapCount = 1; + if (reader.netCache == null) reader.netCache = new NetObjectCache(); + } + + private SerializationContext context; + + /// + /// Addition information about this deserialization operation. + /// + public SerializationContext Context => context; + + /// + /// Releases resources used by the reader, but importantly does not Dispose the + /// underlying stream; in many typical use-cases the stream is used for different + /// processes, so it is assumed that the consumer will Dispose their stream separately. + /// + public void Dispose() + { + // importantly, this does **not** own the stream, and does not dispose it + source = null; + model = null; + BufferPool.ReleaseBufferToPool(ref ioBuffer); + if (stringInterner != null) + { + stringInterner.Clear(); + stringInterner = null; + } + if (netCache != null) netCache.Clear(); + } + internal int TryReadUInt32VariantWithoutMoving(bool trimNegative, out uint value) + { + if (available < 10) Ensure(10, false); + if (available == 0) + { + value = 0; + return 0; + } + int readPos = ioIndex; + value = ioBuffer[readPos++]; + if ((value & 0x80) == 0) return 1; + value &= 0x7F; + if (available == 1) throw EoF(this); + + uint chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 7; + if ((chunk & 0x80) == 0) return 2; + if (available == 2) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 14; + if ((chunk & 0x80) == 0) return 3; + if (available == 3) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 21; + if ((chunk & 0x80) == 0) return 4; + if (available == 4) throw EoF(this); + + chunk = ioBuffer[readPos]; + value |= chunk << 28; // can only use 4 bits from this chunk + if ((chunk & 0xF0) == 0) return 5; + + if (trimNegative // allow for -ve values + && (chunk & 0xF0) == 0xF0 + && available >= 10 + && ioBuffer[++readPos] == 0xFF + && ioBuffer[++readPos] == 0xFF + && ioBuffer[++readPos] == 0xFF + && ioBuffer[++readPos] == 0xFF + && ioBuffer[++readPos] == 0x01) + { + return 10; + } + throw AddErrorData(new OverflowException(), this); + } + + private uint ReadUInt32Variant(bool trimNegative) + { + int read = TryReadUInt32VariantWithoutMoving(trimNegative, out uint value); + if (read > 0) + { + ioIndex += read; + available -= read; + position64 += read; + return value; + } + throw EoF(this); + } + + private bool TryReadUInt32Variant(out uint value) + { + int read = TryReadUInt32VariantWithoutMoving(false, out value); + if (read > 0) + { + ioIndex += read; + available -= read; + position64 += read; + return true; + } + return false; + } + + /// + /// Reads an unsigned 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public uint ReadUInt32() + { + switch (wireType) + { + case WireType.Variant: + return ReadUInt32Variant(false); + case WireType.Fixed32: + if (available < 4) Ensure(4, true); + position64 += 4; + available -= 4; + return ((uint)ioBuffer[ioIndex++]) + | (((uint)ioBuffer[ioIndex++]) << 8) + | (((uint)ioBuffer[ioIndex++]) << 16) + | (((uint)ioBuffer[ioIndex++]) << 24); + case WireType.Fixed64: + ulong val = ReadUInt64(); + checked { return (uint)val; } + default: + throw CreateWireTypeException(); + } + } + + /// + /// Returns the position of the current reader (note that this is not necessarily the same as the position + /// in the underlying stream, if multiple readers are used on the same stream) + /// + public int Position { get { return checked((int)position64); } } + + /// + /// Returns the position of the current reader (note that this is not necessarily the same as the position + /// in the underlying stream, if multiple readers are used on the same stream) + /// + public long LongPosition { get { return position64; } } + internal void Ensure(int count, bool strict) + { + Helpers.DebugAssert(available <= count, "Asking for data without checking first"); + if (count > ioBuffer.Length) + { + BufferPool.ResizeAndFlushLeft(ref ioBuffer, count, ioIndex, available); + ioIndex = 0; + } + else if (ioIndex + count >= ioBuffer.Length) + { + // need to shift the buffer data to the left to make space + Buffer.BlockCopy(ioBuffer, ioIndex, ioBuffer, 0, available); + ioIndex = 0; + } + count -= available; + int writePos = ioIndex + available, bytesRead; + int canRead = ioBuffer.Length - writePos; + if (isFixedLength) + { // throttle it if needed + if (dataRemaining64 < canRead) canRead = (int)dataRemaining64; + } + while (count > 0 && canRead > 0 && (bytesRead = source.Read(ioBuffer, writePos, canRead)) > 0) + { + available += bytesRead; + count -= bytesRead; + canRead -= bytesRead; + writePos += bytesRead; + if (isFixedLength) { dataRemaining64 -= bytesRead; } + } + if (strict && count > 0) + { + throw EoF(this); + } + + } + /// + /// Reads a signed 16-bit integer from the stream: Variant, Fixed32, Fixed64, SignedVariant + /// + public short ReadInt16() + { + checked { return (short)ReadInt32(); } + } + /// + /// Reads an unsigned 16-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public ushort ReadUInt16() + { + checked { return (ushort)ReadUInt32(); } + } + + /// + /// Reads an unsigned 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public byte ReadByte() + { + checked { return (byte)ReadUInt32(); } + } + + /// + /// Reads a signed 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public sbyte ReadSByte() + { + checked { return (sbyte)ReadInt32(); } + } + + /// + /// Reads a signed 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public int ReadInt32() + { + switch (wireType) + { + case WireType.Variant: + return (int)ReadUInt32Variant(true); + case WireType.Fixed32: + if (available < 4) Ensure(4, true); + position64 += 4; + available -= 4; + return ((int)ioBuffer[ioIndex++]) + | (((int)ioBuffer[ioIndex++]) << 8) + | (((int)ioBuffer[ioIndex++]) << 16) + | (((int)ioBuffer[ioIndex++]) << 24); + case WireType.Fixed64: + long l = ReadInt64(); + checked { return (int)l; } + case WireType.SignedVariant: + return Zag(ReadUInt32Variant(true)); + default: + throw CreateWireTypeException(); + } + } + private const long Int64Msb = ((long)1) << 63; + private const int Int32Msb = ((int)1) << 31; + private static int Zag(uint ziggedValue) + { + int value = (int)ziggedValue; + return (-(value & 0x01)) ^ ((value >> 1) & ~ProtoReader.Int32Msb); + } + + private static long Zag(ulong ziggedValue) + { + long value = (long)ziggedValue; + return (-(value & 0x01L)) ^ ((value >> 1) & ~ProtoReader.Int64Msb); + } + /// + /// Reads a signed 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public long ReadInt64() + { + switch (wireType) + { + case WireType.Variant: + return (long)ReadUInt64Variant(); + case WireType.Fixed32: + return ReadInt32(); + case WireType.Fixed64: + if (available < 8) Ensure(8, true); + position64 += 8; + available -= 8; + +#if NETCOREAPP2_1 + var result = System.Buffers.Binary.BinaryPrimitives.ReadInt64LittleEndian(ioBuffer.AsSpan(ioIndex, 8)); + + ioIndex+= 8; + + return result; +#else + return ((long)ioBuffer[ioIndex++]) + | (((long)ioBuffer[ioIndex++]) << 8) + | (((long)ioBuffer[ioIndex++]) << 16) + | (((long)ioBuffer[ioIndex++]) << 24) + | (((long)ioBuffer[ioIndex++]) << 32) + | (((long)ioBuffer[ioIndex++]) << 40) + | (((long)ioBuffer[ioIndex++]) << 48) + | (((long)ioBuffer[ioIndex++]) << 56); +#endif + case WireType.SignedVariant: + return Zag(ReadUInt64Variant()); + default: + throw CreateWireTypeException(); + } + } + + private int TryReadUInt64VariantWithoutMoving(out ulong value) + { + if (available < 10) Ensure(10, false); + if (available == 0) + { + value = 0; + return 0; + } + int readPos = ioIndex; + value = ioBuffer[readPos++]; + if ((value & 0x80) == 0) return 1; + value &= 0x7F; + if (available == 1) throw EoF(this); + + ulong chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 7; + if ((chunk & 0x80) == 0) return 2; + if (available == 2) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 14; + if ((chunk & 0x80) == 0) return 3; + if (available == 3) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 21; + if ((chunk & 0x80) == 0) return 4; + if (available == 4) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 28; + if ((chunk & 0x80) == 0) return 5; + if (available == 5) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 35; + if ((chunk & 0x80) == 0) return 6; + if (available == 6) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 42; + if ((chunk & 0x80) == 0) return 7; + if (available == 7) throw EoF(this); + + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 49; + if ((chunk & 0x80) == 0) return 8; + if (available == 8) throw EoF(this); + + chunk = ioBuffer[readPos++]; + value |= (chunk & 0x7F) << 56; + if ((chunk & 0x80) == 0) return 9; + if (available == 9) throw EoF(this); + + chunk = ioBuffer[readPos]; + value |= chunk << 63; // can only use 1 bit from this chunk + + if ((chunk & ~(ulong)0x01) != 0) throw AddErrorData(new OverflowException(), this); + return 10; + } + + private ulong ReadUInt64Variant() + { + int read = TryReadUInt64VariantWithoutMoving(out ulong value); + if (read > 0) + { + ioIndex += read; + available -= read; + position64 += read; + return value; + } + throw EoF(this); + } + + private Dictionary stringInterner; + private string Intern(string value) + { + if (value == null) return null; + if (value.Length == 0) return ""; + if (stringInterner == null) + { + stringInterner = new Dictionary + { + { value, value } + }; + } + else if (stringInterner.TryGetValue(value, out string found)) + { + value = found; + } + else + { + stringInterner.Add(value, value); + } + return value; + } + +#if COREFX + static readonly Encoding encoding = Encoding.UTF8; +#else + static readonly UTF8Encoding encoding = new UTF8Encoding(); +#endif + /// + /// Reads a string from the stream (using UTF8); supported wire-types: String + /// + public string ReadString() + { + if (wireType == WireType.String) + { + int bytes = (int)ReadUInt32Variant(false); + if (bytes == 0) return ""; + if (bytes < 0) ThrowInvalidLength(bytes); + if (available < bytes) Ensure(bytes, true); + + string s = encoding.GetString(ioBuffer, ioIndex, bytes); + + if (internStrings) { s = Intern(s); } + available -= bytes; + position64 += bytes; + ioIndex += bytes; + return s; + } + throw CreateWireTypeException(); + } + /// + /// Throws an exception indication that the given value cannot be mapped to an enum. + /// + public void ThrowEnumException(Type type, int value) + { + string desc = type == null ? "" : type.FullName; + throw AddErrorData(new ProtoException("No " + desc + " enum is mapped to the wire-value " + value.ToString()), this); + } + + private void ThrowInvalidLength(long length) + { + throw AddErrorData(new InvalidOperationException("Invalid length: " + length.ToString()), this); + } + + private Exception CreateWireTypeException() + { + return CreateException("Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see https://stackoverflow.com/q/2152978/23354"); + } + + private Exception CreateException(string message) + { + return AddErrorData(new ProtoException(message), this); + } + /// + /// Reads a double-precision number from the stream; supported wire-types: Fixed32, Fixed64 + /// + public +#if !FEAT_SAFE + unsafe +#endif + double ReadDouble() + { + switch (wireType) + { + case WireType.Fixed32: + return ReadSingle(); + case WireType.Fixed64: + long value = ReadInt64(); +#if FEAT_SAFE + return BitConverter.ToDouble(BitConverter.GetBytes(value), 0); +#else + return *(double*)&value; +#endif + default: + throw CreateWireTypeException(); + } + } + + /// + /// Reads (merges) a sub-message from the stream, internally calling StartSubItem and EndSubItem, and (in between) + /// parsing the message in accordance with the model associated with the reader + /// + public static object ReadObject(object value, int key, ProtoReader reader) + { + return ReadTypedObject(value, key, reader, null); + } + + internal static object ReadTypedObject(object value, int key, ProtoReader reader, Type type) + { + if (reader.model == null) + { + throw AddErrorData(new InvalidOperationException("Cannot deserialize sub-objects unless a model is provided"), reader); + } + SubItemToken token = ProtoReader.StartSubItem(reader); + if (key >= 0) + { + value = reader.model.Deserialize(key, value, reader); + } + else if (type != null && reader.model.TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, true, false, null)) + { + // ok + } + else + { + TypeModel.ThrowUnexpectedType(type); + } + ProtoReader.EndSubItem(token, reader); + return value; + } + + /// + /// Makes the end of consuming a nested message in the stream; the stream must be either at the correct EndGroup + /// marker, or all fields of the sub-message must have been consumed (in either case, this means ReadFieldHeader + /// should return zero) + /// + public static void EndSubItem(SubItemToken token, ProtoReader reader) + { + if (reader == null) throw new ArgumentNullException("reader"); + long value64 = token.value64; + switch (reader.wireType) + { + case WireType.EndGroup: + if (value64 >= 0) throw AddErrorData(new ArgumentException("token"), reader); + if (-(int)value64 != reader.fieldNumber) throw reader.CreateException("Wrong group was ended"); // wrong group ended! + reader.wireType = WireType.None; // this releases ReadFieldHeader + reader.depth--; + break; + // case WireType.None: // TODO reinstate once reads reset the wire-type + default: + if (value64 < reader.position64) throw reader.CreateException($"Sub-message not read entirely; expected {value64}, was {reader.position64}"); + if (reader.blockEnd64 != reader.position64 && reader.blockEnd64 != long.MaxValue) + { + throw reader.CreateException("Sub-message not read correctly"); + } + reader.blockEnd64 = value64; + reader.depth--; + break; + /*default: + throw reader.BorkedIt(); */ + } + } + + /// + /// Begins consuming a nested message in the stream; supported wire-types: StartGroup, String + /// + /// The token returned must be help and used when callining EndSubItem + public static SubItemToken StartSubItem(ProtoReader reader) + { + if (reader == null) throw new ArgumentNullException("reader"); + switch (reader.wireType) + { + case WireType.StartGroup: + reader.wireType = WireType.None; // to prevent glitches from double-calling + reader.depth++; + return new SubItemToken((long)(-reader.fieldNumber)); + case WireType.String: + long len = (long)reader.ReadUInt64Variant(); + if (len < 0) reader.ThrowInvalidLength(len); + long lastEnd = reader.blockEnd64; + reader.blockEnd64 = reader.position64 + len; + reader.depth++; + return new SubItemToken(lastEnd); + default: + throw reader.CreateWireTypeException(); // throws + } + } + + /// + /// Reads a field header from the stream, setting the wire-type and retuning the field number. If no + /// more fields are available, then 0 is returned. This methods respects sub-messages. + /// + public int ReadFieldHeader() + { + // at the end of a group the caller must call EndSubItem to release the + // reader (which moves the status to Error, since ReadFieldHeader must + // then be called) + if (blockEnd64 <= position64 || wireType == WireType.EndGroup) { return 0; } + + if (TryReadUInt32Variant(out uint tag) && tag != 0) + { + wireType = (WireType)(tag & 7); + fieldNumber = (int)(tag >> 3); + if (fieldNumber < 1) throw new ProtoException("Invalid field in source data: " + fieldNumber.ToString()); + } + else + { + wireType = WireType.None; + fieldNumber = 0; + } + if (wireType == ProtoBuf.WireType.EndGroup) + { + if (depth > 0) return 0; // spoof an end, but note we still set the field-number + throw new ProtoException("Unexpected end-group in source data; this usually means the source data is corrupt"); + } + return fieldNumber; + } + /// + /// Looks ahead to see whether the next field in the stream is what we expect + /// (typically; what we've just finished reading - for example ot read successive list items) + /// + public bool TryReadFieldHeader(int field) + { + // check for virtual end of stream + if (blockEnd64 <= position64 || wireType == WireType.EndGroup) { return false; } + + int read = TryReadUInt32VariantWithoutMoving(false, out uint tag); + WireType tmpWireType; // need to catch this to exclude (early) any "end group" tokens + if (read > 0 && ((int)tag >> 3) == field + && (tmpWireType = (WireType)(tag & 7)) != WireType.EndGroup) + { + wireType = tmpWireType; + fieldNumber = field; + position64 += read; + ioIndex += read; + available -= read; + return true; + } + return false; + } + + /// + /// Get the TypeModel associated with this reader + /// + public TypeModel Model { get { return model; } } + + /// + /// Compares the streams current wire-type to the hinted wire-type, updating the reader if necessary; for example, + /// a Variant may be updated to SignedVariant. If the hinted wire-type is unrelated then no change is made. + /// + public void Hint(WireType wireType) + { + if (this.wireType == wireType) { } // fine; everything as we expect + else if (((int)wireType & 7) == (int)this.wireType) + { // the underling type is a match; we're customising it with an extension + this.wireType = wireType; + } + // note no error here; we're OK about using alternative data + } + + /// + /// Verifies that the stream's current wire-type is as expected, or a specialized sub-type (for example, + /// SignedVariant) - in which case the current wire-type is updated. Otherwise an exception is thrown. + /// + public void Assert(WireType wireType) + { + if (this.wireType == wireType) { } // fine; everything as we expect + else if (((int)wireType & 7) == (int)this.wireType) + { // the underling type is a match; we're customising it with an extension + this.wireType = wireType; + } + else + { // nope; that is *not* what we were expecting! + throw CreateWireTypeException(); + } + } + + /// + /// Discards the data for the current field. + /// + public void SkipField() + { + switch (wireType) + { + case WireType.Fixed32: + if (available < 4) Ensure(4, true); + available -= 4; + ioIndex += 4; + position64 += 4; + return; + case WireType.Fixed64: + if (available < 8) Ensure(8, true); + available -= 8; + ioIndex += 8; + position64 += 8; + return; + case WireType.String: + long len = (long)ReadUInt64Variant(); + if (len < 0) ThrowInvalidLength(len); + if (len <= available) + { // just jump it! + available -= (int)len; + ioIndex += (int)len; + position64 += len; + return; + } + // everything remaining in the buffer is garbage + position64 += len; // assumes success, but if it fails we're screwed anyway + len -= available; // discount anything we've got to-hand + ioIndex = available = 0; // note that we have no data in the buffer + if (isFixedLength) + { + if (len > dataRemaining64) throw EoF(this); + // else assume we're going to be OK + dataRemaining64 -= len; + } + ProtoReader.Seek(source, len, ioBuffer); + return; + case WireType.Variant: + case WireType.SignedVariant: + ReadUInt64Variant(); // and drop it + return; + case WireType.StartGroup: + int originalFieldNumber = this.fieldNumber; + depth++; // need to satisfy the sanity-checks in ReadFieldHeader + while (ReadFieldHeader() > 0) { SkipField(); } + depth--; + if (wireType == WireType.EndGroup && fieldNumber == originalFieldNumber) + { // we expect to exit in a similar state to how we entered + wireType = ProtoBuf.WireType.None; + return; + } + throw CreateWireTypeException(); + case WireType.None: // treat as explicit errorr + case WireType.EndGroup: // treat as explicit error + default: // treat as implicit error + throw CreateWireTypeException(); + } + } + + /// + /// Reads an unsigned 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public ulong ReadUInt64() + { + switch (wireType) + { + case WireType.Variant: + return ReadUInt64Variant(); + case WireType.Fixed32: + return ReadUInt32(); + case WireType.Fixed64: + if (available < 8) Ensure(8, true); + position64 += 8; + available -= 8; + + return ((ulong)ioBuffer[ioIndex++]) + | (((ulong)ioBuffer[ioIndex++]) << 8) + | (((ulong)ioBuffer[ioIndex++]) << 16) + | (((ulong)ioBuffer[ioIndex++]) << 24) + | (((ulong)ioBuffer[ioIndex++]) << 32) + | (((ulong)ioBuffer[ioIndex++]) << 40) + | (((ulong)ioBuffer[ioIndex++]) << 48) + | (((ulong)ioBuffer[ioIndex++]) << 56); + default: + throw CreateWireTypeException(); + } + } + /// + /// Reads a single-precision number from the stream; supported wire-types: Fixed32, Fixed64 + /// + public +#if !FEAT_SAFE + unsafe +#endif + float ReadSingle() + { + switch (wireType) + { + case WireType.Fixed32: + { + int value = ReadInt32(); +#if FEAT_SAFE + return BitConverter.ToSingle(BitConverter.GetBytes(value), 0); +#else + return *(float*)&value; +#endif + } + case WireType.Fixed64: + { + double value = ReadDouble(); + float f = (float)value; + if (float.IsInfinity(f) && !double.IsInfinity(value)) + { + throw AddErrorData(new OverflowException(), this); + } + return f; + } + default: + throw CreateWireTypeException(); + } + } + + /// + /// Reads a boolean value from the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + /// + public bool ReadBoolean() + { + switch (ReadUInt32()) + { + case 0: return false; + case 1: return true; + default: throw CreateException("Unexpected boolean value"); + } + } + + private static readonly byte[] EmptyBlob = new byte[0]; + /// + /// Reads a byte-sequence from the stream, appending them to an existing byte-sequence (which can be null); supported wire-types: String + /// + public static byte[] AppendBytes(byte[] value, ProtoReader reader) + { + if (reader == null) throw new ArgumentNullException(nameof(reader)); + switch (reader.wireType) + { + case WireType.String: + int len = (int)reader.ReadUInt32Variant(false); + reader.wireType = WireType.None; + if (len == 0) return value ?? EmptyBlob; + if (len < 0) reader.ThrowInvalidLength(len); + int offset; + if (value == null || value.Length == 0) + { + offset = 0; + value = new byte[len]; + } + else + { + offset = value.Length; + byte[] tmp = new byte[value.Length + len]; + Buffer.BlockCopy(value, 0, tmp, 0, value.Length); + value = tmp; + } + // value is now sized with the final length, and (if necessary) + // contains the old data up to "offset" + reader.position64 += len; // assume success + while (len > reader.available) + { + if (reader.available > 0) + { + // copy what we *do* have + Buffer.BlockCopy(reader.ioBuffer, reader.ioIndex, value, offset, reader.available); + len -= reader.available; + offset += reader.available; + reader.ioIndex = reader.available = 0; // we've drained the buffer + } + // now refill the buffer (without overflowing it) + int count = len > reader.ioBuffer.Length ? reader.ioBuffer.Length : len; + if (count > 0) reader.Ensure(count, true); + } + // at this point, we know that len <= available + if (len > 0) + { // still need data, but we have enough buffered + Buffer.BlockCopy(reader.ioBuffer, reader.ioIndex, value, offset, len); + reader.ioIndex += len; + reader.available -= len; + } + return value; + case WireType.Variant: + return new byte[0]; + default: + throw reader.CreateWireTypeException(); + } + } + + //static byte[] ReadBytes(Stream stream, int length) + //{ + // if (stream == null) throw new ArgumentNullException("stream"); + // if (length < 0) throw new ArgumentOutOfRangeException("length"); + // byte[] buffer = new byte[length]; + // int offset = 0, read; + // while (length > 0 && (read = stream.Read(buffer, offset, length)) > 0) + // { + // length -= read; + // } + // if (length > 0) throw EoF(null); + // return buffer; + //} + private static int ReadByteOrThrow(Stream source) + { + int val = source.ReadByte(); + if (val < 0) throw EoF(null); + return val; + } + + /// + /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length + /// reader to be created. + /// + public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber) + => ReadLengthPrefix(source, expectHeader, style, out fieldNumber, out int bytesRead); + + /// + /// Reads a little-endian encoded integer. An exception is thrown if the data is not all available. + /// + public static int DirectReadLittleEndianInt32(Stream source) + { + return ReadByteOrThrow(source) + | (ReadByteOrThrow(source) << 8) + | (ReadByteOrThrow(source) << 16) + | (ReadByteOrThrow(source) << 24); + } + + /// + /// Reads a big-endian encoded integer. An exception is thrown if the data is not all available. + /// + public static int DirectReadBigEndianInt32(Stream source) + { + return (ReadByteOrThrow(source) << 24) + | (ReadByteOrThrow(source) << 16) + | (ReadByteOrThrow(source) << 8) + | ReadByteOrThrow(source); + } + + /// + /// Reads a varint encoded integer. An exception is thrown if the data is not all available. + /// + public static int DirectReadVarintInt32(Stream source) + { + int bytes = TryReadUInt64Variant(source, out ulong val); + if (bytes <= 0) throw EoF(null); + return checked((int)val); + } + + /// + /// Reads a string (of a given lenth, in bytes) directly from the source into a pre-existing buffer. An exception is thrown if the data is not all available. + /// + public static void DirectReadBytes(Stream source, byte[] buffer, int offset, int count) + { + int read; + if (source == null) throw new ArgumentNullException("source"); + while (count > 0 && (read = source.Read(buffer, offset, count)) > 0) + { + count -= read; + offset += read; + } + if (count > 0) throw EoF(null); + } + + /// + /// Reads a given number of bytes directly from the source. An exception is thrown if the data is not all available. + /// + public static byte[] DirectReadBytes(Stream source, int count) + { + byte[] buffer = new byte[count]; + DirectReadBytes(source, buffer, 0, count); + return buffer; + } + + /// + /// Reads a string (of a given lenth, in bytes) directly from the source. An exception is thrown if the data is not all available. + /// + public static string DirectReadString(Stream source, int length) + { + byte[] buffer = new byte[length]; + DirectReadBytes(source, buffer, 0, length); + return Encoding.UTF8.GetString(buffer, 0, length); + } + + /// + /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length + /// reader to be created. + /// + public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead) + { + if (style == PrefixStyle.None) + { + bytesRead = fieldNumber = 0; + return int.MaxValue; // avoid the long.maxvalue causing overflow + } + long len64 = ReadLongLengthPrefix(source, expectHeader, style, out fieldNumber, out bytesRead); + return checked((int)len64); + } + + /// + /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length + /// reader to be created. + /// + public static long ReadLongLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead) + { + fieldNumber = 0; + switch (style) + { + case PrefixStyle.None: + bytesRead = 0; + return long.MaxValue; + case PrefixStyle.Base128: + ulong val; + int tmpBytesRead; + bytesRead = 0; + if (expectHeader) + { + tmpBytesRead = ProtoReader.TryReadUInt64Variant(source, out val); + bytesRead += tmpBytesRead; + if (tmpBytesRead > 0) + { + if ((val & 7) != (uint)WireType.String) + { // got a header, but it isn't a string + throw new InvalidOperationException(); + } + fieldNumber = (int)(val >> 3); + tmpBytesRead = ProtoReader.TryReadUInt64Variant(source, out val); + bytesRead += tmpBytesRead; + if (bytesRead == 0) + { // got a header, but no length + throw EoF(null); + } + return (long)val; + } + else + { // no header + bytesRead = 0; + return -1; + } + } + // check for a length + tmpBytesRead = ProtoReader.TryReadUInt64Variant(source, out val); + bytesRead += tmpBytesRead; + return bytesRead < 0 ? -1 : (long)val; + + case PrefixStyle.Fixed32: + { + int b = source.ReadByte(); + if (b < 0) + { + bytesRead = 0; + return -1; + } + bytesRead = 4; + return b + | (ReadByteOrThrow(source) << 8) + | (ReadByteOrThrow(source) << 16) + | (ReadByteOrThrow(source) << 24); + } + case PrefixStyle.Fixed32BigEndian: + { + int b = source.ReadByte(); + if (b < 0) + { + bytesRead = 0; + return -1; + } + bytesRead = 4; + return (b << 24) + | (ReadByteOrThrow(source) << 16) + | (ReadByteOrThrow(source) << 8) + | ReadByteOrThrow(source); + } + default: + throw new ArgumentOutOfRangeException("style"); + } + } + + /// The number of bytes consumed; 0 if no data available + private static int TryReadUInt64Variant(Stream source, out ulong value) + { + value = 0; + int b = source.ReadByte(); + if (b < 0) { return 0; } + value = (uint)b; + if ((value & 0x80) == 0) { return 1; } + value &= 0x7F; + int bytesRead = 1, shift = 7; + while (bytesRead < 9) + { + b = source.ReadByte(); + if (b < 0) throw EoF(null); + value |= ((ulong)b & 0x7F) << shift; + shift += 7; + bytesRead++; + + if ((b & 0x80) == 0) return bytesRead; + } + b = source.ReadByte(); + if (b < 0) throw EoF(null); + if ((b & 1) == 0) // only use 1 bit from the last byte + { + value |= ((ulong)b & 0x7F) << shift; + return ++bytesRead; + } + throw new OverflowException(); + } + + internal static void Seek(Stream source, long count, byte[] buffer) + { + if (source.CanSeek) + { + source.Seek(count, SeekOrigin.Current); + count = 0; + } + else if (buffer != null) + { + int bytesRead; + while (count > buffer.Length && (bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) + { + count -= bytesRead; + } + while (count > 0 && (bytesRead = source.Read(buffer, 0, (int)count)) > 0) + { + count -= bytesRead; + } + } + else // borrow a buffer + { + buffer = BufferPool.GetBuffer(); + try + { + int bytesRead; + while (count > buffer.Length && (bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) + { + count -= bytesRead; + } + while (count > 0 && (bytesRead = source.Read(buffer, 0, (int)count)) > 0) + { + count -= bytesRead; + } + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + if (count > 0) throw EoF(null); + } + internal static Exception AddErrorData(Exception exception, ProtoReader source) + { +#if !CF && !PORTABLE + if (exception != null && source != null && !exception.Data.Contains("protoSource")) + { + exception.Data.Add("protoSource", string.Format("tag={0}; wire-type={1}; offset={2}; depth={3}", + source.fieldNumber, source.wireType, source.position64, source.depth)); + } +#endif + return exception; + } + + private static Exception EoF(ProtoReader source) + { + return AddErrorData(new EndOfStreamException(), source); + } + + /// + /// Copies the current field into the instance as extension data + /// + public void AppendExtensionData(IExtensible instance) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + IExtension extn = instance.GetExtensionObject(true); + bool commit = false; + // unusually we *don't* want "using" here; the "finally" does that, with + // the extension object being responsible for disposal etc + Stream dest = extn.BeginAppend(); + try + { + //TODO: replace this with stream-based, buffered raw copying + using (ProtoWriter writer = ProtoWriter.Create(dest, model, null)) + { + AppendExtensionField(writer); + writer.Close(); + } + commit = true; + } + finally { extn.EndAppend(dest, commit); } + } + + private void AppendExtensionField(ProtoWriter writer) + { + //TODO: replace this with stream-based, buffered raw copying + ProtoWriter.WriteFieldHeader(fieldNumber, wireType, writer); + switch (wireType) + { + case WireType.Fixed32: + ProtoWriter.WriteInt32(ReadInt32(), writer); + return; + case WireType.Variant: + case WireType.SignedVariant: + case WireType.Fixed64: + ProtoWriter.WriteInt64(ReadInt64(), writer); + return; + case WireType.String: + ProtoWriter.WriteBytes(AppendBytes(null, this), writer); + return; + case WireType.StartGroup: + SubItemToken readerToken = StartSubItem(this), + writerToken = ProtoWriter.StartSubItem(null, writer); + while (ReadFieldHeader() > 0) { AppendExtensionField(writer); } + EndSubItem(readerToken, this); + ProtoWriter.EndSubItem(writerToken, writer); + return; + case WireType.None: // treat as explicit errorr + case WireType.EndGroup: // treat as explicit error + default: // treat as implicit error + throw CreateWireTypeException(); + } + } + + /// + /// Indicates whether the reader still has data remaining in the current sub-item, + /// additionally setting the wire-type for the next field if there is more data. + /// This is used when decoding packed data. + /// + public static bool HasSubValue(ProtoBuf.WireType wireType, ProtoReader source) + { + if (source == null) throw new ArgumentNullException("source"); + // check for virtual end of stream + if (source.blockEnd64 <= source.position64 || wireType == WireType.EndGroup) { return false; } + source.wireType = wireType; + return true; + } + + internal int GetTypeKey(ref Type type) + { + return model.GetKey(ref type); + } + + internal NetObjectCache NetCache => netCache; + + internal Type DeserializeType(string value) + { + return TypeModel.DeserializeType(model, value); + } + + internal void SetRootObject(object value) + { + netCache.SetKeyedObject(NetObjectCache.Root, value); + trapCount--; + } + + /// + /// Utility method, not intended for public use; this helps maintain the root object is complex scenarios + /// + public static void NoteObject(object value, ProtoReader reader) + { + if (reader == null) throw new ArgumentNullException("reader"); + if (reader.trapCount != 0) + { + reader.netCache.RegisterTrappedObject(value); + reader.trapCount--; + } + } + + /// + /// Reads a Type from the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String + /// + public Type ReadType() + { + return TypeModel.DeserializeType(model, ReadString()); + } + + internal void TrapNextObject(int newObjectKey) + { + trapCount++; + netCache.SetKeyedObject(newObjectKey, null); // use null as a temp + } + + internal void CheckFullyConsumed() + { + if (isFixedLength) + { + if (dataRemaining64 != 0) throw new ProtoException("Incorrect number of bytes consumed"); + } + else + { + if (available != 0) throw new ProtoException("Unconsumed data left in the buffer; this suggests corrupt input"); + } + } + + /// + /// Merge two objects using the details from the current reader; this is used to change the type + /// of objects when an inheritance relationship is discovered later than usual during deserilazation. + /// + public static object Merge(ProtoReader parent, object from, object to) + { + if (parent == null) throw new ArgumentNullException("parent"); + TypeModel model = parent.Model; + SerializationContext ctx = parent.Context; + if (model == null) throw new InvalidOperationException("Types cannot be merged unless a type-model has been specified"); + using (var ms = new MemoryStream()) + { + model.Serialize(ms, from, ctx); + ms.Position = 0; + return model.Deserialize(ms, to, null); + } + } + + #region RECYCLER + + internal static ProtoReader Create(Stream source, TypeModel model, SerializationContext context, int len) + => Create(source, model, context, (long)len); + /// + /// Creates a new reader against a stream + /// + /// The source stream + /// The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects + /// Additional context about this serialization operation + /// The number of bytes to read, or -1 to read until the end of the stream + public static ProtoReader Create(Stream source, TypeModel model, SerializationContext context = null, long length = TO_EOF) + { + ProtoReader reader = GetRecycled(); + if (reader == null) + { +#pragma warning disable CS0618 + return new ProtoReader(source, model, context, length); +#pragma warning restore CS0618 + } + Init(reader, source, model, context, length); + return reader; + } + +#if !PLAT_NO_THREADSTATIC + [ThreadStatic] + private static ProtoReader lastReader; + + private static ProtoReader GetRecycled() + { + ProtoReader tmp = lastReader; + lastReader = null; + return tmp; + } + internal static void Recycle(ProtoReader reader) + { + if (reader != null) + { + reader.Dispose(); + lastReader = reader; + } + } +#elif !PLAT_NO_INTERLOCKED + private static object lastReader; + private static ProtoReader GetRecycled() + { + return (ProtoReader)System.Threading.Interlocked.Exchange(ref lastReader, null); + } + internal static void Recycle(ProtoReader reader) + { + if(reader != null) + { + reader.Dispose(); + System.Threading.Interlocked.Exchange(ref lastReader, reader); + } + } +#else + private static readonly object recycleLock = new object(); + private static ProtoReader lastReader; + private static ProtoReader GetRecycled() + { + lock(recycleLock) + { + ProtoReader tmp = lastReader; + lastReader = null; + return tmp; + } + } + internal static void Recycle(ProtoReader reader) + { + if(reader != null) + { + reader.Dispose(); + lock(recycleLock) + { + lastReader = reader; + } + } + } +#endif + + #endregion + } +} diff --git a/Runtime/Protobuf-net/ProtoReader.cs.meta b/Runtime/Protobuf-net/ProtoReader.cs.meta new file mode 100644 index 0000000..0826a16 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd9c8ee218e18b14b9058926b6bbc8fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ProtoWriter.cs b/Runtime/Protobuf-net/ProtoWriter.cs new file mode 100644 index 0000000..23fa42d --- /dev/null +++ b/Runtime/Protobuf-net/ProtoWriter.cs @@ -0,0 +1,1003 @@ +using System; +using System.IO; +using System.Text; +using ProtoBuf.Meta; + +namespace ProtoBuf +{ + /// + /// Represents an output stream for writing protobuf data. + /// + /// Why is the API backwards (static methods with writer arguments)? + /// See: http://marcgravell.blogspot.com/2010/03/last-will-be-first-and-first-will-be.html + /// + public sealed class ProtoWriter : IDisposable + { + private Stream dest; + TypeModel model; + /// + /// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type). + /// + /// The object to write. + /// The key that uniquely identifies the type within the model. + /// The destination. + public static void WriteObject(object value, int key, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (writer.model == null) + { + throw new InvalidOperationException("Cannot serialize sub-objects unless a model is provided"); + } + + SubItemToken token = StartSubItem(value, writer); + if (key >= 0) + { + writer.model.Serialize(key, value, writer); + } + else if (writer.model != null && writer.model.TrySerializeAuxiliaryType(writer, value.GetType(), DataFormat.Default, Serializer.ListItemTag, value, false, null)) + { + // all ok + } + else + { + TypeModel.ThrowUnexpectedType(value.GetType()); + } + + EndSubItem(token, writer); + } + /// + /// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type) - but the + /// caller is asserting that this relationship is non-recursive; no recursion check will be + /// performed. + /// + /// The object to write. + /// The key that uniquely identifies the type within the model. + /// The destination. + public static void WriteRecursionSafeObject(object value, int key, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + if (writer.model == null) + { + throw new InvalidOperationException("Cannot serialize sub-objects unless a model is provided"); + } + SubItemToken token = StartSubItem(null, writer); + writer.model.Serialize(key, value, writer); + EndSubItem(token, writer); + } + + internal static void WriteObject(object value, int key, ProtoWriter writer, PrefixStyle style, int fieldNumber) + { + if (writer.model == null) + { + throw new InvalidOperationException("Cannot serialize sub-objects unless a model is provided"); + } + if (writer.wireType != WireType.None) throw ProtoWriter.CreateException(writer); + + switch (style) + { + case PrefixStyle.Base128: + writer.wireType = WireType.String; + writer.fieldNumber = fieldNumber; + if (fieldNumber > 0) WriteHeaderCore(fieldNumber, WireType.String, writer); + break; + case PrefixStyle.Fixed32: + case PrefixStyle.Fixed32BigEndian: + writer.fieldNumber = 0; + writer.wireType = WireType.Fixed32; + break; + default: + throw new ArgumentOutOfRangeException("style"); + } + SubItemToken token = StartSubItem(value, writer, true); + if (key < 0) + { + if (!writer.model.TrySerializeAuxiliaryType(writer, value.GetType(), DataFormat.Default, Serializer.ListItemTag, value, false, null)) + { + TypeModel.ThrowUnexpectedType(value.GetType()); + } + } + else + { + writer.model.Serialize(key, value, writer); + } + EndSubItem(token, writer, style); + } + + internal int GetTypeKey(ref Type type) + { + return model.GetKey(ref type); + } + + private readonly NetObjectCache netCache = new NetObjectCache(); + internal NetObjectCache NetCache => netCache; + + private int fieldNumber, flushLock; + WireType wireType; + internal WireType WireType { get { return wireType; } } + /// + /// Writes a field-header, indicating the format of the next data we plan to write. + /// + public static void WriteFieldHeader(int fieldNumber, WireType wireType, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (writer.wireType != WireType.None) throw new InvalidOperationException("Cannot write a " + wireType.ToString() + + " header until the " + writer.wireType.ToString() + " data has been written"); + if (fieldNumber < 0) throw new ArgumentOutOfRangeException("fieldNumber"); +#if DEBUG + switch (wireType) + { // validate requested header-type + case WireType.Fixed32: + case WireType.Fixed64: + case WireType.String: + case WireType.StartGroup: + case WireType.SignedVariant: + case WireType.Variant: + break; // fine + case WireType.None: + case WireType.EndGroup: + default: + throw new ArgumentException("Invalid wire-type: " + wireType.ToString(), "wireType"); + } +#endif + if (writer.packedFieldNumber == 0) + { + writer.fieldNumber = fieldNumber; + writer.wireType = wireType; + WriteHeaderCore(fieldNumber, wireType, writer); + } + else if (writer.packedFieldNumber == fieldNumber) + { // we'll set things up, but note we *don't* actually write the header here + switch (wireType) + { + case WireType.Fixed32: + case WireType.Fixed64: + case WireType.Variant: + case WireType.SignedVariant: + break; // fine + default: + throw new InvalidOperationException("Wire-type cannot be encoded as packed: " + wireType.ToString()); + } + writer.fieldNumber = fieldNumber; + writer.wireType = wireType; + } + else + { + throw new InvalidOperationException("Field mismatch during packed encoding; expected " + writer.packedFieldNumber.ToString() + " but received " + fieldNumber.ToString()); + } + } + internal static void WriteHeaderCore(int fieldNumber, WireType wireType, ProtoWriter writer) + { + uint header = (((uint)fieldNumber) << 3) + | (((uint)wireType) & 7); + WriteUInt32Variant(header, writer); + } + + /// + /// Writes a byte-array to the stream; supported wire-types: String + /// + public static void WriteBytes(byte[] data, ProtoWriter writer) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + ProtoWriter.WriteBytes(data, 0, data.Length, writer); + } + /// + /// Writes a byte-array to the stream; supported wire-types: String + /// + public static void WriteBytes(byte[] data, int offset, int length, ProtoWriter writer) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + switch (writer.wireType) + { + case WireType.Fixed32: + if (length != 4) throw new ArgumentException(nameof(length)); + goto CopyFixedLength; // ugly but effective + case WireType.Fixed64: + if (length != 8) throw new ArgumentException(nameof(length)); + goto CopyFixedLength; // ugly but effective + case WireType.String: + WriteUInt32Variant((uint)length, writer); + writer.wireType = WireType.None; + if (length == 0) return; + if (writer.flushLock != 0 || length <= writer.ioBuffer.Length) // write to the buffer + { + goto CopyFixedLength; // ugly but effective + } + // writing data that is bigger than the buffer (and the buffer + // isn't currently locked due to a sub-object needing the size backfilled) + Flush(writer); // commit any existing data from the buffer + // now just write directly to the underlying stream + writer.dest.Write(data, offset, length); + writer.position64 += length; // since we've flushed offset etc is 0, and remains + // zero since we're writing directly to the stream + return; + } + throw CreateException(writer); + CopyFixedLength: // no point duplicating this lots of times, and don't really want another stackframe + DemandSpace(length, writer); + Buffer.BlockCopy(data, offset, writer.ioBuffer, writer.ioIndex, length); + IncrementedAndReset(length, writer); + } + private static void CopyRawFromStream(Stream source, ProtoWriter writer) + { + byte[] buffer = writer.ioBuffer; + int space = buffer.Length - writer.ioIndex, bytesRead = 1; // 1 here to spoof case where already full + + // try filling the buffer first + while (space > 0 && (bytesRead = source.Read(buffer, writer.ioIndex, space)) > 0) + { + writer.ioIndex += bytesRead; + writer.position64 += bytesRead; + space -= bytesRead; + } + if (bytesRead <= 0) return; // all done using just the buffer; stream exhausted + + // at this point the stream still has data, but buffer is full; + if (writer.flushLock == 0) + { + // flush the buffer and write to the underlying stream instead + Flush(writer); + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) + { + writer.dest.Write(buffer, 0, bytesRead); + writer.position64 += bytesRead; + } + } + else + { + do + { + // need more space; resize (double) as necessary, + // requesting a reasonable minimum chunk each time + // (128 is the minimum; there may actually be much + // more space than this in the buffer) + DemandSpace(128, writer); + if ((bytesRead = source.Read(writer.ioBuffer, writer.ioIndex, + writer.ioBuffer.Length - writer.ioIndex)) <= 0) break; + writer.position64 += bytesRead; + writer.ioIndex += bytesRead; + } while (true); + } + + } + private static void IncrementedAndReset(int length, ProtoWriter writer) + { + Helpers.DebugAssert(length >= 0); + writer.ioIndex += length; + writer.position64 += length; + writer.wireType = WireType.None; + } + int depth = 0; + const int RecursionCheckDepth = 25; + /// + /// Indicates the start of a nested record. + /// + /// The instance to write. + /// The destination. + /// A token representing the state of the stream; this token is given to EndSubItem. + public static SubItemToken StartSubItem(object instance, ProtoWriter writer) + { + return StartSubItem(instance, writer, false); + } + + MutableList recursionStack; + private void CheckRecursionStackAndPush(object instance) + { + int hitLevel; + if (recursionStack == null) { recursionStack = new MutableList(); } + else if (instance != null && (hitLevel = recursionStack.IndexOfReference(instance)) >= 0) + { +#if DEBUG + Helpers.DebugWriteLine("Stack:"); + foreach (object obj in recursionStack) + { + Helpers.DebugWriteLine(obj == null ? "" : obj.ToString()); + } + Helpers.DebugWriteLine(instance == null ? "" : instance.ToString()); +#endif + throw new ProtoException("Possible recursion detected (offset: " + (recursionStack.Count - hitLevel).ToString() + " level(s)): " + instance.ToString()); + } + recursionStack.Add(instance); + } + private void PopRecursionStack() { recursionStack.RemoveLast(); } + + private static SubItemToken StartSubItem(object instance, ProtoWriter writer, bool allowFixed) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (++writer.depth > RecursionCheckDepth) + { + writer.CheckRecursionStackAndPush(instance); + } + if (writer.packedFieldNumber != 0) throw new InvalidOperationException("Cannot begin a sub-item while performing packed encoding"); + switch (writer.wireType) + { + case WireType.StartGroup: + writer.wireType = WireType.None; + return new SubItemToken((long)(-writer.fieldNumber)); + case WireType.String: +#if DEBUG + if (writer.model != null && writer.model.ForwardsOnly) + { + throw new ProtoException("Should not be buffering data: " + instance ?? "(null)"); + } +#endif + writer.wireType = WireType.None; + DemandSpace(32, writer); // make some space in anticipation... + writer.flushLock++; + writer.position64++; + return new SubItemToken((long)(writer.ioIndex++)); // leave 1 space (optimistic) for length + case WireType.Fixed32: + { + if (!allowFixed) throw CreateException(writer); + DemandSpace(32, writer); // make some space in anticipation... + writer.flushLock++; + SubItemToken token = new SubItemToken((long)writer.ioIndex); + ProtoWriter.IncrementedAndReset(4, writer); // leave 4 space (rigid) for length + return token; + } + default: + throw CreateException(writer); + } + } + + /// + /// Indicates the end of a nested record. + /// + /// The token obtained from StartubItem. + /// The destination. + public static void EndSubItem(SubItemToken token, ProtoWriter writer) + { + EndSubItem(token, writer, PrefixStyle.Base128); + } + private static void EndSubItem(SubItemToken token, ProtoWriter writer, PrefixStyle style) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (writer.wireType != WireType.None) { throw CreateException(writer); } + int value = (int)token.value64; + if (writer.depth <= 0) throw CreateException(writer); + if (writer.depth-- > RecursionCheckDepth) + { + writer.PopRecursionStack(); + } + writer.packedFieldNumber = 0; // ending the sub-item always wipes packed encoding + if (value < 0) + { // group - very simple append + WriteHeaderCore(-value, WireType.EndGroup, writer); + writer.wireType = WireType.None; + return; + } + + // so we're backfilling the length into an existing sequence + int len; + switch (style) + { + case PrefixStyle.Fixed32: + len = (int)((writer.ioIndex - value) - 4); + ProtoWriter.WriteInt32ToBuffer(len, writer.ioBuffer, value); + break; + case PrefixStyle.Fixed32BigEndian: + len = (int)((writer.ioIndex - value) - 4); + byte[] buffer = writer.ioBuffer; + ProtoWriter.WriteInt32ToBuffer(len, buffer, value); + // and swap the byte order + byte b = buffer[value]; + buffer[value] = buffer[value + 3]; + buffer[value + 3] = b; + b = buffer[value + 1]; + buffer[value + 1] = buffer[value + 2]; + buffer[value + 2] = b; + break; + case PrefixStyle.Base128: + // string - complicated because we only reserved one byte; + // if the prefix turns out to need more than this then + // we need to shuffle the existing data + len = (int)((writer.ioIndex - value) - 1); + int offset = 0; + uint tmp = (uint)len; + while ((tmp >>= 7) != 0) offset++; + if (offset == 0) + { + writer.ioBuffer[value] = (byte)(len & 0x7F); + } + else + { + DemandSpace(offset, writer); + byte[] blob = writer.ioBuffer; + Buffer.BlockCopy(blob, value + 1, blob, value + 1 + offset, len); + tmp = (uint)len; + do + { + blob[value++] = (byte)((tmp & 0x7F) | 0x80); + } while ((tmp >>= 7) != 0); + blob[value - 1] = (byte)(blob[value - 1] & ~0x80); + writer.position64 += offset; + writer.ioIndex += offset; + } + break; + default: + throw new ArgumentOutOfRangeException("style"); + } + // and this object is no longer a blockage - also flush if sensible + const int ADVISORY_FLUSH_SIZE = 1024; + if (--writer.flushLock == 0 && writer.ioIndex >= ADVISORY_FLUSH_SIZE) + { + ProtoWriter.Flush(writer); + } + + } + + /// + /// Creates a new writer against a stream + /// + /// The destination stream + /// The model to use for serialization; this can be null, but this will impair the ability to serialize sub-objects + /// Additional context about this serialization operation + public static ProtoWriter Create(Stream dest, TypeModel model, SerializationContext context = null) +#pragma warning disable CS0618 + => new ProtoWriter(dest, model, context); +#pragma warning restore CS0618 + + /// + /// Creates a new writer against a stream + /// + /// The destination stream + /// The model to use for serialization; this can be null, but this will impair the ability to serialize sub-objects + /// Additional context about this serialization operation + [Obsolete("Please use ProtoWriter.Create; this API may be removed in a future version", error: false)] + public ProtoWriter(Stream dest, TypeModel model, SerializationContext context) + { + if (dest == null) throw new ArgumentNullException("dest"); + if (!dest.CanWrite) throw new ArgumentException("Cannot write to stream", "dest"); + //if (model == null) throw new ArgumentNullException("model"); + this.dest = dest; + this.ioBuffer = BufferPool.GetBuffer(); + this.model = model; + this.wireType = WireType.None; + if (context == null) { context = SerializationContext.Default; } + else { context.Freeze(); } + this.context = context; + + } + + private readonly SerializationContext context; + /// + /// Addition information about this serialization operation. + /// + public SerializationContext Context => context; + + void IDisposable.Dispose() + { + Dispose(); + } + + private void Dispose() + { // importantly, this does **not** own the stream, and does not dispose it + if (dest != null) + { + Flush(this); + dest = null; + } + model = null; + BufferPool.ReleaseBufferToPool(ref ioBuffer); + } + + private byte[] ioBuffer; + private int ioIndex; + // note that this is used by some of the unit tests and should not be removed + internal static long GetLongPosition(ProtoWriter writer) { return writer.position64; } + internal static int GetPosition(ProtoWriter writer) { return checked((int)writer.position64); } + private long position64; + private static void DemandSpace(int required, ProtoWriter writer) + { + // check for enough space + if ((writer.ioBuffer.Length - writer.ioIndex) < required) + { + TryFlushOrResize(required, writer); + } + } + + private static void TryFlushOrResize(int required, ProtoWriter writer) + { + if (writer.flushLock == 0) + { + Flush(writer); // try emptying the buffer + if ((writer.ioBuffer.Length - writer.ioIndex) >= required) return; + } + + // either can't empty the buffer, or that didn't help; need more space + BufferPool.ResizeAndFlushLeft(ref writer.ioBuffer, required + writer.ioIndex, 0, writer.ioIndex); + } + + /// + /// Flushes data to the underlying stream, and releases any resources. The underlying stream is *not* disposed + /// by this operation. + /// + public void Close() + { + if (depth != 0 || flushLock != 0) throw new InvalidOperationException("Unable to close stream in an incomplete state"); + Dispose(); + } + + internal void CheckDepthFlushlock() + { + if (depth != 0 || flushLock != 0) throw new InvalidOperationException("The writer is in an incomplete state"); + } + + /// + /// Get the TypeModel associated with this writer + /// + public TypeModel Model => model; + + /// + /// Writes any buffered data (if possible) to the underlying stream. + /// + /// The writer to flush + /// It is not always possible to fully flush, since some sequences + /// may require values to be back-filled into the byte-stream. + internal static void Flush(ProtoWriter writer) + { + if (writer.flushLock == 0 && writer.ioIndex != 0) + { + writer.dest.Write(writer.ioBuffer, 0, writer.ioIndex); + writer.ioIndex = 0; + } + } + + /// + /// Writes an unsigned 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + private static void WriteUInt32Variant(uint value, ProtoWriter writer) + { + DemandSpace(5, writer); + int count = 0; + do + { + writer.ioBuffer[writer.ioIndex++] = (byte)((value & 0x7F) | 0x80); + count++; + } while ((value >>= 7) != 0); + writer.ioBuffer[writer.ioIndex - 1] &= 0x7F; + writer.position64 += count; + } + +#if COREFX + static readonly Encoding encoding = Encoding.UTF8; +#else + static readonly UTF8Encoding encoding = new UTF8Encoding(); +#endif + + internal static uint Zig(int value) + { + return (uint)((value << 1) ^ (value >> 31)); + } + + internal static ulong Zig(long value) + { + return (ulong)((value << 1) ^ (value >> 63)); + } + + private static void WriteUInt64Variant(ulong value, ProtoWriter writer) + { + DemandSpace(10, writer); + int count = 0; + do + { + writer.ioBuffer[writer.ioIndex++] = (byte)((value & 0x7F) | 0x80); + count++; + } while ((value >>= 7) != 0); + writer.ioBuffer[writer.ioIndex - 1] &= 0x7F; + writer.position64 += count; + } + + /// + /// Writes a string to the stream; supported wire-types: String + /// + public static void WriteString(string value, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (writer.wireType != WireType.String) throw CreateException(writer); + if (value == null) throw new ArgumentNullException("value"); // written header; now what? + int len = value.Length; + if (len == 0) + { + WriteUInt32Variant(0, writer); + writer.wireType = WireType.None; + return; // just a header + } + int predicted = encoding.GetByteCount(value); + WriteUInt32Variant((uint)predicted, writer); + DemandSpace(predicted, writer); + int actual = encoding.GetBytes(value, 0, value.Length, writer.ioBuffer, writer.ioIndex); + Helpers.DebugAssert(predicted == actual); + IncrementedAndReset(actual, writer); + } + + /// + /// Writes an unsigned 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public static void WriteUInt64(ulong value, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + switch (writer.wireType) + { + case WireType.Fixed64: + ProtoWriter.WriteInt64((long)value, writer); + return; + case WireType.Variant: + WriteUInt64Variant(value, writer); + writer.wireType = WireType.None; + return; + case WireType.Fixed32: + checked { ProtoWriter.WriteUInt32((uint)value, writer); } + return; + default: + throw CreateException(writer); + } + } + + /// + /// Writes a signed 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public static void WriteInt64(long value, ProtoWriter writer) + { + byte[] buffer; + int index; + if (writer == null) throw new ArgumentNullException(nameof(writer)); + switch (writer.wireType) + { + case WireType.Fixed64: + DemandSpace(8, writer); + buffer = writer.ioBuffer; + index = writer.ioIndex; + +#if NETCOREAPP2_1 + System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian(buffer.AsSpan(index, 8), value); +#else + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); + buffer[index + 4] = (byte)(value >> 32); + buffer[index + 5] = (byte)(value >> 40); + buffer[index + 6] = (byte)(value >> 48); + buffer[index + 7] = (byte)(value >> 56); +#endif + IncrementedAndReset(8, writer); + return; + case WireType.SignedVariant: + WriteUInt64Variant(Zig(value), writer); + writer.wireType = WireType.None; + return; + case WireType.Variant: + if (value >= 0) + { + WriteUInt64Variant((ulong)value, writer); + writer.wireType = WireType.None; + } + else + { + DemandSpace(10, writer); + buffer = writer.ioBuffer; + index = writer.ioIndex; + buffer[index] = (byte)(value | 0x80); + buffer[index + 1] = (byte)((int)(value >> 7) | 0x80); + buffer[index + 2] = (byte)((int)(value >> 14) | 0x80); + buffer[index + 3] = (byte)((int)(value >> 21) | 0x80); + buffer[index + 4] = (byte)((int)(value >> 28) | 0x80); + buffer[index + 5] = (byte)((int)(value >> 35) | 0x80); + buffer[index + 6] = (byte)((int)(value >> 42) | 0x80); + buffer[index + 7] = (byte)((int)(value >> 49) | 0x80); + buffer[index + 8] = (byte)((int)(value >> 56) | 0x80); + buffer[index + 9] = 0x01; // sign bit + IncrementedAndReset(10, writer); + } + return; + case WireType.Fixed32: + checked { WriteInt32((int)value, writer); } + return; + default: + throw CreateException(writer); + } + } + + /// + /// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public static void WriteUInt32(uint value, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + switch (writer.wireType) + { + case WireType.Fixed32: + ProtoWriter.WriteInt32((int)value, writer); + return; + case WireType.Fixed64: + ProtoWriter.WriteInt64((int)value, writer); + return; + case WireType.Variant: + WriteUInt32Variant(value, writer); + writer.wireType = WireType.None; + return; + default: + throw CreateException(writer); + } + } + + /// + /// Writes a signed 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public static void WriteInt16(short value, ProtoWriter writer) + { + ProtoWriter.WriteInt32(value, writer); + } + + /// + /// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public static void WriteUInt16(ushort value, ProtoWriter writer) + { + ProtoWriter.WriteUInt32(value, writer); + } + + /// + /// Writes an unsigned 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public static void WriteByte(byte value, ProtoWriter writer) + { + ProtoWriter.WriteUInt32(value, writer); + } + /// + /// Writes a signed 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public static void WriteSByte(sbyte value, ProtoWriter writer) + { + ProtoWriter.WriteInt32(value, writer); + } + + private static void WriteInt32ToBuffer(int value, byte[] buffer, int index) + { +#if NETCOREAPP2_1 + System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(index, 4), value); +#else + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); +#endif + } + + /// + /// Writes a signed 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant + /// + public static void WriteInt32(int value, ProtoWriter writer) + { + byte[] buffer; + int index; + if (writer == null) throw new ArgumentNullException(nameof(writer)); + switch (writer.wireType) + { + case WireType.Fixed32: + DemandSpace(4, writer); + WriteInt32ToBuffer(value, writer.ioBuffer, writer.ioIndex); + IncrementedAndReset(4, writer); + return; + case WireType.Fixed64: + DemandSpace(8, writer); + buffer = writer.ioBuffer; + index = writer.ioIndex; + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); + buffer[index + 4] = buffer[index + 5] = + buffer[index + 6] = buffer[index + 7] = 0; + IncrementedAndReset(8, writer); + return; + case WireType.SignedVariant: + WriteUInt32Variant(Zig(value), writer); + writer.wireType = WireType.None; + return; + case WireType.Variant: + if (value >= 0) + { + WriteUInt32Variant((uint)value, writer); + writer.wireType = WireType.None; + } + else + { + DemandSpace(10, writer); + buffer = writer.ioBuffer; + index = writer.ioIndex; + buffer[index] = (byte)(value | 0x80); + buffer[index + 1] = (byte)((value >> 7) | 0x80); + buffer[index + 2] = (byte)((value >> 14) | 0x80); + buffer[index + 3] = (byte)((value >> 21) | 0x80); + buffer[index + 4] = (byte)((value >> 28) | 0x80); + buffer[index + 5] = buffer[index + 6] = + buffer[index + 7] = buffer[index + 8] = (byte)0xFF; + buffer[index + 9] = (byte)0x01; + IncrementedAndReset(10, writer); + } + return; + default: + throw CreateException(writer); + } + } + + /// + /// Writes a double-precision number to the stream; supported wire-types: Fixed32, Fixed64 + /// + public +#if !FEAT_SAFE + unsafe +#endif + + static void WriteDouble(double value, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + switch (writer.wireType) + { + case WireType.Fixed32: + float f = (float)value; + if (float.IsInfinity(f) && !double.IsInfinity(value)) + { + throw new OverflowException(); + } + ProtoWriter.WriteSingle(f, writer); + return; + case WireType.Fixed64: +#if FEAT_SAFE + ProtoWriter.WriteInt64(BitConverter.ToInt64(BitConverter.GetBytes(value), 0), writer); +#else + ProtoWriter.WriteInt64(*(long*)&value, writer); +#endif + return; + default: + throw CreateException(writer); + } + } + /// + /// Writes a single-precision number to the stream; supported wire-types: Fixed32, Fixed64 + /// + public +#if !FEAT_SAFE + unsafe +#endif + static void WriteSingle(float value, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + switch (writer.wireType) + { + case WireType.Fixed32: +#if FEAT_SAFE + ProtoWriter.WriteInt32(BitConverter.ToInt32(BitConverter.GetBytes(value), 0), writer); +#else + ProtoWriter.WriteInt32(*(int*)&value, writer); +#endif + return; + case WireType.Fixed64: + ProtoWriter.WriteDouble((double)value, writer); + return; + default: + throw CreateException(writer); + } + } + + /// + /// Throws an exception indicating that the given enum cannot be mapped to a serialized value. + /// + public static void ThrowEnumException(ProtoWriter writer, object enumValue) + { + if (writer == null) throw new ArgumentNullException("writer"); + string rhs = enumValue == null ? "" : (enumValue.GetType().FullName + "." + enumValue.ToString()); + throw new ProtoException("No wire-value is mapped to the enum " + rhs + " at position " + writer.position64.ToString()); + } + + // general purpose serialization exception message + internal static Exception CreateException(ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + return new ProtoException("Invalid serialization operation with wire-type " + writer.wireType.ToString() + " at position " + writer.position64.ToString()); + } + + /// + /// Writes a boolean to the stream; supported wire-types: Variant, Fixed32, Fixed64 + /// + public static void WriteBoolean(bool value, ProtoWriter writer) + { + ProtoWriter.WriteUInt32(value ? (uint)1 : (uint)0, writer); + } + + /// + /// Copies any extension data stored for the instance to the underlying stream + /// + public static void AppendExtensionData(IExtensible instance, ProtoWriter writer) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + // we expect the writer to be raw here; the extension data will have the + // header detail, so we'll copy it implicitly + if (writer.wireType != WireType.None) throw CreateException(writer); + + IExtension extn = instance.GetExtensionObject(false); + if (extn != null) + { + // unusually we *don't* want "using" here; the "finally" does that, with + // the extension object being responsible for disposal etc + Stream source = extn.BeginQuery(); + try + { + CopyRawFromStream(source, writer); + } + finally { extn.EndQuery(source); } + } + } + + private int packedFieldNumber; + /// + /// Used for packed encoding; indicates that the next field should be skipped rather than + /// a field header written. Note that the field number must match, else an exception is thrown + /// when the attempt is made to write the (incorrect) field. The wire-type is taken from the + /// subsequent call to WriteFieldHeader. Only primitive types can be packed. + /// + public static void SetPackedField(int fieldNumber, ProtoWriter writer) + { + if (fieldNumber <= 0) throw new ArgumentOutOfRangeException(nameof(fieldNumber)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.packedFieldNumber = fieldNumber; + } + + /// + /// Used for packed encoding; explicitly reset the packed field marker; this is not required + /// if using StartSubItem/EndSubItem + /// + public static void ClearPackedField(int fieldNumber, ProtoWriter writer) + { + if (fieldNumber != writer.packedFieldNumber) + throw new InvalidOperationException("Field mismatch during packed encoding; expected " + writer.packedFieldNumber.ToString() + " but received " + fieldNumber.ToString()); + writer.packedFieldNumber = 0; + } + + /// + /// Used for packed encoding; writes the length prefix using fixed sizes rather than using + /// buffering. Only valid for fixed-32 and fixed-64 encoding. + /// + public static void WritePackedPrefix(int elementCount, WireType wireType, ProtoWriter writer) + { + if (writer.WireType != WireType.String) throw new InvalidOperationException("Invalid wire-type: " + writer.WireType); + if (elementCount < 0) throw new ArgumentOutOfRangeException(nameof(elementCount)); + ulong bytes; + switch (wireType) + { + // use long in case very large arrays are enabled + case WireType.Fixed32: bytes = ((ulong)elementCount) << 2; break; // x4 + case WireType.Fixed64: bytes = ((ulong)elementCount) << 3; break; // x8 + default: + throw new ArgumentOutOfRangeException(nameof(wireType), "Invalid wire-type: " + wireType); + } + WriteUInt64Variant(bytes, writer); + writer.wireType = WireType.None; + } + + internal string SerializeType(Type type) + { + return TypeModel.SerializeType(model, type); + } + + /// + /// Specifies a known root object to use during reference-tracked serialization + /// + public void SetRootObject(object value) + { + NetCache.SetKeyedObject(NetObjectCache.Root, value); + } + + /// + /// Writes a Type to the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String + /// + public static void WriteType(Type value, ProtoWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + WriteString(writer.SerializeType(value), writer); + } + } +} diff --git a/Runtime/Protobuf-net/ProtoWriter.cs.meta b/Runtime/Protobuf-net/ProtoWriter.cs.meta new file mode 100644 index 0000000..5b91b67 --- /dev/null +++ b/Runtime/Protobuf-net/ProtoWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 63b2636e44dc3824ca2dbc35316e96ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/SerializationContext.cs b/Runtime/Protobuf-net/SerializationContext.cs new file mode 100644 index 0000000..80b76af --- /dev/null +++ b/Runtime/Protobuf-net/SerializationContext.cs @@ -0,0 +1,76 @@ +using System; + +namespace ProtoBuf +{ + /// + /// Additional information about a serialization operation + /// + public sealed class SerializationContext + { + private bool frozen; + internal void Freeze() { frozen = true; } + private void ThrowIfFrozen() { if (frozen) throw new InvalidOperationException("The serialization-context cannot be changed once it is in use"); } + private object context; + /// + /// Gets or sets a user-defined object containing additional information about this serialization/deserialization operation. + /// + public object Context + { + get { return context; } + set { if (context != value) { ThrowIfFrozen(); context = value; } } + } + + private static readonly SerializationContext @default; + + static SerializationContext() + { + @default = new SerializationContext(); + @default.Freeze(); + } + /// + /// A default SerializationContext, with minimal information. + /// + internal static SerializationContext Default => @default; +#if PLAT_BINARYFORMATTER + +#if !(COREFX || PROFILE259) + private System.Runtime.Serialization.StreamingContextStates state = System.Runtime.Serialization.StreamingContextStates.Persistence; + /// + /// Gets or sets the source or destination of the transmitted data. + /// + public System.Runtime.Serialization.StreamingContextStates State + { + get { return state; } + set { if (state != value) { ThrowIfFrozen(); state = value; } } + } +#endif + /// + /// Convert a SerializationContext to a StreamingContext + /// + public static implicit operator System.Runtime.Serialization.StreamingContext(SerializationContext ctx) + { +#if COREFX + return new System.Runtime.Serialization.StreamingContext(); +#else + if (ctx == null) return new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Persistence); + return new System.Runtime.Serialization.StreamingContext(ctx.state, ctx.context); +#endif + } + /// + /// Convert a StreamingContext to a SerializationContext + /// + public static implicit operator SerializationContext (System.Runtime.Serialization.StreamingContext ctx) + { + SerializationContext result = new SerializationContext(); + +#if !(COREFX || PROFILE259) + result.Context = ctx.Context; + result.State = ctx.State; +#endif + + return result; + } +#endif + } + +} diff --git a/Runtime/Protobuf-net/SerializationContext.cs.meta b/Runtime/Protobuf-net/SerializationContext.cs.meta new file mode 100644 index 0000000..9bd8dcc --- /dev/null +++ b/Runtime/Protobuf-net/SerializationContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9361aaa524d95b14fbf398ba5bc075a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializer.cs b/Runtime/Protobuf-net/Serializer.cs new file mode 100644 index 0000000..8a4c38a --- /dev/null +++ b/Runtime/Protobuf-net/Serializer.cs @@ -0,0 +1,514 @@ +using ProtoBuf.Meta; +using System; +using System.IO; +using System.Collections.Generic; +using System.Reflection; + +namespace ProtoBuf +{ + /// + /// Provides protocol-buffer serialization capability for concrete, attributed types. This + /// is a *default* model, but custom serializer models are also supported. + /// + /// + /// Protocol-buffer serialization is a compact binary format, designed to take + /// advantage of sparse data and knowledge of specific data types; it is also + /// extensible, allowing a type to be deserialized / merged even if some data is + /// not recognised. + /// + public static class Serializer + { +#if !NO_RUNTIME + /// + /// Suggest a .proto definition for the given type + /// + /// The type to generate a .proto definition for + /// The .proto definition as a string + public static string GetProto() => GetProto(ProtoSyntax.Proto2); + + /// + /// Suggest a .proto definition for the given type + /// + /// The type to generate a .proto definition for + /// The .proto definition as a string + public static string GetProto(ProtoSyntax syntax) + { + return RuntimeTypeModel.Default.GetSchema(RuntimeTypeModel.Default.MapType(typeof(T)), syntax); + } + /// + /// Create a deep clone of the supplied instance; any sub-items are also cloned. + /// + public static T DeepClone(T instance) + { + return instance == null ? instance : (T)RuntimeTypeModel.Default.DeepClone(instance); + } + + /// + /// Applies a protocol-buffer stream to an existing instance. + /// + /// The type being merged. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public static T Merge(Stream source, T instance) + { + return (T)RuntimeTypeModel.Default.Deserialize(source, instance, typeof(T)); + } + + /// + /// Creates a new instance from a protocol-buffer stream + /// + /// The type to be created. + /// The binary stream to apply to the new instance (cannot be null). + /// A new, initialized instance. + public static T Deserialize(Stream source) + { + return (T)RuntimeTypeModel.Default.Deserialize(source, null, typeof(T)); + } + + /// + /// Creates a new instance from a protocol-buffer stream + /// + /// The type to be created. + /// The binary stream to apply to the new instance (cannot be null). + /// A new, initialized instance. + public static object Deserialize(Type type, Stream source) + { + return RuntimeTypeModel.Default.Deserialize(source, null, type); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream. + /// + /// The existing instance to be serialized (cannot be null). + /// The destination stream to write to. + public static void Serialize(Stream destination, T instance) + { + if (instance != null) + { + RuntimeTypeModel.Default.Serialize(destination, instance); + } + } + + /// + /// Serializes a given instance and deserializes it as a different type; + /// this can be used to translate between wire-compatible objects (where + /// two .NET types represent the same data), or to promote/demote a type + /// through an inheritance hierarchy. + /// + /// No assumption of compatibility is made between the types. + /// The type of the object being copied. + /// The type of the new object to be created. + /// The existing instance to use as a template. + /// A new instane of type TNewType, with the data from TOldType. + public static TTo ChangeType(TFrom instance) + { + using (var ms = new MemoryStream()) + { + Serialize(ms, instance); + ms.Position = 0; + return Deserialize(ms); + } + } +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + /// + /// Writes a protocol-buffer representation of the given instance to the supplied SerializationInfo. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// The destination SerializationInfo to write to. + public static void Serialize(System.Runtime.Serialization.SerializationInfo info, T instance) where T : class, System.Runtime.Serialization.ISerializable + { + Serialize(info, new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Persistence), instance); + } + /// + /// Writes a protocol-buffer representation of the given instance to the supplied SerializationInfo. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// The destination SerializationInfo to write to. + /// Additional information about this serialization operation. + public static void Serialize(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context, T instance) where T : class, System.Runtime.Serialization.ISerializable + { + // note: also tried byte[]... it doesn't perform hugely well with either (compared to regular serialization) + if (info == null) throw new ArgumentNullException("info"); + if (instance == null) throw new ArgumentNullException("instance"); + if (instance.GetType() != typeof(T)) throw new ArgumentException("Incorrect type", "instance"); + using (MemoryStream ms = new MemoryStream()) + { + RuntimeTypeModel.Default.Serialize(ms, instance, context); + info.AddValue(ProtoBinaryField, ms.ToArray()); + } + } +#endif +#if PLAT_XMLSERIALIZER + /// + /// Writes a protocol-buffer representation of the given instance to the supplied XmlWriter. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// The destination XmlWriter to write to. + public static void Serialize(System.Xml.XmlWriter writer, T instance) where T : System.Xml.Serialization.IXmlSerializable + { + if (writer == null) throw new ArgumentNullException("writer"); + if (instance == null) throw new ArgumentNullException("instance"); + + using (MemoryStream ms = new MemoryStream()) + { + Serializer.Serialize(ms, instance); + writer.WriteBase64(Helpers.GetBuffer(ms), 0, (int)ms.Length); + } + } + /// + /// Applies a protocol-buffer from an XmlReader to an existing instance. + /// + /// The type being merged. + /// The existing instance to be modified (cannot be null). + /// The XmlReader containing the data to apply to the instance (cannot be null). + public static void Merge(System.Xml.XmlReader reader, T instance) where T : System.Xml.Serialization.IXmlSerializable + { + if (reader == null) throw new ArgumentNullException("reader"); + if (instance == null) throw new ArgumentNullException("instance"); + + const int LEN = 4096; + byte[] buffer = new byte[LEN]; + int read; + using (MemoryStream ms = new MemoryStream()) + { + int depth = reader.Depth; + while(reader.Read() && reader.Depth > depth) + { + if (reader.NodeType == System.Xml.XmlNodeType.Text) + { + while ((read = reader.ReadContentAsBase64(buffer, 0, LEN)) > 0) + { + ms.Write(buffer, 0, read); + } + if (reader.Depth <= depth) break; + } + } + ms.Position = 0; + Serializer.Merge(ms, instance); + } + } +#endif + + private const string ProtoBinaryField = "proto"; +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + /// + /// Applies a protocol-buffer from a SerializationInfo to an existing instance. + /// + /// The type being merged. + /// The existing instance to be modified (cannot be null). + /// The SerializationInfo containing the data to apply to the instance (cannot be null). + public static void Merge(System.Runtime.Serialization.SerializationInfo info, T instance) where T : class, System.Runtime.Serialization.ISerializable + { + Merge(info, new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Persistence), instance); + } + /// + /// Applies a protocol-buffer from a SerializationInfo to an existing instance. + /// + /// The type being merged. + /// The existing instance to be modified (cannot be null). + /// The SerializationInfo containing the data to apply to the instance (cannot be null). + /// Additional information about this serialization operation. + public static void Merge(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context, T instance) where T : class, System.Runtime.Serialization.ISerializable + { + // note: also tried byte[]... it doesn't perform hugely well with either (compared to regular serialization) + if (info == null) throw new ArgumentNullException("info"); + if (instance == null) throw new ArgumentNullException("instance"); + if (instance.GetType() != typeof(T)) throw new ArgumentException("Incorrect type", "instance"); + + byte[] buffer = (byte[])info.GetValue(ProtoBinaryField, typeof(byte[])); + using (MemoryStream ms = new MemoryStream(buffer)) + { + T result = (T)RuntimeTypeModel.Default.Deserialize(ms, instance, typeof(T), context); + if (!ReferenceEquals(result, instance)) + { + throw new ProtoException("Deserialization changed the instance; cannot succeed."); + } + } + } +#endif + + /// + /// Precompiles the serializer for a given type. + /// + public static void PrepareSerializer() + { + NonGeneric.PrepareSerializer(typeof(T)); + } + +#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259) + /// + /// Creates a new IFormatter that uses protocol-buffer [de]serialization. + /// + /// The type of object to be [de]deserialized by the formatter. + /// A new IFormatter to be used during [de]serialization. + public static System.Runtime.Serialization.IFormatter CreateFormatter() + { + return RuntimeTypeModel.Default.CreateFormatter(typeof(T)); + } +#endif + /// + /// Reads a sequence of consecutive length-prefixed items from a stream, using + /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag + /// are directly comparable to serializing multiple items in succession + /// (use the tag to emulate the implicit behavior + /// when serializing a list/array). When a tag is + /// specified, any records with different tags are silently omitted. The + /// tag is ignored. The tag is ignored for fixed-length prefixes. + /// + /// The type of object to deserialize. + /// The binary stream containing the serialized records. + /// The prefix style used in the data. + /// The tag of records to return (if non-positive, then no tag is + /// expected and all records are returned). + /// The sequence of deserialized objects. + public static IEnumerable DeserializeItems(Stream source, PrefixStyle style, int fieldNumber) + { + return RuntimeTypeModel.Default.DeserializeItems(source, style, fieldNumber); + } + + /// + /// Creates a new instance from a protocol-buffer stream that has a length-prefix + /// on data (to assist with network IO). + /// + /// The type to be created. + /// The binary stream to apply to the new instance (cannot be null). + /// How to encode the length prefix. + /// A new, initialized instance. + public static T DeserializeWithLengthPrefix(Stream source, PrefixStyle style) + { + return DeserializeWithLengthPrefix(source, style, 0); + } + + /// + /// Creates a new instance from a protocol-buffer stream that has a length-prefix + /// on data (to assist with network IO). + /// + /// The type to be created. + /// The binary stream to apply to the new instance (cannot be null). + /// How to encode the length prefix. + /// The expected tag of the item (only used with base-128 prefix style). + /// A new, initialized instance. + public static T DeserializeWithLengthPrefix(Stream source, PrefixStyle style, int fieldNumber) + { + RuntimeTypeModel model = RuntimeTypeModel.Default; + return (T)model.DeserializeWithLengthPrefix(source, null, model.MapType(typeof(T)), style, fieldNumber); + } + + /// + /// Applies a protocol-buffer stream to an existing instance, using length-prefixed + /// data - useful with network IO. + /// + /// The type being merged. + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// How to encode the length prefix. + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public static T MergeWithLengthPrefix(Stream source, T instance, PrefixStyle style) + { + RuntimeTypeModel model = RuntimeTypeModel.Default; + return (T)model.DeserializeWithLengthPrefix(source, instance, model.MapType(typeof(T)), style, 0); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream, + /// with a length-prefix. This is useful for socket programming, + /// as DeserializeWithLengthPrefix/MergeWithLengthPrefix can be used to read the single object back + /// from an ongoing stream. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// How to encode the length prefix. + /// The destination stream to write to. + public static void SerializeWithLengthPrefix(Stream destination, T instance, PrefixStyle style) + { + SerializeWithLengthPrefix(destination, instance, style, 0); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream, + /// with a length-prefix. This is useful for socket programming, + /// as DeserializeWithLengthPrefix/MergeWithLengthPrefix can be used to read the single object back + /// from an ongoing stream. + /// + /// The type being serialized. + /// The existing instance to be serialized (cannot be null). + /// How to encode the length prefix. + /// The destination stream to write to. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + public static void SerializeWithLengthPrefix(Stream destination, T instance, PrefixStyle style, int fieldNumber) + { + RuntimeTypeModel model = RuntimeTypeModel.Default; + model.SerializeWithLengthPrefix(destination, instance, model.MapType(typeof(T)), style, fieldNumber); + } + + /// Indicates the number of bytes expected for the next message. + /// The stream containing the data to investigate for a length. + /// The algorithm used to encode the length. + /// The length of the message, if it could be identified. + /// True if a length could be obtained, false otherwise. + public static bool TryReadLengthPrefix(Stream source, PrefixStyle style, out int length) + { + length = ProtoReader.ReadLengthPrefix(source, false, style, out int fieldNumber, out int bytesRead); + return bytesRead > 0; + } + + /// Indicates the number of bytes expected for the next message. + /// The buffer containing the data to investigate for a length. + /// The offset of the first byte to read from the buffer. + /// The number of bytes to read from the buffer. + /// The algorithm used to encode the length. + /// The length of the message, if it could be identified. + /// True if a length could be obtained, false otherwise. + public static bool TryReadLengthPrefix(byte[] buffer, int index, int count, PrefixStyle style, out int length) + { + using (Stream source = new MemoryStream(buffer, index, count)) + { + return TryReadLengthPrefix(source, style, out length); + } + } +#endif + /// + /// The field number that is used as a default when serializing/deserializing a list of objects. + /// The data is treated as repeated message with field number 1. + /// + public const int ListItemTag = 1; + + +#if !NO_RUNTIME + /// + /// Provides non-generic access to the default serializer. + /// + public static class NonGeneric + { + /// + /// Create a deep clone of the supplied instance; any sub-items are also cloned. + /// + public static object DeepClone(object instance) + { + return instance == null ? null : RuntimeTypeModel.Default.DeepClone(instance); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream. + /// + /// The existing instance to be serialized (cannot be null). + /// The destination stream to write to. + public static void Serialize(Stream dest, object instance) + { + if (instance != null) + { + RuntimeTypeModel.Default.Serialize(dest, instance); + } + } + + /// + /// Creates a new instance from a protocol-buffer stream + /// + /// The type to be created. + /// The binary stream to apply to the new instance (cannot be null). + /// A new, initialized instance. + public static object Deserialize(Type type, Stream source) + { + return RuntimeTypeModel.Default.Deserialize(source, null, type); + } + + /// Applies a protocol-buffer stream to an existing instance. + /// The existing instance to be modified (cannot be null). + /// The binary stream to apply to the instance (cannot be null). + /// The updated instance + public static object Merge(Stream source, object instance) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + return RuntimeTypeModel.Default.Deserialize(source, instance, instance.GetType(), null); + } + + /// + /// Writes a protocol-buffer representation of the given instance to the supplied stream, + /// with a length-prefix. This is useful for socket programming, + /// as DeserializeWithLengthPrefix/MergeWithLengthPrefix can be used to read the single object back + /// from an ongoing stream. + /// + /// The existing instance to be serialized (cannot be null). + /// How to encode the length prefix. + /// The destination stream to write to. + /// The tag used as a prefix to each record (only used with base-128 style prefixes). + public static void SerializeWithLengthPrefix(Stream destination, object instance, PrefixStyle style, int fieldNumber) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + RuntimeTypeModel model = RuntimeTypeModel.Default; + model.SerializeWithLengthPrefix(destination, instance, model.MapType(instance.GetType()), style, fieldNumber); + } + /// + /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed + /// data - useful with network IO. + /// + /// The existing instance to be modified (can be null). + /// The binary stream to apply to the instance (cannot be null). + /// How to encode the length prefix. + /// Used to resolve types on a per-field basis. + /// The updated instance; this may be different to the instance argument if + /// either the original instance was null, or the stream defines a known sub-type of the + /// original instance. + public static bool TryDeserializeWithLengthPrefix(Stream source, PrefixStyle style, TypeResolver resolver, out object value) + { + value = RuntimeTypeModel.Default.DeserializeWithLengthPrefix(source, null, null, style, 0, resolver); + return value != null; + } + + /// + /// Indicates whether the supplied type is explicitly modelled by the model + /// + public static bool CanSerialize(Type type) => RuntimeTypeModel.Default.IsDefined(type); + + /// + /// Precompiles the serializer for a given type. + /// + public static void PrepareSerializer(Type t) + { +#if FEAT_COMPILER + RuntimeTypeModel model = RuntimeTypeModel.Default; + model[model.MapType(t)].CompileInPlace(); +#endif + } + } + + /// + /// Global switches that change the behavior of protobuf-net + /// + public static class GlobalOptions + { + /// + /// + /// + [Obsolete("Please use RuntimeTypeModel.Default.InferTagFromNameDefault instead (or on a per-model basis)", false)] + public static bool InferTagFromName + { + get { return RuntimeTypeModel.Default.InferTagFromNameDefault; } + set { RuntimeTypeModel.Default.InferTagFromNameDefault = value; } + } + } +#endif + /// + /// Maps a field-number to a type + /// + public delegate Type TypeResolver(int fieldNumber); + + /// + /// Releases any internal buffers that have been reserved for efficiency; this does not affect any serialization + /// operations; simply: it can be used (optionally) to release the buffers for garbage collection (at the expense + /// of having to re-allocate a new buffer for the next operation, rather than re-use prior buffers). + /// + public static void FlushPool() + { + BufferPool.Flush(); + } + } +} diff --git a/Runtime/Protobuf-net/Serializer.cs.meta b/Runtime/Protobuf-net/Serializer.cs.meta new file mode 100644 index 0000000..63cf57d --- /dev/null +++ b/Runtime/Protobuf-net/Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dbd7fc6a1f1a0e34b8a1bce7e93c4f61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers.meta b/Runtime/Protobuf-net/Serializers.meta new file mode 100644 index 0000000..569acba --- /dev/null +++ b/Runtime/Protobuf-net/Serializers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90bd17a736284764ca22da41661472de +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ArrayDecorator.cs b/Runtime/Protobuf-net/Serializers/ArrayDecorator.cs new file mode 100644 index 0000000..cad005f --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ArrayDecorator.cs @@ -0,0 +1,310 @@ +#if !NO_RUNTIME +using System; +using System.Collections; +using System.Reflection; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class ArrayDecorator : ProtoDecoratorBase + { + private readonly int fieldNumber; + private const byte + OPTIONS_WritePacked = 1, + OPTIONS_OverwriteList = 2, + OPTIONS_SupportNull = 4; + private readonly byte options; + private readonly WireType packedWireType; + public ArrayDecorator(TypeModel model, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, Type arrayType, bool overwriteList, bool supportNull) + : base(tail) + { + Helpers.DebugAssert(arrayType != null, "arrayType should be non-null"); + Helpers.DebugAssert(arrayType.IsArray && arrayType.GetArrayRank() == 1, "should be single-dimension array; " + arrayType.FullName); + this.itemType = arrayType.GetElementType(); + Type underlyingItemType = supportNull ? itemType : (Helpers.GetUnderlyingType(itemType) ?? itemType); + + Helpers.DebugAssert(underlyingItemType == Tail.ExpectedType + || (Tail.ExpectedType == model.MapType(typeof(object)) && !Helpers.IsValueType(underlyingItemType)), "invalid tail"); + Helpers.DebugAssert(Tail.ExpectedType != model.MapType(typeof(byte)), "Should have used BlobSerializer"); + if ((writePacked || packedWireType != WireType.None) && fieldNumber <= 0) throw new ArgumentOutOfRangeException("fieldNumber"); + if (!ListDecorator.CanPack(packedWireType)) + { + if (writePacked) throw new InvalidOperationException("Only simple data-types can use packed encoding"); + packedWireType = WireType.None; + } + this.fieldNumber = fieldNumber; + this.packedWireType = packedWireType; + if (writePacked) options |= OPTIONS_WritePacked; + if (overwriteList) options |= OPTIONS_OverwriteList; + if (supportNull) options |= OPTIONS_SupportNull; + this.arrayType = arrayType; + } + readonly Type arrayType, itemType; // this is, for example, typeof(int[]) + public override Type ExpectedType { get { return arrayType; } } + public override bool RequiresOldValue { get { return AppendToCollection; } } + public override bool ReturnsValue { get { return true; } } + private bool CanUsePackedPrefix() => CanUsePackedPrefix(packedWireType, itemType); + + internal static bool CanUsePackedPrefix(WireType packedWireType, Type itemType) + { + // needs to be a suitably simple type *and* be definitely not nullable + switch (packedWireType) + { + case WireType.Fixed32: + case WireType.Fixed64: + break; + default: + return false; // nope + } + if (!Helpers.IsValueType(itemType)) return false; + return Helpers.GetUnderlyingType(itemType) == null; + } + +#if FEAT_COMPILER + protected override void EmitWrite(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom) + { + // int i and T[] arr + using (Compiler.Local arr = ctx.GetLocalWithValue(arrayType, valueFrom)) + using (Compiler.Local i = new ProtoBuf.Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + bool writePacked = (options & OPTIONS_WritePacked) != 0; + bool fixedLengthPacked = writePacked && CanUsePackedPrefix(); + + using (Compiler.Local token = (writePacked && !fixedLengthPacked) ? new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))) : null) + { + Type mappedWriter = ctx.MapType(typeof(ProtoWriter)); + if (writePacked) + { + ctx.LoadValue(fieldNumber); + ctx.LoadValue((int)WireType.String); + ctx.LoadReaderWriter(); + ctx.EmitCall(mappedWriter.GetMethod("WriteFieldHeader")); + + if (fixedLengthPacked) + { + // write directly - no need for buffering + ctx.LoadLength(arr, false); + ctx.LoadValue((int)packedWireType); + ctx.LoadReaderWriter(); + ctx.EmitCall(mappedWriter.GetMethod("WritePackedPrefix")); + } + else + { + ctx.LoadValue(arr); + ctx.LoadReaderWriter(); + ctx.EmitCall(mappedWriter.GetMethod("StartSubItem")); + ctx.StoreValue(token); + } + ctx.LoadValue(fieldNumber); + ctx.LoadReaderWriter(); + ctx.EmitCall(mappedWriter.GetMethod("SetPackedField")); + } + EmitWriteArrayLoop(ctx, i, arr); + + if (writePacked) + { + if (fixedLengthPacked) + { + ctx.LoadValue(fieldNumber); + ctx.LoadReaderWriter(); + ctx.EmitCall(mappedWriter.GetMethod("ClearPackedField")); + } + else + { + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(mappedWriter.GetMethod("EndSubItem")); + } + } + } + } + } + + private void EmitWriteArrayLoop(Compiler.CompilerContext ctx, Compiler.Local i, Compiler.Local arr) + { + // i = 0 + ctx.LoadValue(0); + ctx.StoreValue(i); + + // range test is last (to minimise branches) + Compiler.CodeLabel loopTest = ctx.DefineLabel(), processItem = ctx.DefineLabel(); + ctx.Branch(loopTest, false); + ctx.MarkLabel(processItem); + + // {...} + ctx.LoadArrayValue(arr, i); + if (SupportNull) + { + Tail.EmitWrite(ctx, null); + } + else + { + ctx.WriteNullCheckedTail(itemType, Tail, null); + } + + // i++ + ctx.LoadValue(i); + ctx.LoadValue(1); + ctx.Add(); + ctx.StoreValue(i); + + // i < arr.Length + ctx.MarkLabel(loopTest); + ctx.LoadValue(i); + ctx.LoadLength(arr, false); + ctx.BranchIfLess(processItem, false); + } +#endif + private bool AppendToCollection => (options & OPTIONS_OverwriteList) == 0; + + private bool SupportNull { get { return (options & OPTIONS_SupportNull) != 0; } } + + public override void Write(object value, ProtoWriter dest) + { + IList arr = (IList)value; + int len = arr.Count; + SubItemToken token; + bool writePacked = (options & OPTIONS_WritePacked) != 0; + bool fixedLengthPacked = writePacked && CanUsePackedPrefix(); + + if (writePacked) + { + ProtoWriter.WriteFieldHeader(fieldNumber, WireType.String, dest); + + if (fixedLengthPacked) + { + ProtoWriter.WritePackedPrefix(arr.Count, packedWireType, dest); + token = new SubItemToken(); // default + } + else + { + token = ProtoWriter.StartSubItem(value, dest); + } + ProtoWriter.SetPackedField(fieldNumber, dest); + } + else + { + token = new SubItemToken(); // default + } + bool checkForNull = !SupportNull; + for (int i = 0; i < len; i++) + { + object obj = arr[i]; + if (checkForNull && obj == null) { throw new NullReferenceException(); } + Tail.Write(obj, dest); + } + if (writePacked) + { + if (fixedLengthPacked) + { + ProtoWriter.ClearPackedField(fieldNumber, dest); + } + else + { + ProtoWriter.EndSubItem(token, dest); + } + } + } + public override object Read(object value, ProtoReader source) + { + int field = source.FieldNumber; + BasicList list = new BasicList(); + if (packedWireType != WireType.None && source.WireType == WireType.String) + { + SubItemToken token = ProtoReader.StartSubItem(source); + while (ProtoReader.HasSubValue(packedWireType, source)) + { + list.Add(Tail.Read(null, source)); + } + ProtoReader.EndSubItem(token, source); + } + else + { + do + { + list.Add(Tail.Read(null, source)); + } while (source.TryReadFieldHeader(field)); + } + int oldLen = AppendToCollection ? ((value == null ? 0 : ((Array)value).Length)) : 0; + Array result = Array.CreateInstance(itemType, oldLen + list.Count); + if (oldLen != 0) ((Array)value).CopyTo(result, 0); + list.CopyTo(result, oldLen); + return result; + } + +#if FEAT_COMPILER + protected override void EmitRead(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom) + { + Type listType; + listType = ctx.MapType(typeof(System.Collections.Generic.List<>)).MakeGenericType(itemType); + Type expected = ExpectedType; + using (Compiler.Local oldArr = AppendToCollection ? ctx.GetLocalWithValue(expected, valueFrom) : null) + using (Compiler.Local newArr = new Compiler.Local(ctx, expected)) + using (Compiler.Local list = new Compiler.Local(ctx, listType)) + { + ctx.EmitCtor(listType); + ctx.StoreValue(list); + ListDecorator.EmitReadList(ctx, list, Tail, listType.GetMethod("Add"), packedWireType, false); + + // leave this "using" here, as it can share the "FieldNumber" local with EmitReadList + using (Compiler.Local oldLen = AppendToCollection ? new ProtoBuf.Compiler.Local(ctx, ctx.MapType(typeof(int))) : null) + { + Type[] copyToArrayInt32Args = new Type[] { ctx.MapType(typeof(Array)), ctx.MapType(typeof(int)) }; + + if (AppendToCollection) + { + ctx.LoadLength(oldArr, true); + ctx.CopyValue(); + ctx.StoreValue(oldLen); + + ctx.LoadAddress(list, listType); + ctx.LoadValue(listType.GetProperty("Count")); + ctx.Add(); + ctx.CreateArray(itemType, null); // length is on the stack + ctx.StoreValue(newArr); + + ctx.LoadValue(oldLen); + Compiler.CodeLabel nothingToCopy = ctx.DefineLabel(); + ctx.BranchIfFalse(nothingToCopy, true); + ctx.LoadValue(oldArr); + ctx.LoadValue(newArr); + ctx.LoadValue(0); // index in target + + ctx.EmitCall(expected.GetMethod("CopyTo", copyToArrayInt32Args)); + ctx.MarkLabel(nothingToCopy); + + ctx.LoadValue(list); + ctx.LoadValue(newArr); + ctx.LoadValue(oldLen); + + } + else + { + ctx.LoadAddress(list, listType); + ctx.LoadValue(listType.GetProperty("Count")); + ctx.CreateArray(itemType, null); + ctx.StoreValue(newArr); + + ctx.LoadAddress(list, listType); + ctx.LoadValue(newArr); + ctx.LoadValue(0); + } + + copyToArrayInt32Args[0] = expected; // // prefer: CopyTo(T[], int) + MethodInfo copyTo = listType.GetMethod("CopyTo", copyToArrayInt32Args); + if (copyTo == null) + { // fallback: CopyTo(Array, int) + copyToArrayInt32Args[1] = ctx.MapType(typeof(Array)); + copyTo = listType.GetMethod("CopyTo", copyToArrayInt32Args); + } + ctx.EmitCall(copyTo); + } + ctx.LoadValue(newArr); + } + + + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ArrayDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/ArrayDecorator.cs.meta new file mode 100644 index 0000000..6958590 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ArrayDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3689dde3ac5fd544a9e66158c9713872 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/BlobSerializer.cs b/Runtime/Protobuf-net/Serializers/BlobSerializer.cs new file mode 100644 index 0000000..40b2b89 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/BlobSerializer.cs @@ -0,0 +1,59 @@ +#if !NO_RUNTIME +using System; +#if COREFX +using System.Reflection; +#endif +#if FEAT_COMPILER +using System.Reflection.Emit; +#endif + +namespace ProtoBuf.Serializers +{ + sealed class BlobSerializer : IProtoSerializer + { + public Type ExpectedType { get { return expectedType; } } + + static readonly Type expectedType = typeof(byte[]); + + public BlobSerializer(ProtoBuf.Meta.TypeModel model, bool overwriteList) + { + this.overwriteList = overwriteList; + } + + private readonly bool overwriteList; + + public object Read(object value, ProtoReader source) + { + return ProtoReader.AppendBytes(overwriteList ? null : (byte[])value, source); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteBytes((byte[])value, dest); + } + + bool IProtoSerializer.RequiresOldValue { get { return !overwriteList; } } + bool IProtoSerializer.ReturnsValue { get { return true; } } +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteBytes", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + if (overwriteList) + { + ctx.LoadNullRef(); + } + else + { + ctx.LoadValue(valueFrom); + } + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)) + .GetMethod("AppendBytes")); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/BlobSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/BlobSerializer.cs.meta new file mode 100644 index 0000000..49dd403 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/BlobSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c04427a4647d6314e82d8a63882dcb8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/BooleanSerializer.cs b/Runtime/Protobuf-net/Serializers/BooleanSerializer.cs new file mode 100644 index 0000000..c64886a --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/BooleanSerializer.cs @@ -0,0 +1,41 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class BooleanSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(bool); + + public BooleanSerializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType => expectedType; + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteBoolean((bool)value, dest); + } + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadBoolean(); + } + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteBoolean", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadBoolean", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/BooleanSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/BooleanSerializer.cs.meta new file mode 100644 index 0000000..f982384 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/BooleanSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b73f749f97802947812dc66867ed1f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ByteSerializer.cs b/Runtime/Protobuf-net/Serializers/ByteSerializer.cs new file mode 100644 index 0000000..e44a83c --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ByteSerializer.cs @@ -0,0 +1,42 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class ByteSerializer : IProtoSerializer + { + public Type ExpectedType { get { return expectedType; } } + + static readonly Type expectedType = typeof(byte); + + public ByteSerializer(ProtoBuf.Meta.TypeModel model) { } + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteByte((byte)value, dest); + } + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadByte(); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteByte", valueFrom); + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadByte", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ByteSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/ByteSerializer.cs.meta new file mode 100644 index 0000000..23a58b1 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ByteSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c17779da4eb6b1d489531294afcb2a32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/CharSerializer.cs b/Runtime/Protobuf-net/Serializers/CharSerializer.cs new file mode 100644 index 0000000..3bc30d0 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/CharSerializer.cs @@ -0,0 +1,32 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class CharSerializer : UInt16Serializer + { + static readonly Type expectedType = typeof(char); + + public CharSerializer(ProtoBuf.Meta.TypeModel model) : base(model) + { + + } + + public override Type ExpectedType => expectedType; + + public override void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteUInt16((ushort)(char)value, dest); + } + + public override object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return (char)source.ReadUInt16(); + } + + // no need for any special IL here; ushort and char are + // interchangeable as long as there is no boxing/unboxing + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/CharSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/CharSerializer.cs.meta new file mode 100644 index 0000000..0424efc --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/CharSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 526090cb730f087469b7f20948f4932a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/CompiledSerializer.cs b/Runtime/Protobuf-net/Serializers/CompiledSerializer.cs new file mode 100644 index 0000000..1ec3027 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/CompiledSerializer.cs @@ -0,0 +1,88 @@ +#if FEAT_COMPILER +using System; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class CompiledSerializer : IProtoTypeSerializer + { + bool IProtoTypeSerializer.HasCallbacks(TypeModel.CallbackType callbackType) + { + return head.HasCallbacks(callbackType); // these routes only used when bits of the model not compiled + } + + bool IProtoTypeSerializer.CanCreateInstance() + { + return head.CanCreateInstance(); + } + + object IProtoTypeSerializer.CreateInstance(ProtoReader source) + { + return head.CreateInstance(source); + } + + public void Callback(object value, TypeModel.CallbackType callbackType, SerializationContext context) + { + head.Callback(value, callbackType, context); // these routes only used when bits of the model not compiled + } + + public static CompiledSerializer Wrap(IProtoTypeSerializer head, TypeModel model) + { + CompiledSerializer result = head as CompiledSerializer; + if (result == null) + { + result = new CompiledSerializer(head, model); + Helpers.DebugAssert(((IProtoTypeSerializer)result).ExpectedType == head.ExpectedType); + } + return result; + } + + private readonly IProtoTypeSerializer head; + private readonly Compiler.ProtoSerializer serializer; + private readonly Compiler.ProtoDeserializer deserializer; + + private CompiledSerializer(IProtoTypeSerializer head, TypeModel model) + { + this.head = head; + serializer = Compiler.CompilerContext.BuildSerializer(head, model); + deserializer = Compiler.CompilerContext.BuildDeserializer(head, model); + } + + bool IProtoSerializer.RequiresOldValue => head.RequiresOldValue; + + bool IProtoSerializer.ReturnsValue => head.ReturnsValue; + + Type IProtoSerializer.ExpectedType => head.ExpectedType; + + void IProtoSerializer.Write(object value, ProtoWriter dest) + { + serializer(value, dest); + } + + object IProtoSerializer.Read(object value, ProtoReader source) + { + return deserializer(value, source); + } + + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + head.EmitWrite(ctx, valueFrom); + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + head.EmitRead(ctx, valueFrom); + } + + void IProtoTypeSerializer.EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, TypeModel.CallbackType callbackType) + { + head.EmitCallback(ctx, valueFrom, callbackType); + } + + void IProtoTypeSerializer.EmitCreateInstance(Compiler.CompilerContext ctx) + { + head.EmitCreateInstance(ctx); + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/CompiledSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/CompiledSerializer.cs.meta new file mode 100644 index 0000000..ddef875 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/CompiledSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90821da5568834a4682d1a42d7f66963 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs b/Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs new file mode 100644 index 0000000..9755df9 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs @@ -0,0 +1,65 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + internal sealed class DateTimeSerializer : IProtoSerializer + { + private static readonly Type expectedType = typeof(DateTime); + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + bool IProtoSerializer.ReturnsValue => true; + + private readonly bool includeKind, wellKnown; + + public DateTimeSerializer(DataFormat dataFormat, ProtoBuf.Meta.TypeModel model) + { + wellKnown = dataFormat == DataFormat.WellKnown; + includeKind = model?.SerializeDateTimeKind() == true; + } + + public object Read(object value, ProtoReader source) + { + if (wellKnown) + { + return BclHelpers.ReadTimestamp(source); + } + else + { + Helpers.DebugAssert(value == null); // since replaces + return BclHelpers.ReadDateTime(source); + } + } + + public void Write(object value, ProtoWriter dest) + { + if (wellKnown) + BclHelpers.WriteTimestamp((DateTime)value, dest); + else if (includeKind) + BclHelpers.WriteDateTimeWithKind((DateTime)value, dest); + else + BclHelpers.WriteDateTime((DateTime)value, dest); + } +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitWrite(ctx.MapType(typeof(BclHelpers)), + wellKnown ? nameof(BclHelpers.WriteTimestamp) + : includeKind ? nameof(BclHelpers.WriteDateTimeWithKind) : nameof(BclHelpers.WriteDateTime), valueFrom); + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local entity) + { + if (wellKnown) ctx.LoadValue(entity); + ctx.EmitBasicRead(ctx.MapType(typeof(BclHelpers)), + wellKnown ? nameof(BclHelpers.ReadTimestamp) : nameof(BclHelpers.ReadDateTime), + ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs.meta new file mode 100644 index 0000000..6757f0c --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DateTimeSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfba0a8c252b2e54c96478c9e690c7d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/DecimalSerializer.cs b/Runtime/Protobuf-net/Serializers/DecimalSerializer.cs new file mode 100644 index 0000000..1edc621 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DecimalSerializer.cs @@ -0,0 +1,42 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class DecimalSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(decimal); + + public DecimalSerializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return BclHelpers.ReadDecimal(source); + } + + public void Write(object value, ProtoWriter dest) + { + BclHelpers.WriteDecimal((decimal)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitWrite(ctx.MapType(typeof(BclHelpers)), "WriteDecimal", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead(ctx.MapType(typeof(BclHelpers)), "ReadDecimal", ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/DecimalSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/DecimalSerializer.cs.meta new file mode 100644 index 0000000..f8e097a --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DecimalSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80efe6cca6916ab46b430c27dc58369c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs b/Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs new file mode 100644 index 0000000..895d0c4 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs @@ -0,0 +1,259 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class DefaultValueDecorator : ProtoDecoratorBase + { + public override Type ExpectedType => Tail.ExpectedType; + + public override bool RequiresOldValue => Tail.RequiresOldValue; + + public override bool ReturnsValue => Tail.ReturnsValue; + + private readonly object defaultValue; + public DefaultValueDecorator(TypeModel model, object defaultValue, IProtoSerializer tail) : base(tail) + { + if (defaultValue == null) throw new ArgumentNullException(nameof(defaultValue)); + Type type = model.MapType(defaultValue.GetType()); + if (type != tail.ExpectedType) + { + throw new ArgumentException("Default value is of incorrect type", "defaultValue"); + } + this.defaultValue = defaultValue; + } + + public override void Write(object value, ProtoWriter dest) + { + if (!object.Equals(value, defaultValue)) + { + Tail.Write(value, dest); + } + } + + public override object Read(object value, ProtoReader source) + { + return Tail.Read(value, source); + } + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Compiler.CodeLabel done = ctx.DefineLabel(); + if (valueFrom == null) + { + ctx.CopyValue(); // on the stack + Compiler.CodeLabel needToPop = ctx.DefineLabel(); + EmitBranchIfDefaultValue(ctx, needToPop); + Tail.EmitWrite(ctx, null); + ctx.Branch(done, true); + ctx.MarkLabel(needToPop); + ctx.DiscardValue(); + } + else + { + ctx.LoadValue(valueFrom); // variable/parameter + EmitBranchIfDefaultValue(ctx, done); + Tail.EmitWrite(ctx, valueFrom); + } + ctx.MarkLabel(done); + } + private void EmitBeq(Compiler.CompilerContext ctx, Compiler.CodeLabel label, Type type) + { + switch (Helpers.GetTypeCode(type)) + { + case ProtoTypeCode.Boolean: + case ProtoTypeCode.Byte: + case ProtoTypeCode.Char: + case ProtoTypeCode.Double: + case ProtoTypeCode.Int16: + case ProtoTypeCode.Int32: + case ProtoTypeCode.Int64: + case ProtoTypeCode.SByte: + case ProtoTypeCode.Single: + case ProtoTypeCode.UInt16: + case ProtoTypeCode.UInt32: + case ProtoTypeCode.UInt64: + ctx.BranchIfEqual(label, false); + break; + default: +#if COREFX + MethodInfo method = type.GetMethod("op_Equality", new Type[] { type, type }); + if (method == null || !method.IsPublic || !method.IsStatic) method = null; +#else + MethodInfo method = type.GetMethod("op_Equality", BindingFlags.Public | BindingFlags.Static, + null, new Type[] { type, type }, null); +#endif + if (method == null || method.ReturnType != ctx.MapType(typeof(bool))) + { + throw new InvalidOperationException("No suitable equality operator found for default-values of type: " + type.FullName); + } + ctx.EmitCall(method); + ctx.BranchIfTrue(label, false); + break; + + } + } + private void EmitBranchIfDefaultValue(Compiler.CompilerContext ctx, Compiler.CodeLabel label) + { + Type expected = ExpectedType; + switch (Helpers.GetTypeCode(expected)) + { + case ProtoTypeCode.Boolean: + if ((bool)defaultValue) + { + ctx.BranchIfTrue(label, false); + } + else + { + ctx.BranchIfFalse(label, false); + } + break; + case ProtoTypeCode.Byte: + if ((byte)defaultValue == (byte)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)(byte)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.SByte: + if ((sbyte)defaultValue == (sbyte)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)(sbyte)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.Int16: + if ((short)defaultValue == (short)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)(short)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.UInt16: + if ((ushort)defaultValue == (ushort)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)(ushort)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.Int32: + if ((int)defaultValue == (int)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.UInt32: + if ((uint)defaultValue == (uint)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)(uint)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.Char: + if ((char)defaultValue == (char)0) + { + ctx.BranchIfFalse(label, false); + } + else + { + ctx.LoadValue((int)(char)defaultValue); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.Int64: + ctx.LoadValue((long)defaultValue); + EmitBeq(ctx, label, expected); + break; + case ProtoTypeCode.UInt64: + ctx.LoadValue((long)(ulong)defaultValue); + EmitBeq(ctx, label, expected); + break; + case ProtoTypeCode.Double: + ctx.LoadValue((double)defaultValue); + EmitBeq(ctx, label, expected); + break; + case ProtoTypeCode.Single: + ctx.LoadValue((float)defaultValue); + EmitBeq(ctx, label, expected); + break; + case ProtoTypeCode.String: + ctx.LoadValue((string)defaultValue); + EmitBeq(ctx, label, expected); + break; + case ProtoTypeCode.Decimal: + { + decimal d = (decimal)defaultValue; + ctx.LoadValue(d); + EmitBeq(ctx, label, expected); + } + break; + case ProtoTypeCode.TimeSpan: + { + TimeSpan ts = (TimeSpan)defaultValue; + if (ts == TimeSpan.Zero) + { + ctx.LoadValue(typeof(TimeSpan).GetField("Zero")); + } + else + { + ctx.LoadValue(ts.Ticks); + ctx.EmitCall(ctx.MapType(typeof(TimeSpan)).GetMethod("FromTicks")); + } + EmitBeq(ctx, label, expected); + break; + } + case ProtoTypeCode.Guid: + { + ctx.LoadValue((Guid)defaultValue); + EmitBeq(ctx, label, expected); + break; + } + case ProtoTypeCode.DateTime: + { + ctx.LoadValue(((DateTime)defaultValue).ToBinary()); + ctx.EmitCall(ctx.MapType(typeof(DateTime)).GetMethod("FromBinary")); + + EmitBeq(ctx, label, expected); + break; + } + default: + throw new NotSupportedException("Type cannot be represented as a default value: " + expected.FullName); + } + } + + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Tail.EmitRead(ctx, valueFrom); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta new file mode 100644 index 0000000..7cbd6ed --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad3a3e386e17b67488f858d409d3e8a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/DoubleSerializer.cs b/Runtime/Protobuf-net/Serializers/DoubleSerializer.cs new file mode 100644 index 0000000..8b25523 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DoubleSerializer.cs @@ -0,0 +1,42 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class DoubleSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(double); + + public DoubleSerializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadDouble(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteDouble((double)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteDouble", valueFrom); + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadDouble", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/DoubleSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/DoubleSerializer.cs.meta new file mode 100644 index 0000000..cdba0a7 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/DoubleSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65b598b3ebee04946abf8957a0f92762 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/EnumSerializer.cs b/Runtime/Protobuf-net/Serializers/EnumSerializer.cs new file mode 100644 index 0000000..78cb78a --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/EnumSerializer.cs @@ -0,0 +1,267 @@ +#if !NO_RUNTIME +using System; +using ProtoBuf.Meta; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + sealed class EnumSerializer : IProtoSerializer + { + public readonly struct EnumPair + { + public readonly object RawValue; // note that this is boxing, but I'll live with it + public readonly Enum TypedValue; // note that this is boxing, but I'll live with it + public readonly int WireValue; + public EnumPair(int wireValue, object raw, Type type) + { + WireValue = wireValue; + RawValue = raw; + TypedValue = (Enum)Enum.ToObject(type, raw); + } + } + + private readonly Type enumType; + private readonly EnumPair[] map; + public EnumSerializer(Type enumType, EnumPair[] map) + { + this.enumType = enumType ?? throw new ArgumentNullException(nameof(enumType)); + this.map = map; + if (map != null) + { + for (int i = 1; i < map.Length; i++) + for (int j = 0; j < i; j++) + { + if (map[i].WireValue == map[j].WireValue && !Equals(map[i].RawValue, map[j].RawValue)) + { + throw new ProtoException("Multiple enums with wire-value " + map[i].WireValue.ToString()); + } + if (Equals(map[i].RawValue, map[j].RawValue) && map[i].WireValue != map[j].WireValue) + { + throw new ProtoException("Multiple enums with deserialized-value " + map[i].RawValue); + } + } + + } + } + + private ProtoTypeCode GetTypeCode() + { + Type type = Helpers.GetUnderlyingType(enumType); + if (type == null) type = enumType; + return Helpers.GetTypeCode(type); + } + + public Type ExpectedType => enumType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + private int EnumToWire(object value) + { + unchecked + { + switch (GetTypeCode()) + { // unbox then convert to int + case ProtoTypeCode.Byte: return (int)(byte)value; + case ProtoTypeCode.SByte: return (int)(sbyte)value; + case ProtoTypeCode.Int16: return (int)(short)value; + case ProtoTypeCode.Int32: return (int)value; + case ProtoTypeCode.Int64: return (int)(long)value; + case ProtoTypeCode.UInt16: return (int)(ushort)value; + case ProtoTypeCode.UInt32: return (int)(uint)value; + case ProtoTypeCode.UInt64: return (int)(ulong)value; + default: throw new InvalidOperationException(); + } + } + } + + private object WireToEnum(int value) + { + unchecked + { + switch (GetTypeCode()) + { // convert from int then box + case ProtoTypeCode.Byte: return Enum.ToObject(enumType, (byte)value); + case ProtoTypeCode.SByte: return Enum.ToObject(enumType, (sbyte)value); + case ProtoTypeCode.Int16: return Enum.ToObject(enumType, (short)value); + case ProtoTypeCode.Int32: return Enum.ToObject(enumType, value); + case ProtoTypeCode.Int64: return Enum.ToObject(enumType, (long)value); + case ProtoTypeCode.UInt16: return Enum.ToObject(enumType, (ushort)value); + case ProtoTypeCode.UInt32: return Enum.ToObject(enumType, (uint)value); + case ProtoTypeCode.UInt64: return Enum.ToObject(enumType, (ulong)value); + default: throw new InvalidOperationException(); + } + } + } + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + int wireValue = source.ReadInt32(); + if (map == null) + { + return WireToEnum(wireValue); + } + for (int i = 0; i < map.Length; i++) + { + if (map[i].WireValue == wireValue) + { + return map[i].TypedValue; + } + } + source.ThrowEnumException(ExpectedType, wireValue); + return null; // to make compiler happy + } + + public void Write(object value, ProtoWriter dest) + { + if (map == null) + { + ProtoWriter.WriteInt32(EnumToWire(value), dest); + } + else + { + for (int i = 0; i < map.Length; i++) + { + if (object.Equals(map[i].TypedValue, value)) + { + ProtoWriter.WriteInt32(map[i].WireValue, dest); + return; + } + } + ProtoWriter.ThrowEnumException(dest, value); + } + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ProtoTypeCode typeCode = GetTypeCode(); + if (map == null) + { + ctx.LoadValue(valueFrom); + ctx.ConvertToInt32(typeCode, false); + ctx.EmitBasicWrite("WriteInt32", null); + } + else + { + using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + { + Compiler.CodeLabel @continue = ctx.DefineLabel(); + for (int i = 0; i < map.Length; i++) + { + Compiler.CodeLabel tryNextValue = ctx.DefineLabel(), processThisValue = ctx.DefineLabel(); + ctx.LoadValue(loc); + WriteEnumValue(ctx, typeCode, map[i].RawValue); + ctx.BranchIfEqual(processThisValue, true); + ctx.Branch(tryNextValue, true); + ctx.MarkLabel(processThisValue); + ctx.LoadValue(map[i].WireValue); + ctx.EmitBasicWrite("WriteInt32", null); + ctx.Branch(@continue, false); + ctx.MarkLabel(tryNextValue); + } + ctx.LoadReaderWriter(); + ctx.LoadValue(loc); + ctx.CastToObject(ExpectedType); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("ThrowEnumException")); + ctx.MarkLabel(@continue); + } + } + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ProtoTypeCode typeCode = GetTypeCode(); + if (map == null) + { + ctx.EmitBasicRead("ReadInt32", ctx.MapType(typeof(int))); + ctx.ConvertFromInt32(typeCode, false); + } + else + { + int[] wireValues = new int[map.Length]; + object[] values = new object[map.Length]; + for (int i = 0; i < map.Length; i++) + { + wireValues[i] = map[i].WireValue; + values[i] = map[i].RawValue; + } + using (Compiler.Local result = new Compiler.Local(ctx, ExpectedType)) + using (Compiler.Local wireValue = new Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + ctx.EmitBasicRead("ReadInt32", ctx.MapType(typeof(int))); + ctx.StoreValue(wireValue); + Compiler.CodeLabel @continue = ctx.DefineLabel(); + foreach (BasicList.Group group in BasicList.GetContiguousGroups(wireValues, values)) + { + Compiler.CodeLabel tryNextGroup = ctx.DefineLabel(); + int groupItemCount = group.Items.Count; + if (groupItemCount == 1) + { + // discreet group; use an equality test + ctx.LoadValue(wireValue); + ctx.LoadValue(group.First); + Compiler.CodeLabel processThisValue = ctx.DefineLabel(); + ctx.BranchIfEqual(processThisValue, true); + ctx.Branch(tryNextGroup, false); + WriteEnumValue(ctx, typeCode, processThisValue, @continue, group.Items[0], @result); + } + else + { + // implement as a jump-table-based switch + ctx.LoadValue(wireValue); + ctx.LoadValue(group.First); + ctx.Subtract(); // jump-tables are zero-based + Compiler.CodeLabel[] jmp = new Compiler.CodeLabel[groupItemCount]; + for (int i = 0; i < groupItemCount; i++) + { + jmp[i] = ctx.DefineLabel(); + } + ctx.Switch(jmp); + // write the default... + ctx.Branch(tryNextGroup, false); + for (int i = 0; i < groupItemCount; i++) + { + WriteEnumValue(ctx, typeCode, jmp[i], @continue, group.Items[i], @result); + } + } + ctx.MarkLabel(tryNextGroup); + } + // throw source.CreateEnumException(ExpectedType, wireValue); + ctx.LoadReaderWriter(); + ctx.LoadValue(ExpectedType); + ctx.LoadValue(wireValue); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("ThrowEnumException")); + ctx.MarkLabel(@continue); + ctx.LoadValue(result); + } + } + } + private static void WriteEnumValue(Compiler.CompilerContext ctx, ProtoTypeCode typeCode, object value) + { + switch (typeCode) + { + case ProtoTypeCode.Byte: ctx.LoadValue((int)(byte)value); break; + case ProtoTypeCode.SByte: ctx.LoadValue((int)(sbyte)value); break; + case ProtoTypeCode.Int16: ctx.LoadValue((int)(short)value); break; + case ProtoTypeCode.Int32: ctx.LoadValue((int)(int)value); break; + case ProtoTypeCode.Int64: ctx.LoadValue((long)(long)value); break; + case ProtoTypeCode.UInt16: ctx.LoadValue((int)(ushort)value); break; + case ProtoTypeCode.UInt32: ctx.LoadValue((int)(uint)value); break; + case ProtoTypeCode.UInt64: ctx.LoadValue((long)(ulong)value); break; + default: throw new InvalidOperationException(); + } + } + private static void WriteEnumValue(Compiler.CompilerContext ctx, ProtoTypeCode typeCode, Compiler.CodeLabel handler, Compiler.CodeLabel @continue, object value, Compiler.Local local) + { + ctx.MarkLabel(handler); + WriteEnumValue(ctx, typeCode, value); + ctx.StoreValue(local); + ctx.Branch(@continue, false); // "continue" + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/EnumSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/EnumSerializer.cs.meta new file mode 100644 index 0000000..b58d866 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/EnumSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef6c6d630a8f5ca449eec10513147563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/FieldDecorator.cs b/Runtime/Protobuf-net/Serializers/FieldDecorator.cs new file mode 100644 index 0000000..26c0452 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/FieldDecorator.cs @@ -0,0 +1,104 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + sealed class FieldDecorator : ProtoDecoratorBase + { + public override Type ExpectedType => forType; + private readonly FieldInfo field; + private readonly Type forType; + public override bool RequiresOldValue => true; + public override bool ReturnsValue => false; + public FieldDecorator(Type forType, FieldInfo field, IProtoSerializer tail) : base(tail) + { + Helpers.DebugAssert(forType != null); + Helpers.DebugAssert(field != null); + this.forType = forType; + this.field = field; + } + + public override void Write(object value, ProtoWriter dest) + { + Helpers.DebugAssert(value != null); + value = field.GetValue(value); + if (value != null) Tail.Write(value, dest); + } + + public override object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value != null); + object newValue = Tail.Read((Tail.RequiresOldValue ? field.GetValue(value) : null), source); + if (newValue != null) field.SetValue(value, newValue); + return null; + } + + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadAddress(valueFrom, ExpectedType); + ctx.LoadValue(field); + ctx.WriteNullCheckedTail(field.FieldType, Tail, null); + } + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + { + if (Tail.RequiresOldValue) + { + ctx.LoadAddress(loc, ExpectedType); + ctx.LoadValue(field); + } + // value is either now on the stack or not needed + ctx.ReadNullCheckedTail(field.FieldType, Tail, null); + + // the field could be a backing field that needs to be raised back to + // the property if we're doing a full compile + MemberInfo member = field; + ctx.CheckAccessibility(ref member); + bool writeValue = member is FieldInfo; + + if (writeValue) + { + if (Tail.ReturnsValue) + { + using (Compiler.Local newVal = new Compiler.Local(ctx, field.FieldType)) + { + ctx.StoreValue(newVal); + if (Helpers.IsValueType(field.FieldType)) + { + ctx.LoadAddress(loc, ExpectedType); + ctx.LoadValue(newVal); + ctx.StoreValue(field); + } + else + { + Compiler.CodeLabel allDone = ctx.DefineLabel(); + ctx.LoadValue(newVal); + ctx.BranchIfFalse(allDone, true); // interpret null as "don't assign" + + ctx.LoadAddress(loc, ExpectedType); + ctx.LoadValue(newVal); + ctx.StoreValue(field); + + ctx.MarkLabel(allDone); + } + } + } + } + else + { + // can't use result + if (Tail.ReturnsValue) + { + ctx.DiscardValue(); + } + } + } + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/FieldDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/FieldDecorator.cs.meta new file mode 100644 index 0000000..63065a7 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/FieldDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7c1c3141cd2fad47b3112747b44314a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/GuidSerializer.cs b/Runtime/Protobuf-net/Serializers/GuidSerializer.cs new file mode 100644 index 0000000..27556d5 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/GuidSerializer.cs @@ -0,0 +1,43 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class GuidSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(Guid); + + public GuidSerializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType { get { return expectedType; } } + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public void Write(object value, ProtoWriter dest) + { + BclHelpers.WriteGuid((Guid)value, dest); + } + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return BclHelpers.ReadGuid(source); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitWrite(ctx.MapType(typeof(BclHelpers)), "WriteGuid", valueFrom); + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead(ctx.MapType(typeof(BclHelpers)), "ReadGuid", ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/GuidSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/GuidSerializer.cs.meta new file mode 100644 index 0000000..7eeb096 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/GuidSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 362fe2dd035b0cb4eaff2c7b7337fc66 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/IProtoSerializer.cs b/Runtime/Protobuf-net/Serializers/IProtoSerializer.cs new file mode 100644 index 0000000..59e8cc2 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/IProtoSerializer.cs @@ -0,0 +1,64 @@ +#if !NO_RUNTIME +using System; + + +namespace ProtoBuf.Serializers +{ + interface IProtoSerializer + { + /// + /// The type that this serializer is intended to work for. + /// + Type ExpectedType { get; } + + /// + /// Perform the steps necessary to serialize this data. + /// + /// The value to be serialized. + /// The writer entity that is accumulating the output data. + void Write(object value, ProtoWriter dest); + + /// + /// Perform the steps necessary to deserialize this data. + /// + /// The current value, if appropriate. + /// The reader providing the input data. + /// The updated / replacement value. + object Read(object value, ProtoReader source); + + /// + /// Indicates whether a Read operation replaces the existing value, or + /// extends the value. If false, the "value" parameter to Read is + /// discarded, and should be passed in as null. + /// + bool RequiresOldValue { get; } + /// + /// Now all Read operations return a value (although most do); if false no + /// value should be expected. + /// + bool ReturnsValue { get; } + +#if FEAT_COMPILER + /// Emit the IL necessary to perform the given actions + /// to serialize this data. + /// + /// Details and utilities for the method being generated. + /// The source of the data to work against; + /// If the value is only needed once, then LoadValue is sufficient. If + /// the value is needed multiple times, then note that a "null" + /// means "the top of the stack", in which case you should create your + /// own copy - GetLocalWithValue. + void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom); + + /// + /// Emit the IL necessary to perform the given actions to deserialize this data. + /// + /// Details and utilities for the method being generated. + /// For nested values, the instance holding the values; note + /// that this is not always provided - a null means not supplied. Since this is always + /// a variable or argument, it is not necessary to consume this value. + void EmitRead(Compiler.CompilerContext ctx, Compiler.Local entity); +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/IProtoSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/IProtoSerializer.cs.meta new file mode 100644 index 0000000..40d402d --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/IProtoSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6acc35442de99c94aade5d43c7992338 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs b/Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs new file mode 100644 index 0000000..da1439b --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs @@ -0,0 +1,20 @@ +#if !NO_RUNTIME +using ProtoBuf.Meta; +namespace ProtoBuf.Serializers +{ + interface IProtoTypeSerializer : IProtoSerializer + { + bool HasCallbacks(TypeModel.CallbackType callbackType); + bool CanCreateInstance(); + object CreateInstance(ProtoReader source); + void Callback(object value, TypeModel.CallbackType callbackType, SerializationContext context); + +#if FEAT_COMPILER + void EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, TypeModel.CallbackType callbackType); +#endif +#if FEAT_COMPILER + void EmitCreateInstance(Compiler.CompilerContext ctx); +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta new file mode 100644 index 0000000..d4c96cf --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6974491708512ec41b7a5f29805e4c69 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ISerializerProxy.cs b/Runtime/Protobuf-net/Serializers/ISerializerProxy.cs new file mode 100644 index 0000000..3ab2cb8 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ISerializerProxy.cs @@ -0,0 +1,10 @@ +#if !NO_RUNTIME + +namespace ProtoBuf.Serializers +{ + interface ISerializerProxy + { + IProtoSerializer Serializer { get; } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ISerializerProxy.cs.meta b/Runtime/Protobuf-net/Serializers/ISerializerProxy.cs.meta new file mode 100644 index 0000000..aa3cdfa --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ISerializerProxy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f717dd1190cbe174587e3bff7dd3dd76 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs b/Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs new file mode 100644 index 0000000..918d1fd --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs @@ -0,0 +1,304 @@ +#if !NO_RUNTIME +using System; +using System.Collections; +using System.Reflection; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class ImmutableCollectionDecorator : ListDecorator + { + protected override bool RequireAdd { get { return false; } } + + static Type ResolveIReadOnlyCollection(Type declaredType, Type t) + { +#if COREFX || PROFILE259 + if (CheckIsIReadOnlyCollectionExactly(declaredType.GetTypeInfo())) return declaredType; + foreach (Type intImplBasic in declaredType.GetTypeInfo().ImplementedInterfaces) + { + TypeInfo intImpl = intImplBasic.GetTypeInfo(); + if (CheckIsIReadOnlyCollectionExactly(intImpl)) return intImplBasic; + } +#else + if (CheckIsIReadOnlyCollectionExactly(declaredType)) return declaredType; + foreach (Type intImpl in declaredType.GetInterfaces()) + { + if (CheckIsIReadOnlyCollectionExactly(intImpl)) return intImpl; + } +#endif + return null; + } + +#if WINRT || COREFX || PROFILE259 + static bool CheckIsIReadOnlyCollectionExactly(TypeInfo t) +#else + static bool CheckIsIReadOnlyCollectionExactly(Type t) +#endif + { + if (t != null && t.IsGenericType && t.Name.StartsWith("IReadOnlyCollection`")) + { +#if WINRT || COREFX || PROFILE259 + Type[] typeArgs = t.GenericTypeArguments; + if (typeArgs.Length != 1 && typeArgs[0].GetTypeInfo().Equals(t)) return false; +#else + Type[] typeArgs = t.GetGenericArguments(); + if (typeArgs.Length != 1 && typeArgs[0] != t) return false; +#endif + + return true; + } + return false; + } + + internal static bool IdentifyImmutable(TypeModel model, Type declaredType, out MethodInfo builderFactory, out PropertyInfo isEmpty, out PropertyInfo length, out MethodInfo add, out MethodInfo addRange, out MethodInfo finish) + { + builderFactory = add = addRange = finish = null; + isEmpty = length = null; + if (model == null || declaredType == null) return false; +#if COREFX || PROFILE259 + TypeInfo declaredTypeInfo = declaredType.GetTypeInfo(); +#else + Type declaredTypeInfo = declaredType; +#endif + + // try to detect immutable collections; firstly, they are all generic, and all implement IReadOnlyCollection for some T + if (!declaredTypeInfo.IsGenericType) return false; + +#if COREFX || PROFILE259 + Type[] typeArgs = declaredTypeInfo.GenericTypeArguments, effectiveType; +#else + Type[] typeArgs = declaredTypeInfo.GetGenericArguments(), effectiveType; +#endif + switch (typeArgs.Length) + { + case 1: + effectiveType = typeArgs; + break; // fine + case 2: + Type kvp = model.MapType(typeof(System.Collections.Generic.KeyValuePair<,>)); + if (kvp == null) return false; + kvp = kvp.MakeGenericType(typeArgs); + effectiveType = new Type[] { kvp }; + break; + default: + return false; // no clue! + } + + if (ResolveIReadOnlyCollection(declaredType, null) == null) return false; // no IReadOnlyCollection found + + // and we want to use the builder API, so for generic Foo or IFoo we want to use Foo.CreateBuilder + string name = declaredType.Name; + int i = name.IndexOf('`'); + if (i <= 0) return false; + name = declaredTypeInfo.IsInterface ? name.Substring(1, i - 1) : name.Substring(0, i); + + Type outerType = model.GetType(declaredType.Namespace + "." + name, declaredTypeInfo.Assembly); + // I hate special-cases... + if (outerType == null && name == "ImmutableSet") + { + outerType = model.GetType(declaredType.Namespace + ".ImmutableHashSet", declaredTypeInfo.Assembly); + } + if (outerType == null) return false; + +#if PROFILE259 + foreach (MethodInfo method in outerType.GetTypeInfo().DeclaredMethods) +#else + foreach (MethodInfo method in outerType.GetMethods()) +#endif + { + if (!method.IsStatic || method.Name != "CreateBuilder" || !method.IsGenericMethodDefinition || method.GetParameters().Length != 0 + || method.GetGenericArguments().Length != typeArgs.Length) continue; + + builderFactory = method.MakeGenericMethod(typeArgs); + break; + } + Type voidType = model.MapType(typeof(void)); + if (builderFactory == null || builderFactory.ReturnType == null || builderFactory.ReturnType == voidType) return false; + +#if COREFX + TypeInfo typeInfo = declaredType.GetTypeInfo(); +#else + Type typeInfo = declaredType; +#endif + isEmpty = Helpers.GetProperty(typeInfo, "IsDefaultOrEmpty", false); //struct based immutabletypes can have both a "default" and "empty" state + if (isEmpty == null) isEmpty = Helpers.GetProperty(typeInfo, "IsEmpty", false); + if (isEmpty == null) + { + //Fallback to checking length if a "IsEmpty" property is not found + length = Helpers.GetProperty(typeInfo, "Length", false); + if (length == null) length = Helpers.GetProperty(typeInfo, "Count", false); + + if (length == null) length = Helpers.GetProperty(ResolveIReadOnlyCollection(declaredType, effectiveType[0]), "Count", false); + + if (length == null) return false; + } + + add = Helpers.GetInstanceMethod(builderFactory.ReturnType, "Add", effectiveType); + if (add == null) return false; + + finish = Helpers.GetInstanceMethod(builderFactory.ReturnType, "ToImmutable", Helpers.EmptyTypes); + if (finish == null || finish.ReturnType == null || finish.ReturnType == voidType) return false; + + if (!(finish.ReturnType == declaredType || Helpers.IsAssignableFrom(declaredType, finish.ReturnType))) return false; + + addRange = Helpers.GetInstanceMethod(builderFactory.ReturnType, "AddRange", new Type[] { declaredType }); + if (addRange == null) + { + Type enumerable = model.MapType(typeof(System.Collections.Generic.IEnumerable<>), false); + if (enumerable != null) + { + addRange = Helpers.GetInstanceMethod(builderFactory.ReturnType, "AddRange", new Type[] { enumerable.MakeGenericType(effectiveType) }); + } + } + + return true; + } + + private readonly MethodInfo builderFactory, add, addRange, finish; + private readonly PropertyInfo isEmpty, length; + internal ImmutableCollectionDecorator(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull, + MethodInfo builderFactory, PropertyInfo isEmpty, PropertyInfo length, MethodInfo add, MethodInfo addRange, MethodInfo finish) + : base(model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull) + { + this.builderFactory = builderFactory; + this.isEmpty = isEmpty; + this.length = length; + this.add = add; + this.addRange = addRange; + this.finish = finish; + } + + public override object Read(object value, ProtoReader source) + { + object builderInstance = builderFactory.Invoke(null, null); + int field = source.FieldNumber; + object[] args = new object[1]; + if (AppendToCollection && value != null && (isEmpty != null ? !(bool)isEmpty.GetValue(value, null) : (int)length.GetValue(value, null) != 0)) + { + if (addRange != null) + { + args[0] = value; + addRange.Invoke(builderInstance, args); + } + else + { + foreach (object item in (ICollection)value) + { + args[0] = item; + add.Invoke(builderInstance, args); + } + } + } + + if (packedWireType != WireType.None && source.WireType == WireType.String) + { + SubItemToken token = ProtoReader.StartSubItem(source); + while (ProtoReader.HasSubValue(packedWireType, source)) + { + args[0] = Tail.Read(null, source); + add.Invoke(builderInstance, args); + } + ProtoReader.EndSubItem(token, source); + } + else + { + do + { + args[0] = Tail.Read(null, source); + add.Invoke(builderInstance, args); + } while (source.TryReadFieldHeader(field)); + } + + return finish.Invoke(builderInstance, null); + } + +#if FEAT_COMPILER + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + using (Compiler.Local oldList = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : null) + using (Compiler.Local builder = new Compiler.Local(ctx, builderFactory.ReturnType)) + { + ctx.EmitCall(builderFactory); + ctx.StoreValue(builder); + + if (AppendToCollection) + { + Compiler.CodeLabel done = ctx.DefineLabel(); + if (!Helpers.IsValueType(ExpectedType)) + { + ctx.LoadValue(oldList); + ctx.BranchIfFalse(done, false); // old value null; nothing to add + } + + ctx.LoadAddress(oldList, oldList.Type); + if (isEmpty != null) + { + ctx.EmitCall(Helpers.GetGetMethod(isEmpty, false, false)); + ctx.BranchIfTrue(done, false); // old list is empty; nothing to add + } + else + { + ctx.EmitCall(Helpers.GetGetMethod(length, false, false)); + ctx.BranchIfFalse(done, false); // old list is empty; nothing to add + } + + Type voidType = ctx.MapType(typeof(void)); + if (addRange != null) + { + ctx.LoadValue(builder); + ctx.LoadValue(oldList); + ctx.EmitCall(addRange); + if (addRange.ReturnType != null && add.ReturnType != voidType) ctx.DiscardValue(); + } + else + { + // loop and call Add repeatedly + MethodInfo moveNext, current, getEnumerator = GetEnumeratorInfo(ctx.Model, out moveNext, out current); + Helpers.DebugAssert(moveNext != null); + Helpers.DebugAssert(current != null); + Helpers.DebugAssert(getEnumerator != null); + + Type enumeratorType = getEnumerator.ReturnType; + using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType)) + { + ctx.LoadAddress(oldList, ExpectedType); + ctx.EmitCall(getEnumerator); + ctx.StoreValue(iter); + using (ctx.Using(iter)) + { + Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel(); + ctx.Branch(next, false); + + ctx.MarkLabel(body); + ctx.LoadAddress(builder, builder.Type); + ctx.LoadAddress(iter, enumeratorType); + ctx.EmitCall(current); + ctx.EmitCall(add); + if (add.ReturnType != null && add.ReturnType != voidType) ctx.DiscardValue(); + + ctx.MarkLabel(@next); + ctx.LoadAddress(iter, enumeratorType); + ctx.EmitCall(moveNext); + ctx.BranchIfTrue(body, false); + } + } + } + + + ctx.MarkLabel(done); + } + + EmitReadList(ctx, builder, Tail, add, packedWireType, false); + + ctx.LoadAddress(builder, builder.Type); + ctx.EmitCall(finish); + if (ExpectedType != finish.ReturnType) + { + ctx.Cast(ExpectedType); + } + } + } +#endif + } +} +#endif diff --git a/Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta new file mode 100644 index 0000000..f8d9012 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00a3af58286d1674ca64bcf5fd9f0228 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/Int16Serializer.cs b/Runtime/Protobuf-net/Serializers/Int16Serializer.cs new file mode 100644 index 0000000..eac4eb4 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/Int16Serializer.cs @@ -0,0 +1,42 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class Int16Serializer : IProtoSerializer + { + static readonly Type expectedType = typeof(short); + + public Int16Serializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadInt16(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteInt16((short)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteInt16", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadInt16", ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/Int16Serializer.cs.meta b/Runtime/Protobuf-net/Serializers/Int16Serializer.cs.meta new file mode 100644 index 0000000..546159f --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/Int16Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e45229312d2a4fe45b519e513326b708 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/Int32Serializer.cs b/Runtime/Protobuf-net/Serializers/Int32Serializer.cs new file mode 100644 index 0000000..204880e --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/Int32Serializer.cs @@ -0,0 +1,42 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class Int32Serializer : IProtoSerializer + { + static readonly Type expectedType = typeof(int); + + public Int32Serializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadInt32(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteInt32((int)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteInt32", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadInt32", ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/Int32Serializer.cs.meta b/Runtime/Protobuf-net/Serializers/Int32Serializer.cs.meta new file mode 100644 index 0000000..1be4c7e --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/Int32Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a7c49bc45156f442bfe84fc7eef04b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/Int64Serializer.cs b/Runtime/Protobuf-net/Serializers/Int64Serializer.cs new file mode 100644 index 0000000..2791a1e --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/Int64Serializer.cs @@ -0,0 +1,41 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class Int64Serializer : IProtoSerializer + { + static readonly Type expectedType = typeof(long); + + public Int64Serializer(ProtoBuf.Meta.TypeModel model) { } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadInt64(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteInt64((long)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteInt64", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadInt64", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/Int64Serializer.cs.meta b/Runtime/Protobuf-net/Serializers/Int64Serializer.cs.meta new file mode 100644 index 0000000..8dbba5e --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/Int64Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03f2770a306f45046b6e8eab757c9188 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ListDecorator.cs b/Runtime/Protobuf-net/Serializers/ListDecorator.cs new file mode 100644 index 0000000..82bb128 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ListDecorator.cs @@ -0,0 +1,579 @@ +#if !NO_RUNTIME +using System; +using System.Collections; +using ProtoBuf.Meta; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + class ListDecorator : ProtoDecoratorBase + { + internal static bool CanPack(WireType wireType) + { + switch (wireType) + { + case WireType.Fixed32: + case WireType.Fixed64: + case WireType.SignedVariant: + case WireType.Variant: + return true; + default: + return false; + } + } + + private readonly byte options; + + private const byte OPTIONS_IsList = 1, + OPTIONS_SuppressIList = 2, + OPTIONS_WritePacked = 4, + OPTIONS_ReturnList = 8, + OPTIONS_OverwriteList = 16, + OPTIONS_SupportNull = 32; + + private readonly Type declaredType, concreteType; + + private readonly MethodInfo add; + + private readonly int fieldNumber; + + private bool IsList { get { return (options & OPTIONS_IsList) != 0; } } + private bool SuppressIList { get { return (options & OPTIONS_SuppressIList) != 0; } } + private bool WritePacked { get { return (options & OPTIONS_WritePacked) != 0; } } + private bool SupportNull { get { return (options & OPTIONS_SupportNull) != 0; } } + private bool ReturnList { get { return (options & OPTIONS_ReturnList) != 0; } } + protected readonly WireType packedWireType; + + internal static ListDecorator Create(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull) + { + if (returnList && ImmutableCollectionDecorator.IdentifyImmutable(model, declaredType, + out MethodInfo builderFactory, + out PropertyInfo isEmpty, + out PropertyInfo length, + out MethodInfo add, + out MethodInfo addRange, + out MethodInfo finish)) + { + return new ImmutableCollectionDecorator( + model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull, + builderFactory, isEmpty, length, add, addRange, finish); + } + + return new ListDecorator(model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull); + } + + protected ListDecorator(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull) + : base(tail) + { + if (returnList) options |= OPTIONS_ReturnList; + if (overwriteList) options |= OPTIONS_OverwriteList; + if (supportNull) options |= OPTIONS_SupportNull; + if ((writePacked || packedWireType != WireType.None) && fieldNumber <= 0) throw new ArgumentOutOfRangeException("fieldNumber"); + if (!CanPack(packedWireType)) + { + if (writePacked) throw new InvalidOperationException("Only simple data-types can use packed encoding"); + packedWireType = WireType.None; + } + + this.fieldNumber = fieldNumber; + if (writePacked) options |= OPTIONS_WritePacked; + this.packedWireType = packedWireType; + if (declaredType == null) throw new ArgumentNullException("declaredType"); + if (declaredType.IsArray) throw new ArgumentException("Cannot treat arrays as lists", "declaredType"); + this.declaredType = declaredType; + this.concreteType = concreteType; + + // look for a public list.Add(typedObject) method + if (RequireAdd) + { + bool isList; + add = TypeModel.ResolveListAdd(model, declaredType, tail.ExpectedType, out isList); + if (isList) + { + options |= OPTIONS_IsList; + string fullName = declaredType.FullName; + if (fullName != null && fullName.StartsWith("System.Data.Linq.EntitySet`1[[")) + { // see http://stackoverflow.com/questions/6194639/entityset-is-there-a-sane-reason-that-ilist-add-doesnt-set-assigned + options |= OPTIONS_SuppressIList; + } + } + if (add == null) throw new InvalidOperationException("Unable to resolve a suitable Add method for " + declaredType.FullName); + } + + } + protected virtual bool RequireAdd => true; + + public override Type ExpectedType => declaredType; + + public override bool RequiresOldValue => AppendToCollection; + + public override bool ReturnsValue => ReturnList; + + protected bool AppendToCollection + { + get { return (options & OPTIONS_OverwriteList) == 0; } + } + +#if FEAT_COMPILER + protected override void EmitRead(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom) + { + /* This looks more complex than it is. Look at the non-compiled Read to + * see what it is trying to do, but note that it needs to cope with a + * few more scenarios. Note that it picks the **most specific** Add, + * unlike the runtime version that uses IList when possible. The core + * is just a "do {list.Add(readValue())} while {thereIsMore}" + * + * The complexity is due to: + * - value types vs reference types (boxing etc) + * - initialization if we need to pass in a value to the tail + * - handling whether or not the tail *returns* the value vs updates the input + */ + bool returnList = ReturnList; + + using (Compiler.Local list = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : new Compiler.Local(ctx, declaredType)) + using (Compiler.Local origlist = (returnList && AppendToCollection && !Helpers.IsValueType(ExpectedType)) ? new Compiler.Local(ctx, ExpectedType) : null) + { + if (!AppendToCollection) + { // always new + ctx.LoadNullRef(); + ctx.StoreValue(list); + } + else if (returnList && origlist != null) + { // need a copy + ctx.LoadValue(list); + ctx.StoreValue(origlist); + } + if (concreteType != null) + { + ctx.LoadValue(list); + Compiler.CodeLabel notNull = ctx.DefineLabel(); + ctx.BranchIfTrue(notNull, true); + ctx.EmitCtor(concreteType); + ctx.StoreValue(list); + ctx.MarkLabel(notNull); + } + + bool castListForAdd = !add.DeclaringType.IsAssignableFrom(declaredType); + EmitReadList(ctx, list, Tail, add, packedWireType, castListForAdd); + + if (returnList) + { + if (AppendToCollection && origlist != null) + { + // remember ^^^^ we had a spare copy of the list on the stack; now we'll compare + ctx.LoadValue(origlist); + ctx.LoadValue(list); // [orig] [new-value] + Compiler.CodeLabel sameList = ctx.DefineLabel(), allDone = ctx.DefineLabel(); + ctx.BranchIfEqual(sameList, true); + ctx.LoadValue(list); + ctx.Branch(allDone, true); + ctx.MarkLabel(sameList); + ctx.LoadNullRef(); + ctx.MarkLabel(allDone); + } + else + { + ctx.LoadValue(list); + } + } + } + } + + internal static void EmitReadList(ProtoBuf.Compiler.CompilerContext ctx, Compiler.Local list, IProtoSerializer tail, MethodInfo add, WireType packedWireType, bool castListForAdd) + { + using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + Compiler.CodeLabel readPacked = packedWireType == WireType.None ? new Compiler.CodeLabel() : ctx.DefineLabel(); + if (packedWireType != WireType.None) + { + ctx.LoadReaderWriter(); + ctx.LoadValue(typeof(ProtoReader).GetProperty("WireType")); + ctx.LoadValue((int)WireType.String); + ctx.BranchIfEqual(readPacked, false); + } + ctx.LoadReaderWriter(); + ctx.LoadValue(typeof(ProtoReader).GetProperty("FieldNumber")); + ctx.StoreValue(fieldNumber); + + Compiler.CodeLabel @continue = ctx.DefineLabel(); + ctx.MarkLabel(@continue); + + EmitReadAndAddItem(ctx, list, tail, add, castListForAdd); + + ctx.LoadReaderWriter(); + ctx.LoadValue(fieldNumber); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("TryReadFieldHeader")); + ctx.BranchIfTrue(@continue, false); + + if (packedWireType != WireType.None) + { + Compiler.CodeLabel allDone = ctx.DefineLabel(); + ctx.Branch(allDone, false); + ctx.MarkLabel(readPacked); + + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem")); + + Compiler.CodeLabel testForData = ctx.DefineLabel(), noMoreData = ctx.DefineLabel(); + ctx.MarkLabel(testForData); + ctx.LoadValue((int)packedWireType); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("HasSubValue")); + ctx.BranchIfFalse(noMoreData, false); + + EmitReadAndAddItem(ctx, list, tail, add, castListForAdd); + ctx.Branch(testForData, false); + + ctx.MarkLabel(noMoreData); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem")); + ctx.MarkLabel(allDone); + } + } + } + + private static void EmitReadAndAddItem(Compiler.CompilerContext ctx, Compiler.Local list, IProtoSerializer tail, MethodInfo add, bool castListForAdd) + { + ctx.LoadAddress(list, list.Type); // needs to be the reference in case the list is value-type (static-call) + if (castListForAdd) ctx.Cast(add.DeclaringType); + + Type itemType = tail.ExpectedType; + bool tailReturnsValue = tail.ReturnsValue; + if (tail.RequiresOldValue) + { + if (Helpers.IsValueType(itemType) || !tailReturnsValue) + { + // going to need a variable + using (Compiler.Local item = new Compiler.Local(ctx, itemType)) + { + if (Helpers.IsValueType(itemType)) + { // initialise the struct + ctx.LoadAddress(item, itemType); + ctx.EmitCtor(itemType); + } + else + { // assign null + ctx.LoadNullRef(); + ctx.StoreValue(item); + } + tail.EmitRead(ctx, item); + if (!tailReturnsValue) { ctx.LoadValue(item); } + } + } + else + { // no variable; pass the null on the stack and take the value *off* the stack + ctx.LoadNullRef(); + tail.EmitRead(ctx, null); + } + } + else + { + if (tailReturnsValue) + { // out only (on the stack); just emit it + tail.EmitRead(ctx, null); + } + else + { // doesn't take anything in nor return anything! WTF? + throw new InvalidOperationException(); + } + } + // our "Add" is chosen either to take the correct type, or to take "object"; + // we may need to box the value + + Type addParamType = add.GetParameters()[0].ParameterType; + if (addParamType != itemType) + { + if (addParamType == ctx.MapType(typeof(object))) + { + ctx.CastToObject(itemType); + } + else if (Helpers.GetUnderlyingType(addParamType) == itemType) + { // list is nullable + ConstructorInfo ctor = Helpers.GetConstructor(addParamType, new Type[] { itemType }, false); + ctx.EmitCtor(ctor); // the itemType on the stack is now a Nullable + } + else + { + throw new InvalidOperationException("Conflicting item/add type"); + } + } + ctx.EmitCall(add, list.Type); + if (add.ReturnType != ctx.MapType(typeof(void))) + { + ctx.DiscardValue(); + } + } +#endif + +#if COREFX + private static readonly TypeInfo ienumeratorType = typeof(IEnumerator).GetTypeInfo(), ienumerableType = typeof (IEnumerable).GetTypeInfo(); +#else + private static readonly System.Type ienumeratorType = typeof(IEnumerator), ienumerableType = typeof(IEnumerable); +#endif + protected MethodInfo GetEnumeratorInfo(TypeModel model, out MethodInfo moveNext, out MethodInfo current) + => GetEnumeratorInfo(model, ExpectedType, Tail.ExpectedType, out moveNext, out current); + internal static MethodInfo GetEnumeratorInfo(TypeModel model, Type expectedType, Type itemType, out MethodInfo moveNext, out MethodInfo current) + { + +#if COREFX + TypeInfo enumeratorType = null, iteratorType; +#else + Type enumeratorType = null, iteratorType; +#endif + + // try a custom enumerator + MethodInfo getEnumerator = Helpers.GetInstanceMethod(expectedType, "GetEnumerator", null); + + Type getReturnType = null; + if (getEnumerator != null) + { + getReturnType = getEnumerator.ReturnType; + iteratorType = getReturnType +#if COREFX || COREFX + .GetTypeInfo() +#endif + ; + moveNext = Helpers.GetInstanceMethod(iteratorType, "MoveNext", null); + PropertyInfo prop = Helpers.GetProperty(iteratorType, "Current", false); + current = prop == null ? null : Helpers.GetGetMethod(prop, false, false); +#if PROFILE259 + if (moveNext == null && (model.MapType(ienumeratorType).GetTypeInfo().IsAssignableFrom(iteratorType.GetTypeInfo()))) +#else + if (moveNext == null && (model.MapType(ienumeratorType).IsAssignableFrom(iteratorType))) +#endif + { + moveNext = Helpers.GetInstanceMethod(model.MapType(ienumeratorType), "MoveNext", null); + } + // fully typed + if (moveNext != null && moveNext.ReturnType == model.MapType(typeof(bool)) + && current != null && current.ReturnType == itemType) + { + return getEnumerator; + } + moveNext = current = getEnumerator = null; + } + + // try IEnumerable + Type tmp = model.MapType(typeof(System.Collections.Generic.IEnumerable<>), false); + + if (tmp != null) + { + tmp = tmp.MakeGenericType(itemType); + +#if COREFX + enumeratorType = tmp.GetTypeInfo(); +#else + enumeratorType = tmp; +#endif + } +; +#if PROFILE259 + if (enumeratorType != null && enumeratorType.GetTypeInfo().IsAssignableFrom(expectedType +#else + if (enumeratorType != null && enumeratorType.IsAssignableFrom(expectedType +#endif +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + )) + { + getEnumerator = Helpers.GetInstanceMethod(enumeratorType, "GetEnumerator"); + getReturnType = getEnumerator.ReturnType; + +#if COREFX + iteratorType = getReturnType.GetTypeInfo(); +#else + iteratorType = getReturnType; +#endif + + moveNext = Helpers.GetInstanceMethod(model.MapType(ienumeratorType), "MoveNext"); + current = Helpers.GetGetMethod(Helpers.GetProperty(iteratorType, "Current", false), false, false); + return getEnumerator; + } + // give up and fall-back to non-generic IEnumerable + enumeratorType = model.MapType(ienumerableType); + getEnumerator = Helpers.GetInstanceMethod(enumeratorType, "GetEnumerator"); + getReturnType = getEnumerator.ReturnType; + iteratorType = getReturnType +#if COREFX + .GetTypeInfo() +#endif + ; + moveNext = Helpers.GetInstanceMethod(iteratorType, "MoveNext"); + current = Helpers.GetGetMethod(Helpers.GetProperty(iteratorType, "Current", false), false, false); + return getEnumerator; + } +#if FEAT_COMPILER + protected override void EmitWrite(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom) + { + using (Compiler.Local list = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + { + MethodInfo getEnumerator = GetEnumeratorInfo(ctx.Model, out MethodInfo moveNext, out MethodInfo current); + Helpers.DebugAssert(moveNext != null); + Helpers.DebugAssert(current != null); + Helpers.DebugAssert(getEnumerator != null); + Type enumeratorType = getEnumerator.ReturnType; + bool writePacked = WritePacked; + using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType)) + using (Compiler.Local token = writePacked ? new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))) : null) + { + if (writePacked) + { + ctx.LoadValue(fieldNumber); + ctx.LoadValue((int)WireType.String); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader")); + + ctx.LoadValue(list); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem")); + ctx.StoreValue(token); + + ctx.LoadValue(fieldNumber); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("SetPackedField")); + } + + ctx.LoadAddress(list, ExpectedType); + ctx.EmitCall(getEnumerator, ExpectedType); + ctx.StoreValue(iter); + using (ctx.Using(iter)) + { + Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel(); + ctx.Branch(next, false); + + ctx.MarkLabel(body); + + ctx.LoadAddress(iter, enumeratorType); + ctx.EmitCall(current, enumeratorType); + Type itemType = Tail.ExpectedType; + if (itemType != ctx.MapType(typeof(object)) && current.ReturnType == ctx.MapType(typeof(object))) + { + ctx.CastFromObject(itemType); + } + Tail.EmitWrite(ctx, null); + + ctx.MarkLabel(@next); + ctx.LoadAddress(iter, enumeratorType); + ctx.EmitCall(moveNext, enumeratorType); + ctx.BranchIfTrue(body, false); + } + + if (writePacked) + { + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem")); + } + } + } + } +#endif + + public override void Write(object value, ProtoWriter dest) + { + SubItemToken token; + bool writePacked = WritePacked; + bool fixedSizePacked = writePacked & CanUsePackedPrefix(value) && value is ICollection; + if (writePacked) + { + ProtoWriter.WriteFieldHeader(fieldNumber, WireType.String, dest); + if (fixedSizePacked) + { + ProtoWriter.WritePackedPrefix(((ICollection)value).Count, packedWireType, dest); + token = default(SubItemToken); + } + else + { + token = ProtoWriter.StartSubItem(value, dest); + } + ProtoWriter.SetPackedField(fieldNumber, dest); + } + else + { + token = new SubItemToken(); // default + } + bool checkForNull = !SupportNull; + foreach (object subItem in (IEnumerable)value) + { + if (checkForNull && subItem == null) { throw new NullReferenceException(); } + Tail.Write(subItem, dest); + } + if (writePacked) + { + if (fixedSizePacked) + { + ProtoWriter.ClearPackedField(fieldNumber, dest); + } + else + { + ProtoWriter.EndSubItem(token, dest); + } + } + } + + private bool CanUsePackedPrefix(object obj) => + ArrayDecorator.CanUsePackedPrefix(packedWireType, Tail.ExpectedType); + + public override object Read(object value, ProtoReader source) + { + try + { + int field = source.FieldNumber; + object origValue = value; + if (value == null) value = Activator.CreateInstance(concreteType); + bool isList = IsList && !SuppressIList; + if (packedWireType != WireType.None && source.WireType == WireType.String) + { + SubItemToken token = ProtoReader.StartSubItem(source); + if (isList) + { + IList list = (IList)value; + while (ProtoReader.HasSubValue(packedWireType, source)) + { + list.Add(Tail.Read(null, source)); + } + } + else + { + object[] args = new object[1]; + while (ProtoReader.HasSubValue(packedWireType, source)) + { + args[0] = Tail.Read(null, source); + add.Invoke(value, args); + } + } + ProtoReader.EndSubItem(token, source); + } + else + { + if (isList) + { + IList list = (IList)value; + do + { + list.Add(Tail.Read(null, source)); + } while (source.TryReadFieldHeader(field)); + } + else + { + object[] args = new object[1]; + do + { + args[0] = Tail.Read(null, source); + add.Invoke(value, args); + } while (source.TryReadFieldHeader(field)); + } + } + return origValue == value ? null : value; + } + catch (TargetInvocationException tie) + { + if (tie.InnerException != null) throw tie.InnerException; + throw; + } + } + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ListDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/ListDecorator.cs.meta new file mode 100644 index 0000000..a5980a2 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ListDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb7a73aa78c887c478b0af6d506337d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/MapDecorator.cs b/Runtime/Protobuf-net/Serializers/MapDecorator.cs new file mode 100644 index 0000000..033cf26 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/MapDecorator.cs @@ -0,0 +1,298 @@ +using ProtoBuf.Meta; +using System; +#if FEAT_COMPILER +using ProtoBuf.Compiler; +#endif +using System.Collections.Generic; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + class MapDecorator : ProtoDecoratorBase where TDictionary : class, IDictionary + { + private readonly Type concreteType; + private readonly IProtoSerializer keyTail; + private readonly int fieldNumber; + private readonly WireType wireType; + + internal MapDecorator(TypeModel model, Type concreteType, IProtoSerializer keyTail, IProtoSerializer valueTail, + int fieldNumber, WireType wireType, WireType keyWireType, WireType valueWireType, bool overwriteList) + : base(DefaultValue == null + ? (IProtoSerializer)new TagDecorator(2, valueWireType, false, valueTail) + : (IProtoSerializer)new DefaultValueDecorator(model, DefaultValue, new TagDecorator(2, valueWireType, false, valueTail))) + { + this.wireType = wireType; + this.keyTail = new DefaultValueDecorator(model, DefaultKey, new TagDecorator(1, keyWireType, false, keyTail)); + this.fieldNumber = fieldNumber; + this.concreteType = concreteType ?? typeof(TDictionary); + + if (keyTail.RequiresOldValue) throw new InvalidOperationException("Key tail should not require the old value"); + if (!keyTail.ReturnsValue) throw new InvalidOperationException("Key tail should return a value"); + if (!valueTail.ReturnsValue) throw new InvalidOperationException("Value tail should return a value"); + + AppendToCollection = !overwriteList; + } + + private static readonly MethodInfo indexerSet = GetIndexerSetter(); + + private static MethodInfo GetIndexerSetter() + { +#if PROFILE259 + foreach(var prop in typeof(TDictionary).GetRuntimeProperties()) +#else + foreach (var prop in typeof(TDictionary).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) +#endif + { + if (prop.Name != "Item") continue; + if (prop.PropertyType != typeof(TValue)) continue; + + var args = prop.GetIndexParameters(); + if (args == null || args.Length != 1) continue; + + if (args[0].ParameterType != typeof(TKey)) continue; +#if PROFILE259 + var method = prop.SetMethod; +#else + var method = prop.GetSetMethod(true); +#endif + if (method != null) + { + return method; + } + } + throw new InvalidOperationException("Unable to resolve indexer for map"); + } + + private static readonly TKey DefaultKey = (typeof(TKey) == typeof(string)) ? (TKey)(object)"" : default(TKey); + private static readonly TValue DefaultValue = (typeof(TValue) == typeof(string)) ? (TValue)(object)"" : default(TValue); + public override Type ExpectedType => typeof(TDictionary); + + public override bool ReturnsValue => true; + + public override bool RequiresOldValue => AppendToCollection; + + private bool AppendToCollection { get; } + + public override object Read(object untyped, ProtoReader source) + { + TDictionary typed = AppendToCollection ? ((TDictionary)untyped) : null; + if (typed == null) typed = (TDictionary)Activator.CreateInstance(concreteType); + + do + { + var key = DefaultKey; + var value = DefaultValue; + SubItemToken token = ProtoReader.StartSubItem(source); + int field; + while ((field = source.ReadFieldHeader()) > 0) + { + switch (field) + { + case 1: + key = (TKey)keyTail.Read(null, source); + break; + case 2: + value = (TValue)Tail.Read(Tail.RequiresOldValue ? (object)value : null, source); + break; + default: + source.SkipField(); + break; + } + } + + ProtoReader.EndSubItem(token, source); + typed[key] = value; + } while (source.TryReadFieldHeader(fieldNumber)); + + return typed; + } + + public override void Write(object untyped, ProtoWriter dest) + { + foreach (var pair in (TDictionary)untyped) + { + ProtoWriter.WriteFieldHeader(fieldNumber, wireType, dest); + var token = ProtoWriter.StartSubItem(null, dest); + if (pair.Key != null) keyTail.Write(pair.Key, dest); + if (pair.Value != null) Tail.Write(pair.Value, dest); + ProtoWriter.EndSubItem(token, dest); + } + } + +#if FEAT_COMPILER + protected override void EmitWrite(CompilerContext ctx, Local valueFrom) + { + Type itemType = typeof(KeyValuePair); + MethodInfo moveNext, current, getEnumerator = ListDecorator.GetEnumeratorInfo(ctx.Model, + ExpectedType, itemType, out moveNext, out current); + Type enumeratorType = getEnumerator.ReturnType; + + MethodInfo key = itemType.GetProperty(nameof(KeyValuePair.Key)).GetGetMethod(), + @value = itemType.GetProperty(nameof(KeyValuePair.Value)).GetGetMethod(); + + using (Compiler.Local list = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType)) + using (Compiler.Local token = new Compiler.Local(ctx, typeof(SubItemToken))) + using (Compiler.Local kvp = new Compiler.Local(ctx, itemType)) + { + ctx.LoadAddress(list, ExpectedType); + ctx.EmitCall(getEnumerator, ExpectedType); + ctx.StoreValue(iter); + using (ctx.Using(iter)) + { + Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel(); + ctx.Branch(next, false); + + ctx.MarkLabel(body); + + ctx.LoadAddress(iter, enumeratorType); + ctx.EmitCall(current, enumeratorType); + + if (itemType != ctx.MapType(typeof(object)) && current.ReturnType == ctx.MapType(typeof(object))) + { + ctx.CastFromObject(itemType); + } + ctx.StoreValue(kvp); + + ctx.LoadValue(fieldNumber); + ctx.LoadValue((int)wireType); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader")); + + ctx.LoadNullRef(); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem")); + ctx.StoreValue(token); + + ctx.LoadAddress(kvp, itemType); + ctx.EmitCall(key, itemType); + ctx.WriteNullCheckedTail(typeof(TKey), keyTail, null); + + ctx.LoadAddress(kvp, itemType); + ctx.EmitCall(value, itemType); + ctx.WriteNullCheckedTail(typeof(TValue), Tail, null); + + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem")); + + ctx.MarkLabel(@next); + ctx.LoadAddress(iter, enumeratorType); + ctx.EmitCall(moveNext, enumeratorType); + ctx.BranchIfTrue(body, false); + } + } + } + protected override void EmitRead(CompilerContext ctx, Local valueFrom) + { + using (Compiler.Local list = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) + : new Compiler.Local(ctx, typeof(TDictionary))) + using (Compiler.Local token = new Compiler.Local(ctx, typeof(SubItemToken))) + using (Compiler.Local key = new Compiler.Local(ctx, typeof(TKey))) + using (Compiler.Local @value = new Compiler.Local(ctx, typeof(TValue))) + using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + if (!AppendToCollection) + { // always new + ctx.LoadNullRef(); + ctx.StoreValue(list); + } + if (concreteType != null) + { + ctx.LoadValue(list); + Compiler.CodeLabel notNull = ctx.DefineLabel(); + ctx.BranchIfTrue(notNull, true); + ctx.EmitCtor(concreteType); + ctx.StoreValue(list); + ctx.MarkLabel(notNull); + } + + var redoFromStart = ctx.DefineLabel(); + ctx.MarkLabel(redoFromStart); + + // key = default(TKey); value = default(TValue); + if (typeof(TKey) == typeof(string)) + { + ctx.LoadValue(""); + ctx.StoreValue(key); + } + else + { + ctx.InitLocal(typeof(TKey), key); + } + if (typeof(TValue) == typeof(string)) + { + ctx.LoadValue(""); + ctx.StoreValue(value); + } + else + { + ctx.InitLocal(typeof(TValue), @value); + } + + // token = ProtoReader.StartSubItem(reader); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem")); + ctx.StoreValue(token); + + Compiler.CodeLabel @continue = ctx.DefineLabel(), processField = ctx.DefineLabel(); + // while ... + ctx.Branch(@continue, false); + + // switch(fieldNumber) + ctx.MarkLabel(processField); + ctx.LoadValue(fieldNumber); + CodeLabel @default = ctx.DefineLabel(), one = ctx.DefineLabel(), two = ctx.DefineLabel(); + ctx.Switch(new[] { @default, one, two }); // zero based, hence explicit 0 + + // case 0: default: reader.SkipField(); + ctx.MarkLabel(@default); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField")); + ctx.Branch(@continue, false); + + // case 1: key = ... + ctx.MarkLabel(one); + keyTail.EmitRead(ctx, null); + ctx.StoreValue(key); + ctx.Branch(@continue, false); + + // case 2: value = ... + ctx.MarkLabel(two); + Tail.EmitRead(ctx, Tail.RequiresOldValue ? @value : null); + ctx.StoreValue(value); + + // (fieldNumber = reader.ReadFieldHeader()) > 0 + ctx.MarkLabel(@continue); + ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int))); + ctx.CopyValue(); + ctx.StoreValue(fieldNumber); + ctx.LoadValue(0); + ctx.BranchIfGreater(processField, false); + + // ProtoReader.EndSubItem(token, reader); + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem")); + + // list[key] = value; + ctx.LoadAddress(list, ExpectedType); + ctx.LoadValue(key); + ctx.LoadValue(@value); + ctx.EmitCall(indexerSet); + + // while reader.TryReadFieldReader(fieldNumber) + ctx.LoadReaderWriter(); + ctx.LoadValue(this.fieldNumber); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("TryReadFieldHeader")); + ctx.BranchIfTrue(redoFromStart, false); + + if (ReturnsValue) + { + ctx.LoadValue(list); + } + } + } +#endif + } +} diff --git a/Runtime/Protobuf-net/Serializers/MapDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/MapDecorator.cs.meta new file mode 100644 index 0000000..51c4525 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/MapDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 522b5c8a0fa5be14591bc9cbe3b194d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs b/Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs new file mode 100644 index 0000000..3ee80a5 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs @@ -0,0 +1,76 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + sealed class MemberSpecifiedDecorator : ProtoDecoratorBase + { + public override Type ExpectedType => Tail.ExpectedType; + + public override bool RequiresOldValue => Tail.RequiresOldValue; + + public override bool ReturnsValue => Tail.ReturnsValue; + + private readonly MethodInfo getSpecified, setSpecified; + public MemberSpecifiedDecorator(MethodInfo getSpecified, MethodInfo setSpecified, IProtoSerializer tail) + : base(tail) + { + if (getSpecified == null && setSpecified == null) throw new InvalidOperationException(); + this.getSpecified = getSpecified; + this.setSpecified = setSpecified; + } + + public override void Write(object value, ProtoWriter dest) + { + if (getSpecified == null || (bool)getSpecified.Invoke(value, null)) + { + Tail.Write(value, dest); + } + } + + public override object Read(object value, ProtoReader source) + { + object result = Tail.Read(value, source); + if (setSpecified != null) setSpecified.Invoke(value, new object[] { true }); + return result; + } + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + if (getSpecified == null) + { + Tail.EmitWrite(ctx, valueFrom); + return; + } + using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + { + ctx.LoadAddress(loc, ExpectedType); + ctx.EmitCall(getSpecified); + Compiler.CodeLabel done = ctx.DefineLabel(); + ctx.BranchIfFalse(done, false); + Tail.EmitWrite(ctx, loc); + ctx.MarkLabel(done); + } + + } + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + if (setSpecified == null) + { + Tail.EmitRead(ctx, valueFrom); + return; + } + using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + { + Tail.EmitRead(ctx, loc); + ctx.LoadAddress(loc, ExpectedType); + ctx.LoadValue(1); // true + ctx.EmitCall(setSpecified); + } + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta new file mode 100644 index 0000000..f2d6187 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58836f822e85e2447817d187f3bcd5de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs b/Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs new file mode 100644 index 0000000..c3d685b --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs @@ -0,0 +1,64 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class NetObjectSerializer : IProtoSerializer + { + private readonly int key; + private readonly Type type; + + private readonly BclHelpers.NetObjectOptions options; + + public NetObjectSerializer(TypeModel model, Type type, int key, BclHelpers.NetObjectOptions options) + { + bool dynamicType = (options & BclHelpers.NetObjectOptions.DynamicType) != 0; + this.key = dynamicType ? -1 : key; + this.type = dynamicType ? model.MapType(typeof(object)) : type; + this.options = options; + } + + public Type ExpectedType => type; + + public bool ReturnsValue => true; + + public bool RequiresOldValue => true; + + public object Read(object value, ProtoReader source) + { + return BclHelpers.ReadNetObject(value, source, key, type == typeof(object) ? null : type, options); + } + + public void Write(object value, ProtoWriter dest) + { + BclHelpers.WriteNetObject(value, dest, key, options); + } + +#if FEAT_COMPILER + public void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadValue(valueFrom); + ctx.CastToObject(type); + ctx.LoadReaderWriter(); + ctx.LoadValue(ctx.MapMetaKeyToCompiledKey(key)); + if (type == ctx.MapType(typeof(object))) ctx.LoadNullRef(); + else ctx.LoadValue(type); + ctx.LoadValue((int)options); + ctx.EmitCall(ctx.MapType(typeof(BclHelpers)).GetMethod("ReadNetObject")); + ctx.CastFromObject(type); + } + public void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadValue(valueFrom); + ctx.CastToObject(type); + ctx.LoadReaderWriter(); + ctx.LoadValue(ctx.MapMetaKeyToCompiledKey(key)); + ctx.LoadValue((int)options); + ctx.EmitCall(ctx.MapType(typeof(BclHelpers)).GetMethod("WriteNetObject")); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs.meta new file mode 100644 index 0000000..f53dfad --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/NetObjectSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd6edd815f76150449f39f7571679912 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/NullDecorator.cs b/Runtime/Protobuf-net/Serializers/NullDecorator.cs new file mode 100644 index 0000000..52db14c --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/NullDecorator.cs @@ -0,0 +1,167 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class NullDecorator : ProtoDecoratorBase + { + private readonly Type expectedType; + public const int Tag = 1; + public NullDecorator(TypeModel model, IProtoSerializer tail) : base(tail) + { + if (!tail.ReturnsValue) + throw new NotSupportedException("NullDecorator only supports implementations that return values"); + + Type tailType = tail.ExpectedType; + if (Helpers.IsValueType(tailType)) + { + expectedType = model.MapType(typeof(Nullable<>)).MakeGenericType(tailType); + } + else + { + expectedType = tailType; + } + } + + public override Type ExpectedType => expectedType; + + public override bool ReturnsValue => true; + + public override bool RequiresOldValue => true; + +#if FEAT_COMPILER + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + using (Compiler.Local oldValue = ctx.GetLocalWithValue(expectedType, valueFrom)) + using (Compiler.Local token = new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken)))) + using (Compiler.Local field = new Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem")); + ctx.StoreValue(token); + + Compiler.CodeLabel next = ctx.DefineLabel(), processField = ctx.DefineLabel(), end = ctx.DefineLabel(); + + ctx.MarkLabel(next); + + ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int))); + ctx.CopyValue(); + ctx.StoreValue(field); + ctx.LoadValue(Tag); // = 1 - process + ctx.BranchIfEqual(processField, true); + ctx.LoadValue(field); + ctx.LoadValue(1); // < 1 - exit + ctx.BranchIfLess(end, false); + + // default: skip + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField")); + ctx.Branch(next, true); + + // process + ctx.MarkLabel(processField); + if (Tail.RequiresOldValue) + { + if (Helpers.IsValueType(expectedType)) + { + ctx.LoadAddress(oldValue, expectedType); + ctx.EmitCall(expectedType.GetMethod("GetValueOrDefault", Helpers.EmptyTypes)); + } + else + { + ctx.LoadValue(oldValue); + } + } + Tail.EmitRead(ctx, null); + // note we demanded always returns a value + if (Helpers.IsValueType(expectedType)) + { + ctx.EmitCtor(expectedType, Tail.ExpectedType); // re-nullable it + } + ctx.StoreValue(oldValue); + ctx.Branch(next, false); + + // outro + ctx.MarkLabel(end); + + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem")); + ctx.LoadValue(oldValue); // load the old value + } + } + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + using (Compiler.Local valOrNull = ctx.GetLocalWithValue(expectedType, valueFrom)) + using (Compiler.Local token = new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken)))) + { + ctx.LoadNullRef(); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem")); + ctx.StoreValue(token); + + if (Helpers.IsValueType(expectedType)) + { + ctx.LoadAddress(valOrNull, expectedType); + ctx.LoadValue(expectedType.GetProperty("HasValue")); + } + else + { + ctx.LoadValue(valOrNull); + } + Compiler.CodeLabel @end = ctx.DefineLabel(); + ctx.BranchIfFalse(@end, false); + if (Helpers.IsValueType(expectedType)) + { + ctx.LoadAddress(valOrNull, expectedType); + ctx.EmitCall(expectedType.GetMethod("GetValueOrDefault", Helpers.EmptyTypes)); + } + else + { + ctx.LoadValue(valOrNull); + } + Tail.EmitWrite(ctx, null); + + ctx.MarkLabel(@end); + + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem")); + } + } +#endif + + public override object Read(object value, ProtoReader source) + { + SubItemToken tok = ProtoReader.StartSubItem(source); + int field; + while ((field = source.ReadFieldHeader()) > 0) + { + if (field == Tag) + { + value = Tail.Read(value, source); + } + else + { + source.SkipField(); + } + } + ProtoReader.EndSubItem(tok, source); + return value; + } + + public override void Write(object value, ProtoWriter dest) + { + SubItemToken token = ProtoWriter.StartSubItem(null, dest); + if (value != null) + { + Tail.Write(value, dest); + } + ProtoWriter.EndSubItem(token, dest); + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/NullDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/NullDecorator.cs.meta new file mode 100644 index 0000000..4fb2a35 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/NullDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4728d79a9a96bde4097e4c599266a6e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ParseableSerializer.cs b/Runtime/Protobuf-net/Serializers/ParseableSerializer.cs new file mode 100644 index 0000000..9a4bb07 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ParseableSerializer.cs @@ -0,0 +1,111 @@ +#if !NO_RUNTIME +using System; +using System.Net; +using ProtoBuf.Meta; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + sealed class ParseableSerializer : IProtoSerializer + { + private readonly MethodInfo parse; + public static ParseableSerializer TryCreate(Type type, TypeModel model) + { + if (type == null) throw new ArgumentNullException("type"); +#if PORTABLE || COREFX || PROFILE259 + MethodInfo method = null; + +#if COREFX || PROFILE259 + foreach (MethodInfo tmp in type.GetTypeInfo().GetDeclaredMethods("Parse")) +#else + foreach (MethodInfo tmp in type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)) +#endif + { + ParameterInfo[] p; + if (tmp.Name == "Parse" && tmp.IsPublic && tmp.IsStatic && tmp.DeclaringType == type && (p = tmp.GetParameters()) != null && p.Length == 1 && p[0].ParameterType == typeof(string)) + { + method = tmp; + break; + } + } +#else + MethodInfo method = type.GetMethod("Parse", + BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, + null, new Type[] { model.MapType(typeof(string)) }, null); +#endif + if (method != null && method.ReturnType == type) + { + if (Helpers.IsValueType(type)) + { + MethodInfo toString = GetCustomToString(type); + if (toString == null || toString.ReturnType != model.MapType(typeof(string))) return null; // need custom ToString, fools + } + return new ParseableSerializer(method); + } + return null; + } + private static MethodInfo GetCustomToString(Type type) + { +#if PORTABLE || COREFX || PROFILE259 + MethodInfo method = Helpers.GetInstanceMethod(type, "ToString", Helpers.EmptyTypes); + if (method == null || !method.IsPublic || method.IsStatic || method.DeclaringType != type) return null; + return method; +#else + + return type.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, Helpers.EmptyTypes, null); +#endif + } + + private ParseableSerializer(MethodInfo parse) + { + this.parse = parse; + } + + public Type ExpectedType => parse.DeclaringType; + + bool IProtoSerializer.RequiresOldValue { get { return false; } } + bool IProtoSerializer.ReturnsValue { get { return true; } } + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return parse.Invoke(null, new object[] { source.ReadString() }); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteString(value.ToString(), dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Type type = ExpectedType; + if (Helpers.IsValueType(type)) + { // note that for structs, we've already asserted that a custom ToString + // exists; no need to handle the box/callvirt scenario + + // force it to a variable if needed, so we can take the address + using (Compiler.Local loc = ctx.GetLocalWithValue(type, valueFrom)) + { + ctx.LoadAddress(loc, type); + ctx.EmitCall(GetCustomToString(type)); + } + } + else + { + ctx.EmitCall(ctx.MapType(typeof(object)).GetMethod("ToString")); + } + ctx.EmitBasicWrite("WriteString", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadString", ctx.MapType(typeof(string))); + ctx.EmitCall(parse); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ParseableSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/ParseableSerializer.cs.meta new file mode 100644 index 0000000..9ee5ec1 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ParseableSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 562e4dd519901854fba22d511f594b82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/PropertyDecorator.cs b/Runtime/Protobuf-net/Serializers/PropertyDecorator.cs new file mode 100644 index 0000000..8b0a014 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/PropertyDecorator.cs @@ -0,0 +1,167 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class PropertyDecorator : ProtoDecoratorBase + { + public override Type ExpectedType => forType; + private readonly PropertyInfo property; + private readonly Type forType; + public override bool RequiresOldValue => true; + public override bool ReturnsValue => false; + private readonly bool readOptionsWriteValue; + private readonly MethodInfo shadowSetter; + + public PropertyDecorator(TypeModel model, Type forType, PropertyInfo property, IProtoSerializer tail) : base(tail) + { + Helpers.DebugAssert(forType != null); + Helpers.DebugAssert(property != null); + this.forType = forType; + this.property = property; + SanityCheck(model, property, tail, out readOptionsWriteValue, true, true); + shadowSetter = GetShadowSetter(model, property); + } + + private static void SanityCheck(TypeModel model, PropertyInfo property, IProtoSerializer tail, out bool writeValue, bool nonPublic, bool allowInternal) + { + if (property == null) throw new ArgumentNullException("property"); + + writeValue = tail.ReturnsValue && (GetShadowSetter(model, property) != null || (property.CanWrite && Helpers.GetSetMethod(property, nonPublic, allowInternal) != null)); + if (!property.CanRead || Helpers.GetGetMethod(property, nonPublic, allowInternal) == null) + { + throw new InvalidOperationException("Cannot serialize property without a get accessor"); + } + if (!writeValue && (!tail.RequiresOldValue || Helpers.IsValueType(tail.ExpectedType))) + { // so we can't save the value, and the tail doesn't use it either... not helpful + // or: can't write the value, so the struct value will be lost + throw new InvalidOperationException("Cannot apply changes to property " + property.DeclaringType.FullName + "." + property.Name); + } + } + static MethodInfo GetShadowSetter(TypeModel model, PropertyInfo property) + { +#if COREFX + MethodInfo method = Helpers.GetInstanceMethod(property.DeclaringType.GetTypeInfo(), "Set" + property.Name, new Type[] { property.PropertyType }); +#else + +#if PROFILE259 + Type reflectedType = property.DeclaringType; +#else + Type reflectedType = property.ReflectedType; +#endif + MethodInfo method = Helpers.GetInstanceMethod(reflectedType, "Set" + property.Name, new Type[] { property.PropertyType }); +#endif + if (method == null || !method.IsPublic || method.ReturnType != model.MapType(typeof(void))) return null; + return method; + } + + public override void Write(object value, ProtoWriter dest) + { + Helpers.DebugAssert(value != null); + value = property.GetValue(value, null); + if (value != null) Tail.Write(value, dest); + } + + public override object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value != null); + + object oldVal = Tail.RequiresOldValue ? property.GetValue(value, null) : null; + object newVal = Tail.Read(oldVal, source); + if (readOptionsWriteValue && newVal != null) // if the tail returns a null, intepret that as *no assign* + { + if (shadowSetter == null) + { + property.SetValue(value, newVal, null); + } + else + { + shadowSetter.Invoke(value, new object[] { newVal }); + } + } + return null; + } + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadAddress(valueFrom, ExpectedType); + ctx.LoadValue(property); + ctx.WriteNullCheckedTail(property.PropertyType, Tail, null); + } + + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + SanityCheck(ctx.Model, property, Tail, out bool writeValue, ctx.NonPublic, ctx.AllowInternal(property)); + if (Helpers.IsValueType(ExpectedType) && valueFrom == null) + { + throw new InvalidOperationException("Attempt to mutate struct on the head of the stack; changes would be lost"); + } + + using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom)) + { + if (Tail.RequiresOldValue) + { + ctx.LoadAddress(loc, ExpectedType); // stack is: old-addr + ctx.LoadValue(property); // stack is: old-value + } + Type propertyType = property.PropertyType; + ctx.ReadNullCheckedTail(propertyType, Tail, null); // stack is [new-value] + + if (writeValue) + { + using (Compiler.Local newVal = new Compiler.Local(ctx, property.PropertyType)) + { + ctx.StoreValue(newVal); // stack is empty + + Compiler.CodeLabel allDone = new Compiler.CodeLabel(); // <=== default structs + if (!Helpers.IsValueType(propertyType)) + { // if the tail returns a null, intepret that as *no assign* + allDone = ctx.DefineLabel(); + ctx.LoadValue(newVal); // stack is: new-value + ctx.BranchIfFalse(@allDone, true); // stack is empty + } + // assign the value + ctx.LoadAddress(loc, ExpectedType); // parent-addr + ctx.LoadValue(newVal); // parent-obj|new-value + if (shadowSetter == null) + { + ctx.StoreValue(property); // empty + } + else + { + ctx.EmitCall(shadowSetter); // empty + } + if (!Helpers.IsValueType(propertyType)) + { + ctx.MarkLabel(allDone); + } + } + + } + else + { // don't want return value; drop it if anything there + // stack is [new-value] + if (Tail.ReturnsValue) { ctx.DiscardValue(); } + } + } + } +#endif + + internal static bool CanWrite(TypeModel model, MemberInfo member) + { + if (member == null) throw new ArgumentNullException(nameof(member)); + + if (member is PropertyInfo prop) + { + return prop.CanWrite || GetShadowSetter(model, prop) != null; + } + + return member is FieldInfo; // fields are always writeable; anything else: JUST SAY NO! + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/PropertyDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/PropertyDecorator.cs.meta new file mode 100644 index 0000000..5aca94d --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/PropertyDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b21fd2b2435fed4da28bd193e2ce80d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs b/Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs new file mode 100644 index 0000000..e7f2b34 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs @@ -0,0 +1,24 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + abstract class ProtoDecoratorBase : IProtoSerializer + { + public abstract Type ExpectedType { get; } + protected readonly IProtoSerializer Tail; + protected ProtoDecoratorBase(IProtoSerializer tail) { this.Tail = tail; } + public abstract bool ReturnsValue { get; } + public abstract bool RequiresOldValue { get; } + public abstract void Write(object value, ProtoWriter dest); + public abstract object Read(object value, ProtoReader source); + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) { EmitWrite(ctx, valueFrom); } + protected abstract void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom); + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) { EmitRead(ctx, valueFrom); } + protected abstract void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom); +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta b/Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta new file mode 100644 index 0000000..92acdfc --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e9afbb9465ade140ab0fcd217ea0b66 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs b/Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs new file mode 100644 index 0000000..44edef0 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs @@ -0,0 +1,90 @@ +#if !NO_RUNTIME +#if PORTABLE +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + /// + /// Manipulates with uris via reflection rather than strongly typed objects. + /// This is because in PCLs, the Uri type may not match (WinRT uses Internal/Uri, .Net uses System/Uri) + /// + sealed class ReflectedUriDecorator : ProtoDecoratorBase + { + private readonly Type expectedType; + + private readonly PropertyInfo absoluteUriProperty; + + private readonly ConstructorInfo typeConstructor; + + public ReflectedUriDecorator(Type type, ProtoBuf.Meta.TypeModel model, IProtoSerializer tail) : base(tail) + { + expectedType = type; + +#if PROFILE259 + absoluteUriProperty = expectedType.GetRuntimeProperty("AbsoluteUri"); + IEnumerable constructors = expectedType.GetTypeInfo().DeclaredConstructors; + typeConstructor = null; + foreach(ConstructorInfo constructor in constructors) + { + ParameterInfo[] parameters = constructor.GetParameters(); + ParameterInfo parameterFirst = parameters.FirstOrDefault(); + Type stringType = typeof(string); + if (parameterFirst != null && + parameterFirst.ParameterType == stringType) + { + typeConstructor = constructor; + break; + } + } +#else + absoluteUriProperty = expectedType.GetProperty("AbsoluteUri"); + typeConstructor = expectedType.GetConstructor(new Type[] { typeof(string) }); +#endif + } + public override Type ExpectedType { get { return expectedType; } } + public override bool RequiresOldValue { get { return false; } } + public override bool ReturnsValue { get { return true; } } + + public override void Write(object value, ProtoWriter dest) + { + Tail.Write(absoluteUriProperty.GetValue(value, null), dest); + } + public override object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // not expecting incoming + string s = (string)Tail.Read(null, source); + + return s.Length == 0 ? null : typeConstructor.Invoke(new object[] { s }); + } + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadValue(valueFrom); + ctx.LoadValue(absoluteUriProperty); + Tail.EmitWrite(ctx, null); + } + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Tail.EmitRead(ctx, valueFrom); + ctx.CopyValue(); + Compiler.CodeLabel @nonEmpty = ctx.DefineLabel(), @end = ctx.DefineLabel(); + ctx.LoadValue(typeof(string).GetProperty("Length")); + ctx.BranchIfTrue(@nonEmpty, true); + ctx.DiscardValue(); + ctx.LoadNullRef(); + ctx.Branch(@end, true); + ctx.MarkLabel(@nonEmpty); + ctx.EmitCtor(expectedType, ctx.MapType(typeof(string))); + ctx.MarkLabel(@end); + + } +#endif + } +} +#endif +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta new file mode 100644 index 0000000..2441cdf --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cf63470b1970d84ead637b9ee2bface +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/SByteSerializer.cs b/Runtime/Protobuf-net/Serializers/SByteSerializer.cs new file mode 100644 index 0000000..81d233e --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SByteSerializer.cs @@ -0,0 +1,45 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class SByteSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(sbyte); + + public SByteSerializer(ProtoBuf.Meta.TypeModel model) + { + + } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadSByte(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteSByte((sbyte)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteSByte", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadSByte", ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/SByteSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/SByteSerializer.cs.meta new file mode 100644 index 0000000..7d71fef --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SByteSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09de0f3e56d4834428cb8ba75929d216 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/SingleSerializer.cs b/Runtime/Protobuf-net/Serializers/SingleSerializer.cs new file mode 100644 index 0000000..c5ade13 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SingleSerializer.cs @@ -0,0 +1,45 @@ +#if !NO_RUNTIME +using System; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class SingleSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(float); + + public Type ExpectedType { get { return expectedType; } } + + public SingleSerializer(TypeModel model) + { + } + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadSingle(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteSingle((float)value, dest); + } + + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteSingle", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadSingle", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/SingleSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/SingleSerializer.cs.meta new file mode 100644 index 0000000..ee64e5a --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SingleSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b89c111cae2d81469987d208a41af4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/StringSerializer.cs b/Runtime/Protobuf-net/Serializers/StringSerializer.cs new file mode 100644 index 0000000..399b4bb --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/StringSerializer.cs @@ -0,0 +1,41 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class StringSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(string); + + public StringSerializer(ProtoBuf.Meta.TypeModel model) + { + } + + public Type ExpectedType => expectedType; + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteString((string)value, dest); + } + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadString(); + } +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteString", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadString", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/StringSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/StringSerializer.cs.meta new file mode 100644 index 0000000..697d8c2 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/StringSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80c8f3efc5f697845b352b56780105ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/SubItemSerializer.cs b/Runtime/Protobuf-net/Serializers/SubItemSerializer.cs new file mode 100644 index 0000000..58015aa --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SubItemSerializer.cs @@ -0,0 +1,138 @@ +#if !NO_RUNTIME +using System; +using ProtoBuf.Meta; + +#if FEAT_COMPILER +using System.Reflection.Emit; +#endif + +namespace ProtoBuf.Serializers +{ + sealed class SubItemSerializer : IProtoTypeSerializer + { + bool IProtoTypeSerializer.HasCallbacks(TypeModel.CallbackType callbackType) + { + return ((IProtoTypeSerializer)proxy.Serializer).HasCallbacks(callbackType); + } + + bool IProtoTypeSerializer.CanCreateInstance() + { + return ((IProtoTypeSerializer)proxy.Serializer).CanCreateInstance(); + } + +#if FEAT_COMPILER + void IProtoTypeSerializer.EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, TypeModel.CallbackType callbackType) + { + ((IProtoTypeSerializer)proxy.Serializer).EmitCallback(ctx, valueFrom, callbackType); + } + + void IProtoTypeSerializer.EmitCreateInstance(Compiler.CompilerContext ctx) + { + ((IProtoTypeSerializer)proxy.Serializer).EmitCreateInstance(ctx); + } +#endif + + void IProtoTypeSerializer.Callback(object value, TypeModel.CallbackType callbackType, SerializationContext context) + { + ((IProtoTypeSerializer)proxy.Serializer).Callback(value, callbackType, context); + } + + object IProtoTypeSerializer.CreateInstance(ProtoReader source) + { + return ((IProtoTypeSerializer)proxy.Serializer).CreateInstance(source); + } + + private readonly int key; + private readonly Type type; + private readonly ISerializerProxy proxy; + private readonly bool recursionCheck; + public SubItemSerializer(Type type, int key, ISerializerProxy proxy, bool recursionCheck) + { + this.type = type ?? throw new ArgumentNullException(nameof(type)); + this.proxy = proxy ?? throw new ArgumentNullException(nameof(proxy)); + this.key = key; + this.recursionCheck = recursionCheck; + } + + Type IProtoSerializer.ExpectedType => type; + + bool IProtoSerializer.RequiresOldValue => true; + + bool IProtoSerializer.ReturnsValue => true; + + void IProtoSerializer.Write(object value, ProtoWriter dest) + { + if (recursionCheck) + { + ProtoWriter.WriteObject(value, key, dest); + } + else + { + ProtoWriter.WriteRecursionSafeObject(value, key, dest); + } + } + + object IProtoSerializer.Read(object value, ProtoReader source) + { + return ProtoReader.ReadObject(value, key, source); + } + +#if FEAT_COMPILER + bool EmitDedicatedMethod(Compiler.CompilerContext ctx, Compiler.Local valueFrom, bool read) + { + MethodBuilder method = ctx.GetDedicatedMethod(key, read); + if (method == null) return false; + + using (Compiler.Local token = new ProtoBuf.Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken)))) + { + Type rwType = ctx.MapType(read ? typeof(ProtoReader) : typeof(ProtoWriter)); + ctx.LoadValue(valueFrom); + if (!read) // write requires the object for StartSubItem; read doesn't + { // (if recursion-check is disabled [subtypes] then null is fine too) + if (Helpers.IsValueType(type) || !recursionCheck) { ctx.LoadNullRef(); } + else { ctx.CopyValue(); } + } + ctx.LoadReaderWriter(); + ctx.EmitCall(Helpers.GetStaticMethod(rwType, "StartSubItem", + read ? new Type[] { rwType } : new Type[] { ctx.MapType(typeof(object)), rwType })); + ctx.StoreValue(token); + + // note: value already on the stack + ctx.LoadReaderWriter(); + ctx.EmitCall(method); + // handle inheritance (we will be calling the *base* version of things, + // but we expect Read to return the "type" type) + if (read && type != method.ReturnType) ctx.Cast(this.type); + ctx.LoadValue(token); + ctx.LoadReaderWriter(); + ctx.EmitCall(Helpers.GetStaticMethod(rwType, "EndSubItem", new Type[] { ctx.MapType(typeof(SubItemToken)), rwType })); + } + return true; + } + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + if (!EmitDedicatedMethod(ctx, valueFrom, false)) + { + ctx.LoadValue(valueFrom); + if (Helpers.IsValueType(type)) ctx.CastToObject(type); + ctx.LoadValue(ctx.MapMetaKeyToCompiledKey(key)); // re-map for formality, but would expect identical, else dedicated method + ctx.LoadReaderWriter(); + ctx.EmitCall(Helpers.GetStaticMethod(ctx.MapType(typeof(ProtoWriter)), recursionCheck ? "WriteObject" : "WriteRecursionSafeObject", new Type[] { ctx.MapType(typeof(object)), ctx.MapType(typeof(int)), ctx.MapType(typeof(ProtoWriter)) })); + } + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + if (!EmitDedicatedMethod(ctx, valueFrom, true)) + { + ctx.LoadValue(valueFrom); + if (Helpers.IsValueType(type)) ctx.CastToObject(type); + ctx.LoadValue(ctx.MapMetaKeyToCompiledKey(key)); // re-map for formality, but would expect identical, else dedicated method + ctx.LoadReaderWriter(); + ctx.EmitCall(Helpers.GetStaticMethod(ctx.MapType(typeof(ProtoReader)), "ReadObject")); + ctx.CastFromObject(type); + } + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/SubItemSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/SubItemSerializer.cs.meta new file mode 100644 index 0000000..aaeac6f --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SubItemSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d29a14abe6c62349930c948a6d438c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs b/Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs new file mode 100644 index 0000000..86275eb --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs @@ -0,0 +1,157 @@ +#if !NO_RUNTIME +using System; +using ProtoBuf.Meta; +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + sealed class SurrogateSerializer : IProtoTypeSerializer + { + bool IProtoTypeSerializer.HasCallbacks(ProtoBuf.Meta.TypeModel.CallbackType callbackType) { return false; } +#if FEAT_COMPILER + void IProtoTypeSerializer.EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, ProtoBuf.Meta.TypeModel.CallbackType callbackType) { } + void IProtoTypeSerializer.EmitCreateInstance(Compiler.CompilerContext ctx) { throw new NotSupportedException(); } +#endif + bool IProtoTypeSerializer.CanCreateInstance() => false; + + object IProtoTypeSerializer.CreateInstance(ProtoReader source) => throw new NotSupportedException(); + + void IProtoTypeSerializer.Callback(object value, ProtoBuf.Meta.TypeModel.CallbackType callbackType, SerializationContext context) { } + + public bool ReturnsValue => false; + + public bool RequiresOldValue => true; + + public Type ExpectedType => forType; + + private readonly Type forType, declaredType; + private readonly MethodInfo toTail, fromTail; + IProtoTypeSerializer rootTail; + + public SurrogateSerializer(TypeModel model, Type forType, Type declaredType, IProtoTypeSerializer rootTail) + { + Helpers.DebugAssert(forType != null, "forType"); + Helpers.DebugAssert(declaredType != null, "declaredType"); + Helpers.DebugAssert(rootTail != null, "rootTail"); + Helpers.DebugAssert(rootTail.RequiresOldValue, "RequiresOldValue"); + Helpers.DebugAssert(!rootTail.ReturnsValue, "ReturnsValue"); + Helpers.DebugAssert(declaredType == rootTail.ExpectedType || Helpers.IsSubclassOf(declaredType, rootTail.ExpectedType)); + this.forType = forType; + this.declaredType = declaredType; + this.rootTail = rootTail; + toTail = GetConversion(model, true); + fromTail = GetConversion(model, false); + } + private static bool HasCast(TypeModel model, Type type, Type from, Type to, out MethodInfo op) + { +#if PROFILE259 + System.Collections.Generic.List list = new System.Collections.Generic.List(); + foreach (var item in type.GetRuntimeMethods()) + { + if (item.IsStatic) list.Add(item); + } + MethodInfo[] found = list.ToArray(); +#else + const BindingFlags flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + MethodInfo[] found = type.GetMethods(flags); +#endif + ParameterInfo[] paramTypes; + Type convertAttributeType = null; + for (int i = 0; i < found.Length; i++) + { + MethodInfo m = found[i]; + if (m.ReturnType != to) continue; + paramTypes = m.GetParameters(); + if (paramTypes.Length == 1 && paramTypes[0].ParameterType == from) + { + if (convertAttributeType == null) + { + convertAttributeType = model.MapType(typeof(ProtoConverterAttribute), false); + if (convertAttributeType == null) + { // attribute isn't defined in the source assembly: stop looking + break; + } + } + if (m.IsDefined(convertAttributeType, true)) + { + op = m; + return true; + } + } + } + + for (int i = 0; i < found.Length; i++) + { + MethodInfo m = found[i]; + if ((m.Name != "op_Implicit" && m.Name != "op_Explicit") || m.ReturnType != to) + { + continue; + } + paramTypes = m.GetParameters(); + if (paramTypes.Length == 1 && paramTypes[0].ParameterType == from) + { + op = m; + return true; + } + } + op = null; + return false; + } + + public MethodInfo GetConversion(TypeModel model, bool toTail) + { + Type to = toTail ? declaredType : forType; + Type from = toTail ? forType : declaredType; + MethodInfo op; + if (HasCast(model, declaredType, from, to, out op) || HasCast(model, forType, from, to, out op)) + { + return op; + } + throw new InvalidOperationException("No suitable conversion operator found for surrogate: " + + forType.FullName + " / " + declaredType.FullName); + } + + public void Write(object value, ProtoWriter writer) + { + rootTail.Write(toTail.Invoke(null, new object[] { value }), writer); + } + + public object Read(object value, ProtoReader source) + { + // convert the incoming value + object[] args = { value }; + value = toTail.Invoke(null, args); + + // invoke the tail and convert the outgoing value + args[0] = rootTail.Read(value, source); + return fromTail.Invoke(null, args); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Helpers.DebugAssert(valueFrom != null); // don't support stack-head for this + using (Compiler.Local converted = new Compiler.Local(ctx, declaredType)) // declare/re-use local + { + ctx.LoadValue(valueFrom); // load primary onto stack + ctx.EmitCall(toTail); // static convert op, primary-to-surrogate + ctx.StoreValue(converted); // store into surrogate local + + rootTail.EmitRead(ctx, converted); // downstream processing against surrogate local + + ctx.LoadValue(converted); // load from surrogate local + ctx.EmitCall(fromTail); // static convert op, surrogate-to-primary + ctx.StoreValue(valueFrom); // store back into primary + } + } + + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadValue(valueFrom); + ctx.EmitCall(toTail); + rootTail.EmitWrite(ctx, null); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs.meta new file mode 100644 index 0000000..39cf419 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SurrogateSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 517911a690de2bd4c97e81f35104a81a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs b/Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs new file mode 100644 index 0000000..4b1656d --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs @@ -0,0 +1,46 @@ +using System; + +#if !NO_RUNTIME + +namespace ProtoBuf.Serializers +{ + sealed class SystemTypeSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(Type); + + public SystemTypeSerializer(ProtoBuf.Meta.TypeModel model) + { + + } + + public Type ExpectedType => expectedType; + + void IProtoSerializer.Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteType((Type)value, dest); + } + + object IProtoSerializer.Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadType(); + } + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteType", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadType", ExpectedType); + } +#endif + } +} + +#endif diff --git a/Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta new file mode 100644 index 0000000..4f43a1f --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3b3869287521554a816dba994928f83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/TagDecorator.cs b/Runtime/Protobuf-net/Serializers/TagDecorator.cs new file mode 100644 index 0000000..509b8a0 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TagDecorator.cs @@ -0,0 +1,108 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class TagDecorator : ProtoDecoratorBase, IProtoTypeSerializer + { + public bool HasCallbacks(TypeModel.CallbackType callbackType) + { + IProtoTypeSerializer pts = Tail as IProtoTypeSerializer; + return pts != null && pts.HasCallbacks(callbackType); + } + + public bool CanCreateInstance() + { + IProtoTypeSerializer pts = Tail as IProtoTypeSerializer; + return pts != null && pts.CanCreateInstance(); + } + + public object CreateInstance(ProtoReader source) + { + return ((IProtoTypeSerializer)Tail).CreateInstance(source); + } + + public void Callback(object value, TypeModel.CallbackType callbackType, SerializationContext context) + { + if (Tail is IProtoTypeSerializer pts) + { + pts.Callback(value, callbackType, context); + } + } + +#if FEAT_COMPILER + public void EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, TypeModel.CallbackType callbackType) + { + // we only expect this to be invoked if HasCallbacks returned true, so implicitly Tail + // **must** be of the correct type + ((IProtoTypeSerializer)Tail).EmitCallback(ctx, valueFrom, callbackType); + } + + public void EmitCreateInstance(Compiler.CompilerContext ctx) + { + ((IProtoTypeSerializer)Tail).EmitCreateInstance(ctx); + } +#endif + public override Type ExpectedType => Tail.ExpectedType; + + public TagDecorator(int fieldNumber, WireType wireType, bool strict, IProtoSerializer tail) + : base(tail) + { + this.fieldNumber = fieldNumber; + this.wireType = wireType; + this.strict = strict; + } + + public override bool RequiresOldValue => Tail.RequiresOldValue; + + public override bool ReturnsValue => Tail.ReturnsValue; + + private readonly bool strict; + private readonly int fieldNumber; + private readonly WireType wireType; + + private bool NeedsHint => ((int)wireType & ~7) != 0; + + public override object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(fieldNumber == source.FieldNumber); + if (strict) { source.Assert(wireType); } + else if (NeedsHint) { source.Hint(wireType); } + return Tail.Read(value, source); + } + + public override void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteFieldHeader(fieldNumber, wireType, dest); + Tail.Write(value, dest); + } + + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadValue((int)fieldNumber); + ctx.LoadValue((int)wireType); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader")); + Tail.EmitWrite(ctx, valueFrom); + } + + protected override void EmitRead(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom) + { + if (strict || NeedsHint) + { + ctx.LoadReaderWriter(); + ctx.LoadValue((int)wireType); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod(strict ? "Assert" : "Hint")); + } + Tail.EmitRead(ctx, valueFrom); + } +#endif + } + +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/TagDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/TagDecorator.cs.meta new file mode 100644 index 0000000..e522eda --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TagDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f807f0cbd3358f6479b78ab47c89ad48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs b/Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs new file mode 100644 index 0000000..4c8b828 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs @@ -0,0 +1,63 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class TimeSpanSerializer : IProtoSerializer + { + static readonly Type expectedType = typeof(TimeSpan); + private readonly bool wellKnown; + public TimeSpanSerializer(DataFormat dataFormat, ProtoBuf.Meta.TypeModel model) + { + + wellKnown = dataFormat == DataFormat.WellKnown; + } + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + if (wellKnown) + { + return BclHelpers.ReadDuration(source); + } + else + { + Helpers.DebugAssert(value == null); // since replaces + return BclHelpers.ReadTimeSpan(source); + } + } + + public void Write(object value, ProtoWriter dest) + { + if (wellKnown) + { + BclHelpers.WriteDuration((TimeSpan)value, dest); + } + else + { + BclHelpers.WriteTimeSpan((TimeSpan)value, dest); + } + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitWrite(ctx.MapType(typeof(BclHelpers)), + wellKnown ? nameof(BclHelpers.WriteDuration) : nameof(BclHelpers.WriteTimeSpan), valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + if (wellKnown) ctx.LoadValue(valueFrom); + ctx.EmitBasicRead(ctx.MapType(typeof(BclHelpers)), + wellKnown ? nameof(BclHelpers.ReadDuration) : nameof(BclHelpers.ReadTimeSpan), + ExpectedType); + } +#endif + + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta new file mode 100644 index 0000000..013bd6e --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88ce40638421a9d4abd295d84d1991e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/TupleSerializer.cs b/Runtime/Protobuf-net/Serializers/TupleSerializer.cs new file mode 100644 index 0000000..b6f9c69 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TupleSerializer.cs @@ -0,0 +1,339 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; +using ProtoBuf.Meta; + +namespace ProtoBuf.Serializers +{ + sealed class TupleSerializer : IProtoTypeSerializer + { + private readonly MemberInfo[] members; + private readonly ConstructorInfo ctor; + private IProtoSerializer[] tails; + public TupleSerializer(RuntimeTypeModel model, ConstructorInfo ctor, MemberInfo[] members) + { + this.ctor = ctor ?? throw new ArgumentNullException(nameof(ctor)); + this.members = members ?? throw new ArgumentNullException(nameof(members)); + this.tails = new IProtoSerializer[members.Length]; + + ParameterInfo[] parameters = ctor.GetParameters(); + for (int i = 0; i < members.Length; i++) + { + WireType wireType; + Type finalType = parameters[i].ParameterType; + + Type itemType = null, defaultType = null; + + MetaType.ResolveListTypes(model, finalType, ref itemType, ref defaultType); + Type tmp = itemType == null ? finalType : itemType; + + bool asReference = false; + int typeIndex = model.FindOrAddAuto(tmp, false, true, false); + if (typeIndex >= 0) + { + asReference = model[tmp].AsReferenceDefault; + } + IProtoSerializer tail = ValueMember.TryGetCoreSerializer(model, DataFormat.Default, tmp, out wireType, asReference, false, false, true), serializer; + if (tail == null) + { + throw new InvalidOperationException("No serializer defined for type: " + tmp.FullName); + } + + tail = new TagDecorator(i + 1, wireType, false, tail); + if (itemType == null) + { + serializer = tail; + } + else + { + if (finalType.IsArray) + { + serializer = new ArrayDecorator(model, tail, i + 1, false, wireType, finalType, false, false); + } + else + { + serializer = ListDecorator.Create(model, finalType, defaultType, tail, i + 1, false, wireType, true, false, false); + } + } + tails[i] = serializer; + } + } + public bool HasCallbacks(Meta.TypeModel.CallbackType callbackType) + { + return false; + } + +#if FEAT_COMPILER + public void EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, Meta.TypeModel.CallbackType callbackType) { } +#endif + public Type ExpectedType => ctor.DeclaringType; + + void IProtoTypeSerializer.Callback(object value, Meta.TypeModel.CallbackType callbackType, SerializationContext context) { } + object IProtoTypeSerializer.CreateInstance(ProtoReader source) { throw new NotSupportedException(); } + private object GetValue(object obj, int index) + { + PropertyInfo prop; + FieldInfo field; + + if ((prop = members[index] as PropertyInfo) != null) + { + if (obj == null) + return Helpers.IsValueType(prop.PropertyType) ? Activator.CreateInstance(prop.PropertyType) : null; + return prop.GetValue(obj, null); + } + else if ((field = members[index] as FieldInfo) != null) + { + if (obj == null) + return Helpers.IsValueType(field.FieldType) ? Activator.CreateInstance(field.FieldType) : null; + return field.GetValue(obj); + } + else + { + throw new InvalidOperationException(); + } + } + + public object Read(object value, ProtoReader source) + { + object[] values = new object[members.Length]; + bool invokeCtor = false; + if (value == null) + { + invokeCtor = true; + } + for (int i = 0; i < values.Length; i++) + values[i] = GetValue(value, i); + int field; + while ((field = source.ReadFieldHeader()) > 0) + { + invokeCtor = true; + if (field <= tails.Length) + { + IProtoSerializer tail = tails[field - 1]; + values[field - 1] = tails[field - 1].Read(tail.RequiresOldValue ? values[field - 1] : null, source); + } + else + { + source.SkipField(); + } + } + return invokeCtor ? ctor.Invoke(values) : value; + } + + public void Write(object value, ProtoWriter dest) + { + for (int i = 0; i < tails.Length; i++) + { + object val = GetValue(value, i); + if (val != null) tails[i].Write(val, dest); + } + } + + public bool RequiresOldValue => true; + + public bool ReturnsValue => false; + + Type GetMemberType(int index) + { + Type result = Helpers.GetMemberType(members[index]); + if (result == null) throw new InvalidOperationException(); + return result; + } + + bool IProtoTypeSerializer.CanCreateInstance() { return false; } + +#if FEAT_COMPILER + public void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + using (Compiler.Local loc = ctx.GetLocalWithValue(ctor.DeclaringType, valueFrom)) + { + for (int i = 0; i < tails.Length; i++) + { + Type type = GetMemberType(i); + ctx.LoadAddress(loc, ExpectedType); + if (members[i] is FieldInfo) + { + ctx.LoadValue((FieldInfo)members[i]); + } + else if (members[i] is PropertyInfo) + { + ctx.LoadValue((PropertyInfo)members[i]); + } + ctx.WriteNullCheckedTail(type, tails[i], null); + } + } + } + + void IProtoTypeSerializer.EmitCreateInstance(Compiler.CompilerContext ctx) { throw new NotSupportedException(); } + + public void EmitRead(Compiler.CompilerContext ctx, Compiler.Local incoming) + { + using (Compiler.Local objValue = ctx.GetLocalWithValue(ExpectedType, incoming)) + { + Compiler.Local[] locals = new Compiler.Local[members.Length]; + try + { + for (int i = 0; i < locals.Length; i++) + { + Type type = GetMemberType(i); + bool store = true; + locals[i] = new Compiler.Local(ctx, type); + if (!Helpers.IsValueType(ExpectedType)) + { + // value-types always read the old value + if (Helpers.IsValueType(type)) + { + switch (Helpers.GetTypeCode(type)) + { + case ProtoTypeCode.Boolean: + case ProtoTypeCode.Byte: + case ProtoTypeCode.Int16: + case ProtoTypeCode.Int32: + case ProtoTypeCode.SByte: + case ProtoTypeCode.UInt16: + case ProtoTypeCode.UInt32: + ctx.LoadValue(0); + break; + case ProtoTypeCode.Int64: + case ProtoTypeCode.UInt64: + ctx.LoadValue(0L); + break; + case ProtoTypeCode.Single: + ctx.LoadValue(0.0F); + break; + case ProtoTypeCode.Double: + ctx.LoadValue(0.0D); + break; + case ProtoTypeCode.Decimal: + ctx.LoadValue(0M); + break; + case ProtoTypeCode.Guid: + ctx.LoadValue(Guid.Empty); + break; + default: + ctx.LoadAddress(locals[i], type); + ctx.EmitCtor(type); + store = false; + break; + } + } + else + { + ctx.LoadNullRef(); + } + if (store) + { + ctx.StoreValue(locals[i]); + } + } + } + + Compiler.CodeLabel skipOld = Helpers.IsValueType(ExpectedType) + ? new Compiler.CodeLabel() + : ctx.DefineLabel(); + if (!Helpers.IsValueType(ExpectedType)) + { + ctx.LoadAddress(objValue, ExpectedType); + ctx.BranchIfFalse(skipOld, false); + } + for (int i = 0; i < members.Length; i++) + { + ctx.LoadAddress(objValue, ExpectedType); + if (members[i] is FieldInfo) + { + ctx.LoadValue((FieldInfo)members[i]); + } + else if (members[i] is PropertyInfo) + { + ctx.LoadValue((PropertyInfo)members[i]); + } + ctx.StoreValue(locals[i]); + } + + if (!Helpers.IsValueType(ExpectedType)) ctx.MarkLabel(skipOld); + + using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + Compiler.CodeLabel @continue = ctx.DefineLabel(), + processField = ctx.DefineLabel(), + notRecognised = ctx.DefineLabel(); + ctx.Branch(@continue, false); + + Compiler.CodeLabel[] handlers = new Compiler.CodeLabel[members.Length]; + for (int i = 0; i < members.Length; i++) + { + handlers[i] = ctx.DefineLabel(); + } + + ctx.MarkLabel(processField); + + ctx.LoadValue(fieldNumber); + ctx.LoadValue(1); + ctx.Subtract(); // jump-table is zero-based + ctx.Switch(handlers); + + // and the default: + ctx.Branch(notRecognised, false); + for (int i = 0; i < handlers.Length; i++) + { + ctx.MarkLabel(handlers[i]); + IProtoSerializer tail = tails[i]; + Compiler.Local oldValIfNeeded = tail.RequiresOldValue ? locals[i] : null; + ctx.ReadNullCheckedTail(locals[i].Type, tail, oldValIfNeeded); + if (tail.ReturnsValue) + { + if (Helpers.IsValueType(locals[i].Type)) + { + ctx.StoreValue(locals[i]); + } + else + { + Compiler.CodeLabel hasValue = ctx.DefineLabel(), allDone = ctx.DefineLabel(); + + ctx.CopyValue(); + ctx.BranchIfTrue(hasValue, true); // interpret null as "don't assign" + ctx.DiscardValue(); + ctx.Branch(allDone, true); + ctx.MarkLabel(hasValue); + ctx.StoreValue(locals[i]); + ctx.MarkLabel(allDone); + } + } + ctx.Branch(@continue, false); + } + + ctx.MarkLabel(notRecognised); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField")); + + ctx.MarkLabel(@continue); + ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int))); + ctx.CopyValue(); + ctx.StoreValue(fieldNumber); + ctx.LoadValue(0); + ctx.BranchIfGreater(processField, false); + } + for (int i = 0; i < locals.Length; i++) + { + ctx.LoadValue(locals[i]); + } + + ctx.EmitCtor(ctor); + ctx.StoreValue(objValue); + } + finally + { + for (int i = 0; i < locals.Length; i++) + { + if (locals[i] != null) + locals[i].Dispose(); // release for re-use + } + } + } + + } +#endif + } +} + +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/TupleSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/TupleSerializer.cs.meta new file mode 100644 index 0000000..df99bbe --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TupleSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e57d0cd813299f40a1a32236d9931a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/TypeSerializer.cs b/Runtime/Protobuf-net/Serializers/TypeSerializer.cs new file mode 100644 index 0000000..d851b47 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TypeSerializer.cs @@ -0,0 +1,798 @@ +#if !NO_RUNTIME +using System; +using ProtoBuf.Meta; +#if FEAT_COMPILER + +#endif + +using System.Reflection; + +namespace ProtoBuf.Serializers +{ + sealed class TypeSerializer : IProtoTypeSerializer + { + public bool HasCallbacks(TypeModel.CallbackType callbackType) + { + if (callbacks != null && callbacks[callbackType] != null) return true; + for (int i = 0; i < serializers.Length; i++) + { + if (serializers[i].ExpectedType != forType && ((IProtoTypeSerializer)serializers[i]).HasCallbacks(callbackType)) return true; + } + return false; + } + private readonly Type forType, constructType; +#if COREFX || PROFILE259 + private readonly TypeInfo typeInfo; +#endif + public Type ExpectedType { get { return forType; } } + private readonly IProtoSerializer[] serializers; + private readonly int[] fieldNumbers; + private readonly bool isRootType, useConstructor, isExtensible, hasConstructor; + private readonly CallbackSet callbacks; + private readonly MethodInfo[] baseCtorCallbacks; + private readonly MethodInfo factory; + public TypeSerializer(TypeModel model, Type forType, int[] fieldNumbers, IProtoSerializer[] serializers, MethodInfo[] baseCtorCallbacks, bool isRootType, bool useConstructor, CallbackSet callbacks, Type constructType, MethodInfo factory) + { + Helpers.DebugAssert(forType != null); + Helpers.DebugAssert(fieldNumbers != null); + Helpers.DebugAssert(serializers != null); + Helpers.DebugAssert(fieldNumbers.Length == serializers.Length); + + Helpers.Sort(fieldNumbers, serializers); + bool hasSubTypes = false; + for (int i = 0; i < fieldNumbers.Length; i++) + { + if (i != 0 && fieldNumbers[i] == fieldNumbers[i - 1]) throw new InvalidOperationException("Duplicate field-number detected; " + + fieldNumbers[i].ToString() + " on: " + forType.FullName); + if (!hasSubTypes && serializers[i].ExpectedType != forType) + { + hasSubTypes = true; + } + } + this.forType = forType; + this.factory = factory; +#if COREFX || PROFILE259 + this.typeInfo = forType.GetTypeInfo(); +#endif + if (constructType == null) + { + constructType = forType; + } + else + { +#if COREFX || PROFILE259 + if (!typeInfo.IsAssignableFrom(constructType.GetTypeInfo())) +#else + if (!forType.IsAssignableFrom(constructType)) +#endif + { + throw new InvalidOperationException(forType.FullName + " cannot be assigned from " + constructType.FullName); + } + } + this.constructType = constructType; + this.serializers = serializers; + this.fieldNumbers = fieldNumbers; + this.callbacks = callbacks; + this.isRootType = isRootType; + this.useConstructor = useConstructor; + + if (baseCtorCallbacks != null && baseCtorCallbacks.Length == 0) baseCtorCallbacks = null; + this.baseCtorCallbacks = baseCtorCallbacks; + + if (Helpers.GetUnderlyingType(forType) != null) + { + throw new ArgumentException("Cannot create a TypeSerializer for nullable types", "forType"); + } + +#if COREFX || PROFILE259 + if (iextensible.IsAssignableFrom(typeInfo)) + { + if (typeInfo.IsValueType || !isRootType || hasSubTypes) +#else + if (model.MapType(iextensible).IsAssignableFrom(forType)) + { + if (forType.IsValueType || !isRootType || hasSubTypes) +#endif + { + throw new NotSupportedException("IExtensible is not supported in structs or classes with inheritance"); + } + isExtensible = true; + } +#if COREFX || PROFILE259 + TypeInfo constructTypeInfo = constructType.GetTypeInfo(); + hasConstructor = !constructTypeInfo.IsAbstract && Helpers.GetConstructor(constructTypeInfo, Helpers.EmptyTypes, true) != null; +#else + hasConstructor = !constructType.IsAbstract && Helpers.GetConstructor(constructType, Helpers.EmptyTypes, true) != null; +#endif + if (constructType != forType && useConstructor && !hasConstructor) + { + throw new ArgumentException("The supplied default implementation cannot be created: " + constructType.FullName, "constructType"); + } + } +#if COREFX || PROFILE259 + private static readonly TypeInfo iextensible = typeof(IExtensible).GetTypeInfo(); +#else + private static readonly System.Type iextensible = typeof(IExtensible); +#endif + + private bool CanHaveInheritance + { + get + { +#if COREFX || PROFILE259 + return (typeInfo.IsClass || typeInfo.IsInterface) && !typeInfo.IsSealed; +#else + return (forType.IsClass || forType.IsInterface) && !forType.IsSealed; +#endif + } + } + + bool IProtoTypeSerializer.CanCreateInstance() { return true; } + + object IProtoTypeSerializer.CreateInstance(ProtoReader source) + { + return CreateInstance(source, false); + } + public void Callback(object value, TypeModel.CallbackType callbackType, SerializationContext context) + { + if (callbacks != null) InvokeCallback(callbacks[callbackType], value, context); + IProtoTypeSerializer ser = (IProtoTypeSerializer)GetMoreSpecificSerializer(value); + if (ser != null) ser.Callback(value, callbackType, context); + } + private IProtoSerializer GetMoreSpecificSerializer(object value) + { + if (!CanHaveInheritance) return null; + Type actualType = value.GetType(); + if (actualType == forType) return null; + + for (int i = 0; i < serializers.Length; i++) + { + IProtoSerializer ser = serializers[i]; + if (ser.ExpectedType != forType && Helpers.IsAssignableFrom(ser.ExpectedType, actualType)) + { + return ser; + } + } + if (actualType == constructType) return null; // needs to be last in case the default concrete type is also a known sub-type + TypeModel.ThrowUnexpectedSubtype(forType, actualType); // might throw (if not a proxy) + return null; + } + + public void Write(object value, ProtoWriter dest) + { + if (isRootType) Callback(value, TypeModel.CallbackType.BeforeSerialize, dest.Context); + // write inheritance first + IProtoSerializer next = GetMoreSpecificSerializer(value); + if (next != null) next.Write(value, dest); + + // write all actual fields + //Helpers.DebugWriteLine(">> Writing fields for " + forType.FullName); + for (int i = 0; i < serializers.Length; i++) + { + IProtoSerializer ser = serializers[i]; + if (ser.ExpectedType == forType) + { + //Helpers.DebugWriteLine(": " + ser.ToString()); + ser.Write(value, dest); + } + } + //Helpers.DebugWriteLine("<< Writing fields for " + forType.FullName); + if (isExtensible) ProtoWriter.AppendExtensionData((IExtensible)value, dest); + if (isRootType) Callback(value, TypeModel.CallbackType.AfterSerialize, dest.Context); + } + + public object Read(object value, ProtoReader source) + { + if (isRootType && value != null) { Callback(value, TypeModel.CallbackType.BeforeDeserialize, source.Context); } + int fieldNumber, lastFieldNumber = 0, lastFieldIndex = 0; + bool fieldHandled; + + //Helpers.DebugWriteLine(">> Reading fields for " + forType.FullName); + while ((fieldNumber = source.ReadFieldHeader()) > 0) + { + fieldHandled = false; + if (fieldNumber < lastFieldNumber) + { + lastFieldNumber = lastFieldIndex = 0; + } + for (int i = lastFieldIndex; i < fieldNumbers.Length; i++) + { + if (fieldNumbers[i] == fieldNumber) + { + IProtoSerializer ser = serializers[i]; + //Helpers.DebugWriteLine(": " + ser.ToString()); + Type serType = ser.ExpectedType; + if (value == null) + { + if (serType == forType) value = CreateInstance(source, true); + } + else + { + if (serType != forType && ((IProtoTypeSerializer)ser).CanCreateInstance() + && serType +#if COREFX || PROFILE259 + .GetTypeInfo() +#endif + .IsSubclassOf(value.GetType())) + { + value = ProtoReader.Merge(source, value, ((IProtoTypeSerializer)ser).CreateInstance(source)); + } + } + + if (ser.ReturnsValue) + { + value = ser.Read(value, source); + } + else + { // pop + ser.Read(value, source); + } + + lastFieldIndex = i; + lastFieldNumber = fieldNumber; + fieldHandled = true; + break; + } + } + if (!fieldHandled) + { + //Helpers.DebugWriteLine(": [" + fieldNumber + "] (unknown)"); + if (value == null) value = CreateInstance(source, true); + if (isExtensible) + { + source.AppendExtensionData((IExtensible)value); + } + else + { + source.SkipField(); + } + } + } + //Helpers.DebugWriteLine("<< Reading fields for " + forType.FullName); + if (value == null) value = CreateInstance(source, true); + if (isRootType) { Callback(value, TypeModel.CallbackType.AfterDeserialize, source.Context); } + return value; + } + + private object InvokeCallback(MethodInfo method, object obj, SerializationContext context) + { + object result = null; + object[] args; + if (method != null) + { // pass in a streaming context if one is needed, else null + bool handled; + ParameterInfo[] parameters = method.GetParameters(); + switch (parameters.Length) + { + case 0: + args = null; + handled = true; + break; + default: + args = new object[parameters.Length]; + handled = true; + for (int i = 0; i < args.Length; i++) + { + object val; + Type paramType = parameters[i].ParameterType; + if (paramType == typeof(SerializationContext)) val = context; + else if (paramType == typeof(System.Type)) val = constructType; +#if PLAT_BINARYFORMATTER + else if (paramType == typeof(System.Runtime.Serialization.StreamingContext)) val = (System.Runtime.Serialization.StreamingContext)context; +#endif + else + { + val = null; + handled = false; + } + args[i] = val; + } + break; + } + if (handled) + { + result = method.Invoke(obj, args); + } + else + { + throw Meta.CallbackSet.CreateInvalidCallbackSignature(method); + } + + } + return result; + } + object CreateInstance(ProtoReader source, bool includeLocalCallback) + { + //Helpers.DebugWriteLine("* creating : " + forType.FullName); + object obj; + if (factory != null) + { + obj = InvokeCallback(factory, null, source.Context); + } + else if (useConstructor) + { + if (!hasConstructor) TypeModel.ThrowCannotCreateInstance(constructType); +#if PROFILE259 + ConstructorInfo constructorInfo = System.Linq.Enumerable.First( + constructType.GetTypeInfo().DeclaredConstructors, c => c.GetParameters().Length == 0); + obj = constructorInfo.Invoke(new object[] {}); + +#else + obj = Activator.CreateInstance(constructType +#if !(CF || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4 || UAP) + , nonPublic: true +#endif + ); +#endif + } + else + { + obj = BclHelpers.GetUninitializedObject(constructType); + } + ProtoReader.NoteObject(obj, source); + if (baseCtorCallbacks != null) + { + for (int i = 0; i < baseCtorCallbacks.Length; i++) + { + InvokeCallback(baseCtorCallbacks[i], obj, source.Context); + } + } + if (includeLocalCallback && callbacks != null) InvokeCallback(callbacks.BeforeDeserialize, obj, source.Context); + return obj; + } + + bool IProtoSerializer.RequiresOldValue { get { return true; } } + bool IProtoSerializer.ReturnsValue { get { return false; } } // updates field directly +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Type expected = ExpectedType; + using (Compiler.Local loc = ctx.GetLocalWithValue(expected, valueFrom)) + { + // pre-callbacks + EmitCallbackIfNeeded(ctx, loc, TypeModel.CallbackType.BeforeSerialize); + + Compiler.CodeLabel startFields = ctx.DefineLabel(); + // inheritance + if (CanHaveInheritance) + { + for (int i = 0; i < serializers.Length; i++) + { + IProtoSerializer ser = serializers[i]; + Type serType = ser.ExpectedType; + if (serType != forType) + { + Compiler.CodeLabel ifMatch = ctx.DefineLabel(), nextTest = ctx.DefineLabel(); + ctx.LoadValue(loc); + ctx.TryCast(serType); + ctx.CopyValue(); + ctx.BranchIfTrue(ifMatch, true); + ctx.DiscardValue(); + ctx.Branch(nextTest, true); + ctx.MarkLabel(ifMatch); + if (Helpers.IsValueType(serType)) + { + ctx.DiscardValue(); + ctx.LoadValue(loc); + ctx.CastFromObject(serType); + } + ser.EmitWrite(ctx, null); + ctx.Branch(startFields, false); + ctx.MarkLabel(nextTest); + } + } + + + if (constructType != null && constructType != forType) + { + using (Compiler.Local actualType = new Compiler.Local(ctx, ctx.MapType(typeof(System.Type)))) + { + // would have jumped to "fields" if an expected sub-type, so two options: + // a: *exactly* that type, b: an *unexpected* type + ctx.LoadValue(loc); + ctx.EmitCall(ctx.MapType(typeof(object)).GetMethod("GetType")); + ctx.CopyValue(); + ctx.StoreValue(actualType); + ctx.LoadValue(forType); + ctx.BranchIfEqual(startFields, true); + + ctx.LoadValue(actualType); + ctx.LoadValue(constructType); + ctx.BranchIfEqual(startFields, true); + } + } + else + { + // would have jumped to "fields" if an expected sub-type, so two options: + // a: *exactly* that type, b: an *unexpected* type + ctx.LoadValue(loc); + ctx.EmitCall(ctx.MapType(typeof(object)).GetMethod("GetType")); + ctx.LoadValue(forType); + ctx.BranchIfEqual(startFields, true); + } + // unexpected, then... note that this *might* be a proxy, which + // is handled by ThrowUnexpectedSubtype + ctx.LoadValue(forType); + ctx.LoadValue(loc); + ctx.EmitCall(ctx.MapType(typeof(object)).GetMethod("GetType")); + ctx.EmitCall(ctx.MapType(typeof(TypeModel)).GetMethod("ThrowUnexpectedSubtype", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)); + + } + // fields + + ctx.MarkLabel(startFields); + for (int i = 0; i < serializers.Length; i++) + { + IProtoSerializer ser = serializers[i]; + if (ser.ExpectedType == forType) ser.EmitWrite(ctx, loc); + } + + // extension data + if (isExtensible) + { + ctx.LoadValue(loc); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("AppendExtensionData")); + } + // post-callbacks + EmitCallbackIfNeeded(ctx, loc, TypeModel.CallbackType.AfterSerialize); + } + } + static void EmitInvokeCallback(Compiler.CompilerContext ctx, MethodInfo method, bool copyValue, Type constructType, Type type) + { + if (method != null) + { + if (copyValue) ctx.CopyValue(); // assumes the target is on the stack, and that we want to *retain* it on the stack + ParameterInfo[] parameters = method.GetParameters(); + bool handled = true; + + for (int i = 0; i < parameters.Length; i++) + { + Type parameterType = parameters[i].ParameterType; + if (parameterType == ctx.MapType(typeof(SerializationContext))) + { + ctx.LoadSerializationContext(); + } + else if (parameterType == ctx.MapType(typeof(System.Type))) + { + Type tmp = constructType; + if (tmp == null) tmp = type; // no ?? in some C# profiles + ctx.LoadValue(tmp); + } +#if PLAT_BINARYFORMATTER + else if (parameterType == ctx.MapType(typeof(System.Runtime.Serialization.StreamingContext))) + { + ctx.LoadSerializationContext(); + MethodInfo op = ctx.MapType(typeof(SerializationContext)).GetMethod("op_Implicit", new Type[] { ctx.MapType(typeof(SerializationContext)) }); + if (op != null) + { // it isn't always! (framework versions, etc) + ctx.EmitCall(op); + handled = true; + } + } +#endif + else + { + handled = false; + } + } + if (handled) + { + ctx.EmitCall(method); + if (constructType != null) + { + if (method.ReturnType == ctx.MapType(typeof(object))) + { + ctx.CastFromObject(type); + } + } + } + else + { + throw Meta.CallbackSet.CreateInvalidCallbackSignature(method); + } + } + } + + private void EmitCallbackIfNeeded(Compiler.CompilerContext ctx, Compiler.Local valueFrom, TypeModel.CallbackType callbackType) + { + Helpers.DebugAssert(valueFrom != null); + if (isRootType && ((IProtoTypeSerializer)this).HasCallbacks(callbackType)) + { + ((IProtoTypeSerializer)this).EmitCallback(ctx, valueFrom, callbackType); + } + } + + void IProtoTypeSerializer.EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, TypeModel.CallbackType callbackType) + { + bool actuallyHasInheritance = false; + if (CanHaveInheritance) + { + + for (int i = 0; i < serializers.Length; i++) + { + IProtoSerializer ser = serializers[i]; + if (ser.ExpectedType != forType && ((IProtoTypeSerializer)ser).HasCallbacks(callbackType)) + { + actuallyHasInheritance = true; + } + } + } + + Helpers.DebugAssert(((IProtoTypeSerializer)this).HasCallbacks(callbackType), "Shouldn't be calling this if there is nothing to do"); + MethodInfo method = callbacks?[callbackType]; + if (method == null && !actuallyHasInheritance) + { + return; + } + ctx.LoadAddress(valueFrom, ExpectedType); + EmitInvokeCallback(ctx, method, actuallyHasInheritance, null, forType); + + if (actuallyHasInheritance) + { + Compiler.CodeLabel @break = ctx.DefineLabel(); + for (int i = 0; i < serializers.Length; i++) + { + IProtoSerializer ser = serializers[i]; + IProtoTypeSerializer typeser; + Type serType = ser.ExpectedType; + if (serType != forType && + (typeser = (IProtoTypeSerializer)ser).HasCallbacks(callbackType)) + { + Compiler.CodeLabel ifMatch = ctx.DefineLabel(), nextTest = ctx.DefineLabel(); + ctx.CopyValue(); + ctx.TryCast(serType); + ctx.CopyValue(); + ctx.BranchIfTrue(ifMatch, true); + ctx.DiscardValue(); + ctx.Branch(nextTest, false); + ctx.MarkLabel(ifMatch); + typeser.EmitCallback(ctx, null, callbackType); + ctx.Branch(@break, false); + ctx.MarkLabel(nextTest); + } + } + ctx.MarkLabel(@break); + ctx.DiscardValue(); + } + } + + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Type expected = ExpectedType; + Helpers.DebugAssert(valueFrom != null); + + using (Compiler.Local loc = ctx.GetLocalWithValue(expected, valueFrom)) + using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int)))) + { + // pre-callbacks + if (HasCallbacks(TypeModel.CallbackType.BeforeDeserialize)) + { + if (Helpers.IsValueType(ExpectedType)) + { + EmitCallbackIfNeeded(ctx, loc, TypeModel.CallbackType.BeforeDeserialize); + } + else + { // could be null + Compiler.CodeLabel callbacksDone = ctx.DefineLabel(); + ctx.LoadValue(loc); + ctx.BranchIfFalse(callbacksDone, false); + EmitCallbackIfNeeded(ctx, loc, TypeModel.CallbackType.BeforeDeserialize); + ctx.MarkLabel(callbacksDone); + } + } + + Compiler.CodeLabel @continue = ctx.DefineLabel(), processField = ctx.DefineLabel(); + ctx.Branch(@continue, false); + + ctx.MarkLabel(processField); + foreach (BasicList.Group group in BasicList.GetContiguousGroups(fieldNumbers, serializers)) + { + Compiler.CodeLabel tryNextField = ctx.DefineLabel(); + int groupItemCount = group.Items.Count; + if (groupItemCount == 1) + { + // discreet group; use an equality test + ctx.LoadValue(fieldNumber); + ctx.LoadValue(group.First); + Compiler.CodeLabel processThisField = ctx.DefineLabel(); + ctx.BranchIfEqual(processThisField, true); + ctx.Branch(tryNextField, false); + WriteFieldHandler(ctx, expected, loc, processThisField, @continue, (IProtoSerializer)group.Items[0]); + } + else + { // implement as a jump-table-based switch + ctx.LoadValue(fieldNumber); + ctx.LoadValue(group.First); + ctx.Subtract(); // jump-tables are zero-based + Compiler.CodeLabel[] jmp = new Compiler.CodeLabel[groupItemCount]; + for (int i = 0; i < groupItemCount; i++) + { + jmp[i] = ctx.DefineLabel(); + } + ctx.Switch(jmp); + // write the default... + ctx.Branch(tryNextField, false); + for (int i = 0; i < groupItemCount; i++) + { + WriteFieldHandler(ctx, expected, loc, jmp[i], @continue, (IProtoSerializer)group.Items[i]); + } + } + ctx.MarkLabel(tryNextField); + } + + EmitCreateIfNull(ctx, loc); + ctx.LoadReaderWriter(); + if (isExtensible) + { + ctx.LoadValue(loc); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("AppendExtensionData")); + } + else + { + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField")); + } + + ctx.MarkLabel(@continue); + ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int))); + ctx.CopyValue(); + ctx.StoreValue(fieldNumber); + ctx.LoadValue(0); + ctx.BranchIfGreater(processField, false); + + EmitCreateIfNull(ctx, loc); + // post-callbacks + EmitCallbackIfNeeded(ctx, loc, TypeModel.CallbackType.AfterDeserialize); + + if (valueFrom != null && !loc.IsSame(valueFrom)) + { + ctx.LoadValue(loc); + ctx.Cast(valueFrom.Type); + ctx.StoreValue(valueFrom); + } + } + } + + private void WriteFieldHandler( + Compiler.CompilerContext ctx, Type expected, Compiler.Local loc, + Compiler.CodeLabel handler, Compiler.CodeLabel @continue, IProtoSerializer serializer) + { + ctx.MarkLabel(handler); + Type serType = serializer.ExpectedType; + if (serType == forType) + { + EmitCreateIfNull(ctx, loc); + serializer.EmitRead(ctx, loc); + } + else + { + //RuntimeTypeModel rtm = (RuntimeTypeModel)ctx.Model; + if (((IProtoTypeSerializer)serializer).CanCreateInstance()) + { + Compiler.CodeLabel allDone = ctx.DefineLabel(); + + ctx.LoadValue(loc); + ctx.BranchIfFalse(allDone, false); // null is always ok + + ctx.LoadValue(loc); + ctx.TryCast(serType); + ctx.BranchIfTrue(allDone, false); // not null, but of the correct type + + // otherwise, need to convert it + ctx.LoadReaderWriter(); + ctx.LoadValue(loc); + ((IProtoTypeSerializer)serializer).EmitCreateInstance(ctx); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("Merge")); + ctx.Cast(expected); + ctx.StoreValue(loc); // Merge always returns a value + + // nothing needs doing + ctx.MarkLabel(allDone); + } + + if (Helpers.IsValueType(serType)) + { + Compiler.CodeLabel initValue = ctx.DefineLabel(); + Compiler.CodeLabel hasValue = ctx.DefineLabel(); + using (Compiler.Local emptyValue = new Compiler.Local(ctx, serType)) + { + ctx.LoadValue(loc); + ctx.BranchIfFalse(initValue, false); + + ctx.LoadValue(loc); + ctx.CastFromObject(serType); + ctx.Branch(hasValue, false); + + ctx.MarkLabel(initValue); + ctx.InitLocal(serType, emptyValue); + ctx.LoadValue(emptyValue); + + ctx.MarkLabel(hasValue); + } + } + else + { + ctx.LoadValue(loc); + ctx.Cast(serType); + } + + serializer.EmitRead(ctx, null); + + } + + if (serializer.ReturnsValue) + { // update the variable + if (Helpers.IsValueType(serType)) + { + // but box it first in case of value type + ctx.CastToObject(serType); + } + ctx.StoreValue(loc); + } + ctx.Branch(@continue, false); // "continue" + } + + void IProtoTypeSerializer.EmitCreateInstance(Compiler.CompilerContext ctx) + { + // different ways of creating a new instance + bool callNoteObject = true; + if (factory != null) + { + EmitInvokeCallback(ctx, factory, false, constructType, forType); + } + else if (!useConstructor) + { // DataContractSerializer style + ctx.LoadValue(constructType); + ctx.EmitCall(ctx.MapType(typeof(BclHelpers)).GetMethod("GetUninitializedObject")); + ctx.Cast(forType); + } + else if (Helpers.IsClass(constructType) && hasConstructor) + { // XmlSerializer style + ctx.EmitCtor(constructType); + } + else + { + ctx.LoadValue(ExpectedType); + ctx.EmitCall(ctx.MapType(typeof(TypeModel)).GetMethod("ThrowCannotCreateInstance", + BindingFlags.Static | BindingFlags.Public)); + ctx.LoadNullRef(); + callNoteObject = false; + } + if (callNoteObject) + { + // track root object creation + ctx.CopyValue(); + ctx.LoadReaderWriter(); + ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("NoteObject", + BindingFlags.Static | BindingFlags.Public)); + } + if (baseCtorCallbacks != null) + { + for (int i = 0; i < baseCtorCallbacks.Length; i++) + { + EmitInvokeCallback(ctx, baseCtorCallbacks[i], true, null, forType); + } + } + } + private void EmitCreateIfNull(Compiler.CompilerContext ctx, Compiler.Local storage) + { + Helpers.DebugAssert(storage != null); + if (!Helpers.IsValueType(ExpectedType)) + { + Compiler.CodeLabel afterNullCheck = ctx.DefineLabel(); + ctx.LoadValue(storage); + ctx.BranchIfTrue(afterNullCheck, false); + + ((IProtoTypeSerializer)this).EmitCreateInstance(ctx); + + if (callbacks != null) EmitInvokeCallback(ctx, callbacks.BeforeDeserialize, true, null, forType); + ctx.StoreValue(storage); + ctx.MarkLabel(afterNullCheck); + } + } +#endif + } + +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/TypeSerializer.cs.meta b/Runtime/Protobuf-net/Serializers/TypeSerializer.cs.meta new file mode 100644 index 0000000..0b9bc49 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/TypeSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3f577c98285d56469b3eb1c9190e174 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/UInt16Serializer.cs b/Runtime/Protobuf-net/Serializers/UInt16Serializer.cs new file mode 100644 index 0000000..ff9f89b --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UInt16Serializer.cs @@ -0,0 +1,43 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + class UInt16Serializer : IProtoSerializer + { + static readonly Type expectedType = typeof(ushort); + + public UInt16Serializer(ProtoBuf.Meta.TypeModel model) + { + } + + public virtual Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public virtual object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadUInt16(); + } + + public virtual void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteUInt16((ushort)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteUInt16", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadUInt16", ctx.MapType(typeof(ushort))); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/UInt16Serializer.cs.meta b/Runtime/Protobuf-net/Serializers/UInt16Serializer.cs.meta new file mode 100644 index 0000000..3e94120 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UInt16Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95fff5b2239c48c4cbb32346fee1be94 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/UInt32Serializer.cs b/Runtime/Protobuf-net/Serializers/UInt32Serializer.cs new file mode 100644 index 0000000..08b4f4b --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UInt32Serializer.cs @@ -0,0 +1,43 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class UInt32Serializer : IProtoSerializer + { + static readonly Type expectedType = typeof(uint); + + public UInt32Serializer(ProtoBuf.Meta.TypeModel model) + { + + } + + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadUInt32(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteUInt32((uint)value, dest); + } +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteUInt32", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadUInt32", ctx.MapType(typeof(uint))); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/UInt32Serializer.cs.meta b/Runtime/Protobuf-net/Serializers/UInt32Serializer.cs.meta new file mode 100644 index 0000000..342cdec --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UInt32Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79149f5f69e868c45a17d389322e4bb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/UInt64Serializer.cs b/Runtime/Protobuf-net/Serializers/UInt64Serializer.cs new file mode 100644 index 0000000..8577edd --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UInt64Serializer.cs @@ -0,0 +1,43 @@ +#if !NO_RUNTIME +using System; + +namespace ProtoBuf.Serializers +{ + sealed class UInt64Serializer : IProtoSerializer + { + static readonly Type expectedType = typeof(ulong); + + public UInt64Serializer(ProtoBuf.Meta.TypeModel model) + { + + } + public Type ExpectedType => expectedType; + + bool IProtoSerializer.RequiresOldValue => false; + + bool IProtoSerializer.ReturnsValue => true; + + public object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // since replaces + return source.ReadUInt64(); + } + + public void Write(object value, ProtoWriter dest) + { + ProtoWriter.WriteUInt64((ulong)value, dest); + } + +#if FEAT_COMPILER + void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicWrite("WriteUInt64", valueFrom); + } + void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.EmitBasicRead("ReadUInt64", ExpectedType); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/UInt64Serializer.cs.meta b/Runtime/Protobuf-net/Serializers/UInt64Serializer.cs.meta new file mode 100644 index 0000000..72452d9 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UInt64Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e549c20b3409c4a4dbf0e7fc25062c71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/Serializers/UriDecorator.cs b/Runtime/Protobuf-net/Serializers/UriDecorator.cs new file mode 100644 index 0000000..d34ac2d --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UriDecorator.cs @@ -0,0 +1,62 @@ +#if !NO_RUNTIME +using System; +using System.Reflection; + +#if FEAT_COMPILER +using ProtoBuf.Compiler; +#endif + +namespace ProtoBuf.Serializers +{ + sealed class UriDecorator : ProtoDecoratorBase + { + static readonly Type expectedType = typeof(Uri); + public UriDecorator(ProtoBuf.Meta.TypeModel model, IProtoSerializer tail) : base(tail) + { + + } + + public override Type ExpectedType => expectedType; + + public override bool RequiresOldValue => false; + + public override bool ReturnsValue => true; + + public override void Write(object value, ProtoWriter dest) + { + Tail.Write(((Uri)value).OriginalString, dest); + } + + public override object Read(object value, ProtoReader source) + { + Helpers.DebugAssert(value == null); // not expecting incoming + string s = (string)Tail.Read(null, source); + return s.Length == 0 ? null : new Uri(s, UriKind.RelativeOrAbsolute); + } + +#if FEAT_COMPILER + protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + ctx.LoadValue(valueFrom); + ctx.LoadValue(typeof(Uri).GetProperty("OriginalString")); + Tail.EmitWrite(ctx, null); + } + protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) + { + Tail.EmitRead(ctx, valueFrom); + ctx.CopyValue(); + Compiler.CodeLabel @nonEmpty = ctx.DefineLabel(), @end = ctx.DefineLabel(); + ctx.LoadValue(typeof(string).GetProperty("Length")); + ctx.BranchIfTrue(@nonEmpty, true); + ctx.DiscardValue(); + ctx.LoadNullRef(); + ctx.Branch(@end, true); + ctx.MarkLabel(@nonEmpty); + ctx.LoadValue((int)UriKind.RelativeOrAbsolute); + ctx.EmitCtor(ctx.MapType(typeof(Uri)), ctx.MapType(typeof(string)), ctx.MapType(typeof(UriKind))); + ctx.MarkLabel(@end); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/Serializers/UriDecorator.cs.meta b/Runtime/Protobuf-net/Serializers/UriDecorator.cs.meta new file mode 100644 index 0000000..0095ee9 --- /dev/null +++ b/Runtime/Protobuf-net/Serializers/UriDecorator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b784c432eb5cbf742b3d96161e7c8d73 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ServiceModel.meta b/Runtime/Protobuf-net/ServiceModel.meta new file mode 100644 index 0000000..36e1d95 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af34a7ba57dbd6340b8d3fa0bfdbd0a1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs new file mode 100644 index 0000000..928207e --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs @@ -0,0 +1,35 @@ +#if FEAT_SERVICEMODEL && PLAT_XMLSERIALIZER +using System; +using System.ServiceModel.Channels; +using System.ServiceModel.Description; +using System.ServiceModel.Dispatcher; + +namespace ProtoBuf.ServiceModel +{ + /// + /// Uses protocol buffer serialization on the specified operation; note that this + /// must be enabled on both the client and server. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class ProtoBehaviorAttribute : Attribute, IOperationBehavior + { + void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) + { } + + void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) + { + IOperationBehavior innerBehavior = new ProtoOperationBehavior(operationDescription); + innerBehavior.ApplyClientBehavior(operationDescription, clientOperation); + } + + void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) + { + IOperationBehavior innerBehavior = new ProtoOperationBehavior(operationDescription); + innerBehavior.ApplyDispatchBehavior(operationDescription, dispatchOperation); + } + + void IOperationBehavior.Validate(OperationDescription operationDescription) + { } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta new file mode 100644 index 0000000..1facc70 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: feda16667cbcb8248951368dfbfef6b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs new file mode 100644 index 0000000..56edb79 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs @@ -0,0 +1,29 @@ +#if FEAT_SERVICEMODEL && PLAT_XMLSERIALIZER && FEAT_SERVICECONFIGMODEL +using System; +using System.ServiceModel.Configuration; + +namespace ProtoBuf.ServiceModel +{ + /// + /// Configuration element to swap out DatatContractSerilaizer with the XmlProtoSerializer for a given endpoint. + /// + /// + public class ProtoBehaviorExtension : BehaviorExtensionElement + { + /// + /// Creates a new ProtoBehaviorExtension instance. + /// + public ProtoBehaviorExtension() { } + /// + /// Gets the type of behavior. + /// + public override Type BehaviorType => typeof(ProtoEndpointBehavior); + + /// + /// Creates a behavior extension based on the current configuration settings. + /// + /// The behavior extension. + protected override object CreateBehavior() => new ProtoEndpointBehavior(); + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta new file mode 100644 index 0000000..7850781 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c70aaa3829dd1fa45b0530efc37727f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs b/Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs new file mode 100644 index 0000000..9bcfb99 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs @@ -0,0 +1,82 @@ +#if FEAT_SERVICEMODEL && PLAT_XMLSERIALIZER +using System.ServiceModel.Description; + +namespace ProtoBuf.ServiceModel +{ + /// + /// Behavior to swap out DatatContractSerilaizer with the XmlProtoSerializer for a given endpoint. + /// + /// Add the following to the server and client app.config in the system.serviceModel section: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Configure your endpoints to have a behaviorConfiguration as follows: + /// + /// + /// + /// + /// + /// + /// + /// + /// + public class ProtoEndpointBehavior : IEndpointBehavior + { + #region IEndpointBehavior Members + + void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) + { + } + + void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) + { + ReplaceDataContractSerializerOperationBehavior(endpoint); + } + + void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) + { + ReplaceDataContractSerializerOperationBehavior(endpoint); + } + + void IEndpointBehavior.Validate(ServiceEndpoint endpoint) + { + } + + private static void ReplaceDataContractSerializerOperationBehavior(ServiceEndpoint serviceEndpoint) + { + foreach (OperationDescription operationDescription in serviceEndpoint.Contract.Operations) + { + ReplaceDataContractSerializerOperationBehavior(operationDescription); + } + } + + private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description) + { + DataContractSerializerOperationBehavior dcsOperationBehavior = description.Behaviors.Find(); + if (dcsOperationBehavior != null) + { + description.Behaviors.Remove(dcsOperationBehavior); + + ProtoOperationBehavior newBehavior = new ProtoOperationBehavior(description); + newBehavior.MaxItemsInObjectGraph = dcsOperationBehavior.MaxItemsInObjectGraph; + description.Behaviors.Add(newBehavior); + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta b/Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta new file mode 100644 index 0000000..23ab783 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6776c4cee4f69a94e9507afa458fdb50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs b/Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs new file mode 100644 index 0000000..9d5f02c --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs @@ -0,0 +1,52 @@ +#if FEAT_SERVICEMODEL && PLAT_XMLSERIALIZER +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.ServiceModel.Description; +using System.Xml; +using ProtoBuf.Meta; + +namespace ProtoBuf.ServiceModel +{ + /// + /// Describes a WCF operation behaviour that can perform protobuf serialization + /// + public sealed class ProtoOperationBehavior : DataContractSerializerOperationBehavior + { + private TypeModel model; + + /// + /// Create a new ProtoOperationBehavior instance + /// + public ProtoOperationBehavior(OperationDescription operation) : base(operation) + { +#if !NO_RUNTIME + model = RuntimeTypeModel.Default; +#endif + } + + /// + /// The type-model that should be used with this behaviour + /// + public TypeModel Model + { + get { return model; } + set + { + model = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + //public ProtoOperationBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormat) : base(operation, dataContractFormat) { } + + /// + /// Creates a protobuf serializer if possible (falling back to the default WCF serializer) + /// + public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList knownTypes) + { + if (model == null) throw new InvalidOperationException("No Model instance has been assigned to the ProtoOperationBehavior"); + return XmlProtoSerializer.TryCreate(model, type) ?? base.CreateSerializer(type, name, ns, knownTypes); + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta b/Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta new file mode 100644 index 0000000..3bd6fe4 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc6637ab509d5ba41b14e428ed365764 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs b/Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs new file mode 100644 index 0000000..23959ea --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs @@ -0,0 +1,208 @@ +#if FEAT_SERVICEMODEL && PLAT_XMLSERIALIZER +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Xml; +using ProtoBuf.Meta; + +namespace ProtoBuf.ServiceModel +{ + /// + /// An xml object serializer that can embed protobuf data in a base-64 hunk (looking like a byte[]) + /// + public sealed class XmlProtoSerializer : XmlObjectSerializer + { + private readonly TypeModel model; + private readonly int key; + private readonly bool isList, isEnum; + private readonly Type type; + internal XmlProtoSerializer(TypeModel model, int key, Type type, bool isList) + { + if (key < 0) throw new ArgumentOutOfRangeException(nameof(key)); + this.model = model ?? throw new ArgumentNullException(nameof(model)); + this.key = key; + this.isList = isList; + this.type = type ?? throw new ArgumentOutOfRangeException(nameof(type)); + this.isEnum = Helpers.IsEnum(type); + } + /// + /// Attempt to create a new serializer for the given model and type + /// + /// A new serializer instance if the type is recognised by the model; null otherwise + public static XmlProtoSerializer TryCreate(TypeModel model, Type type) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + int key = GetKey(model, ref type, out bool isList); + if (key >= 0) + { + return new XmlProtoSerializer(model, key, type, isList); + } + return null; + } + + /// + /// Creates a new serializer for the given model and type + /// + public XmlProtoSerializer(TypeModel model, Type type) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + key = GetKey(model, ref type, out isList); + this.model = model; + this.type = type; + this.isEnum = Helpers.IsEnum(type); + if (key < 0) throw new ArgumentOutOfRangeException(nameof(type), "Type not recognised by the model: " + type.FullName); + } + + static int GetKey(TypeModel model, ref Type type, out bool isList) + { + if (model != null && type != null) + { + int key = model.GetKey(ref type); + if (key >= 0) + { + isList = false; + return key; + } + Type itemType = TypeModel.GetListItemType(model, type); + if (itemType != null) + { + key = model.GetKey(ref itemType); + if (key >= 0) + { + isList = true; + return key; + } + } + } + + isList = false; + return -1; + } + + /// + /// Ends an object in the output + /// + public override void WriteEndObject(XmlDictionaryWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.WriteEndElement(); + } + + /// + /// Begins an object in the output + /// + public override void WriteStartObject(XmlDictionaryWriter writer, object graph) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.WriteStartElement(PROTO_ELEMENT); + } + + private const string PROTO_ELEMENT = "proto"; + + /// + /// Writes the body of an object in the output + /// + public override void WriteObjectContent(XmlDictionaryWriter writer, object graph) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + if (graph == null) + { + writer.WriteAttributeString("nil", "true"); + } + else + { + using (MemoryStream ms = new MemoryStream()) + { + if (isList) + { + model.Serialize(ms, graph, null); + } + else + { + using (ProtoWriter protoWriter = ProtoWriter.Create(ms, model, null)) + { + model.Serialize(key, graph, protoWriter); + } + } + byte[] buffer = ms.GetBuffer(); + writer.WriteBase64(buffer, 0, (int)ms.Length); + } + } + } + + /// + /// Indicates whether this is the start of an object we are prepared to handle + /// + public override bool IsStartObject(XmlDictionaryReader reader) + { + if (reader == null) throw new ArgumentNullException(nameof(reader)); + reader.MoveToContent(); + return reader.NodeType == XmlNodeType.Element && reader.Name == PROTO_ELEMENT; + } + + /// + /// Reads the body of an object + /// + public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName) + { + if (reader == null) throw new ArgumentNullException(nameof(reader)); + reader.MoveToContent(); + bool isSelfClosed = reader.IsEmptyElement, isNil = reader.GetAttribute("nil") == "true"; + reader.ReadStartElement(PROTO_ELEMENT); + + // explicitly null + if (isNil) + { + if (!isSelfClosed) reader.ReadEndElement(); + return null; + } + if (isSelfClosed) // no real content + { + if (isList || isEnum) + { + return model.Deserialize(Stream.Null, null, type, null); + } + ProtoReader protoReader = null; + try + { + protoReader = ProtoReader.Create(Stream.Null, model, null, ProtoReader.TO_EOF); + return model.Deserialize(key, null, protoReader); + } + finally + { + ProtoReader.Recycle(protoReader); + } + } + + object result; + Helpers.DebugAssert(reader.CanReadBinaryContent, "CanReadBinaryContent"); + using (MemoryStream ms = new MemoryStream(reader.ReadContentAsBase64())) + { + if (isList || isEnum) + { + result = model.Deserialize(ms, null, type, null); + } + else + { + ProtoReader protoReader = null; + try + { + protoReader = ProtoReader.Create(ms, model, null, ProtoReader.TO_EOF); + result = model.Deserialize(key, null, protoReader); + } + finally + { + ProtoReader.Recycle(protoReader); + } + } + } + reader.ReadEndElement(); + return result; + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta b/Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta new file mode 100644 index 0000000..d564f13 --- /dev/null +++ b/Runtime/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bca9bc75e9bb7c841b04b85204a0c9f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/SubItemToken.cs b/Runtime/Protobuf-net/SubItemToken.cs new file mode 100644 index 0000000..51f4a24 --- /dev/null +++ b/Runtime/Protobuf-net/SubItemToken.cs @@ -0,0 +1,16 @@ + +using System; + +namespace ProtoBuf +{ + /// + /// Used to hold particulars relating to nested objects. This is opaque to the caller - simply + /// give back the token you are given at the end of an object. + /// + public readonly struct SubItemToken + { + internal readonly long value64; + internal SubItemToken(int value) => value64 = value; + internal SubItemToken(long value) => value64 = value; + } +} diff --git a/Runtime/Protobuf-net/SubItemToken.cs.meta b/Runtime/Protobuf-net/SubItemToken.cs.meta new file mode 100644 index 0000000..75435a1 --- /dev/null +++ b/Runtime/Protobuf-net/SubItemToken.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbb510795b4f3fa46aeecbd4521adfc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Protobuf-net/WireType.cs b/Runtime/Protobuf-net/WireType.cs new file mode 100644 index 0000000..ab4fa20 --- /dev/null +++ b/Runtime/Protobuf-net/WireType.cs @@ -0,0 +1,50 @@ +namespace ProtoBuf +{ + /// + /// Indicates the encoding used to represent an individual value in a protobuf stream + /// + public enum WireType + { + /// + /// Represents an error condition + /// + None = -1, + + /// + /// Base-128 variant-length encoding + /// + Variant = 0, + + /// + /// Fixed-length 8-byte encoding + /// + Fixed64 = 1, + + /// + /// Length-variant-prefixed encoding + /// + String = 2, + + /// + /// Indicates the start of a group + /// + StartGroup = 3, + + /// + /// Indicates the end of a group + /// + EndGroup = 4, + + /// + /// Fixed-length 4-byte encoding + /// 10 + Fixed32 = 5, + + /// + /// This is not a formal wire-type in the "protocol buffers" spec, but + /// denotes a variant integer that should be interpreted using + /// zig-zag semantics (so -ve numbers aren't a significant overhead) + /// + SignedVariant = WireType.Variant | (1 << 3), + } +} diff --git a/Runtime/Protobuf-net/WireType.cs.meta b/Runtime/Protobuf-net/WireType.cs.meta new file mode 100644 index 0000000..2566026 --- /dev/null +++ b/Runtime/Protobuf-net/WireType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a8403cbfeff31942997d1726a909e89 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/csharp-kcp.meta b/Runtime/csharp-kcp.meta new file mode 100644 index 0000000..994a01e --- /dev/null +++ b/Runtime/csharp-kcp.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 70af0f7bda0af43e28b19c8b9bcb332c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/csharp-kcp/Plugins.meta b/Runtime/csharp-kcp/Plugins.meta new file mode 100644 index 0000000..372aaf1 --- /dev/null +++ b/Runtime/csharp-kcp/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 75050ad5678e1a14ca35aca009239c94 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll b/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll new file mode 100644 index 0000000000000000000000000000000000000000..33ee9d8c7a241d02e33f5551d4b8d75125bc8da4 GIT binary patch literal 168448 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�F;Wg+EgER?4c-WUaR;gc1q#(LB%8yp1OU^dvy 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%0DAvw%BNZL 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 a>iR$Pca5nmH1@b9|7TkKuZF&h68L|6(gno; literal 0 HcmV?d00001 diff --git a/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll.mdb b/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.dll.mdb new file mode 100644 index 0000000000000000000000000000000000000000..c71d1e96cf7941e4ce37df7ee80b79ae70a6bc8e GIT binary patch literal 119604 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 -g=aK(;}!Xm6<)f+%T{>#3a?nyopQU$IQD(U! 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℘1selmXojjfOIz zEXba>bD&%(56XuMphBn!8Uu}m#zDo{T;Iy$N|MdaS(=giPn^@jRD zeW89(f9QMk?fuXJ=m+RW=sO5Q%)-#K@KAILhGRSp!cEm&<5xO=tF2Dv&&!O$m4(JQ$ zOK2yw3;GKB8rlu*fxdyhh4w=GAWT8l-OxRd{av!5kiA#C4;lugLc^gEP#WZfMna>Y zbSMM59~up1LRnBYlmlT(wep~Rr~oR2il8wNrZ{UHR19GXv@n%g6QGIEgU~}z2~-L_ z3_Su(f+jVz3eFJ?9?S=M1-$CC)`=JBS573X$LFf?l6ZA857&-#|0{sdd zg^of0gnomLLnokrLBB&Mp;OSmp+BJ0&>84I(4Wv*=p1w&`U|q$@IT}ZT?2VQp3t>W zEyxS14b_3_LiHf~d!!AZ>mYB)2f7}*0cr^OLN`Kg&u>ZKxNS5&=b&9Xd3h+ z^b|B5ngKlxJp;{z{#QHq`1FUd1pv714I0~aW4Dd%CTSYmcG9r1ZQHhOJMSCYww?R@ z{)zkHPIu<{xL?l9+1Yb;_Kar&6Pd(hrZAOhOlJl&nZ<18Fqe7EX8{XY#A24Plw~Yu z1uI#_YSyrpb*yIt8`;EWwy>3LY-a~M*~M=5u$O)8=Ku#e#9@wblw%y{1SdJgX@Usm z3}-pVc`k5~OI+p(SGmS@Zg7)Z+~y8t(vyLVWFj+J$VxV{^DDm*s3UTalU(E`4|&N)ehN^K zLKLP5MJYycN>Gwg{6T5TP?o?QzZ`+yN_i?!kxEpi3RS5_b!t$PTGXZvb*V>v8qknN zG^PnnX-0Ee(2`cPrVVXrM|%RbMh805iOzJPEC0}q?)0E1z35FJ`qGd73}7IG7|alc zGK}GjU?ig$%^1cqj`2)jB9oZR6s9tb>C9jzvzW~s<}#1@EMOsvSj-ZZvW(@dU?rNkn3j zkd$O3Cj}`Tw zNFfSSgrXFqI3*}aDgK}|Whl#^l;bbTQ-O+9qB2#eN;RregPPQ$Hg%{=J?hhdhBTrv zO=wCpn$v=ow4ya_XiGcV^EVynNGCeeg|7TVH@eeEMhTBSjsY%vx1eZVl``6 z%R1JxfsJfpGh5ioHny{ao$O*ad)Ui9_H%%P9O5uXILa}ObApqc;xs`7bA~%1CL?#MRiAHo{5R+KMCJu3lM|={HkVGUV2}wyta#E0zRQy6}(vX&Pq$dLz$wX$d zk}QP1q$Cx;keW24B^~L>Kt?i=nJi=_8`=4l-}s#zLRG3!of_1n7PYBEUFuPv1~jA*jcGztn$esV zw4@cSX+vAu(VoBQKu0>!nJ#oC@Ltl5?)0E1z35FJ`qGd73}7IG7|alcGK}GjU?ig$ z%^1cqj`2)jB9oZR6s9tb>C9jzvzW~s<}#1@EMOsvSj-ZZvW(@dU?r + + + DotNetty.Buffers + + + + +

+ Abstract base class implementation of a + + + + + + Abstract base class for instances + + + + + + Abstract base class for implementations that wrap another + . + + + + + Returns a hex dump + of the specified buffer's sub-region. + + + + + Returns a hex dump + of the specified buffer's sub-region. + + + + + Returns a hex dump + of the specified buffer's sub-region. + + + + + Returns a hex dump + of the specified buffer's sub-region. + + + + + Calculates the hash code of the specified buffer. This method is + useful when implementing a new buffer type. + + + + + Returns the reader index of needle in haystack, or -1 if needle is not in haystack. + + + + + Returns {@code true} if and only if the two specified buffers are + identical to each other for {@code length} bytes starting at {@code aStartIndex} + index for the {@code a} buffer and {@code bStartIndex} index for the {@code b} buffer. + A more compact way to express this is: +

+ {@code a[aStartIndex : aStartIndex + length] == b[bStartIndex : bStartIndex + length]} +

+
+ + + Returns {@code true} if and only if the two specified buffers are + identical to each other as described in {@link ByteBuf#equals(Object)}. + This method is useful when implementing a new buffer type. + + + + + Compares the two specified buffers as described in {@link ByteBuf#compareTo(ByteBuf)}. + This method is useful when implementing a new buffer type. + + + + + The default implementation of . + This method is useful when implementing a new buffer type. + + + + + Read the given amount of bytes into a new {@link ByteBuf} that is allocated from the {@link ByteBufAllocator}. + + + + + Encode a string in http://en.wikipedia.org/wiki/UTF-8 and write it into reserveBytes of + a byte buffer. The reserveBytes must be computed (ie eagerly using {@link #utf8MaxBytes(string)} + or exactly with #utf8Bytes(string)}) to ensure this method not to not: for performance reasons + the index checks will be performed using just reserveBytes. + + This method returns the actual number of bytes written. + + + + Encode the given using the given into a new + which + is allocated via the . + + The to allocate {@link IByteBuffer}. + src The to encode. + charset The specified + + + + Encode the given using the given into a new + which + is allocated via the . + + The to allocate {@link IByteBuffer}. + src The to encode. + charset The specified + the extra capacity to alloc except the space for decoding. + + + + Returns a multi-line hexadecimal dump of the specified {@link ByteBuf} that is easy to read by humans. + + + + + Returns a multi-line hexadecimal dump of the specified {@link ByteBuf} that is easy to read by humans, + starting at the given {@code offset} using the given {@code length}. + + + + + Appends the prettified multi-line hexadecimal dump of the specified {@link ByteBuf} to the specified + {@link StringBuilder} that is easy to read by humans. + + + + + Appends the prettified multi-line hexadecimal dump of the specified {@link ByteBuf} to the specified + {@link StringBuilder} that is easy to read by humans, starting at the given {@code offset} using + the given {@code length}. + + + + + Toggles the endianness of the specified 64-bit long integer. + + + + + Toggles the endianness of the specified 32-bit integer. + + + + + Toggles the endianness of the specified 16-bit integer. + + + + + Default on most Windows systems + + + + + Add the given {@link IByteBuffer}. + Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuffer}. + If you need to have it increased you need to handle it by your own. + @param buffer the {@link IByteBuffer} to add + + + + + Add the given {@link IByteBuffer}s. + Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuffer}. + If you need to have it increased you need to handle it by your own. + @param buffers the {@link IByteBuffer}s to add + + + + + Add the given {@link IByteBuffer}s. + Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuffer}. + If you need to have it increased you need to handle it by your own. + @param buffers the {@link IByteBuffer}s to add + + + + + Add the given {@link IByteBuffer} on the specific index. + Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuffer}. + If you need to have it increased you need to handle it by your own. + @param cIndex the index on which the {@link IByteBuffer} will be added + @param buffer the {@link IByteBuffer} to add + + + + + Add the given {@link IByteBuffer}s on the specific index + Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuffer}. + If you need to have it increased you need to handle it by your own. + @param cIndex the index on which the {@link IByteBuffer} will be added. + @param buffers the {@link IByteBuffer}s to add + + + + + Add the given {@link ByteBuf}s on the specific index + Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuffer}. + If you need to have it increased you need to handle it by your own. + @param cIndex the index on which the {@link IByteBuffer} will be added. + @param buffers the {@link IByteBuffer}s to add + + + + + This should only be called as last operation from a method as this may adjust the underlying + array of components and so affect the index etc. + + + + + Remove the {@link IByteBuffer} from the given index. + @param cIndex the index on from which the {@link IByteBuffer} will be remove + + + + + Remove the number of {@link IByteBuffer}s starting from the given index. + @param cIndex the index on which the {@link IByteBuffer}s will be started to removed + @param numComponents the number of components to remove + + + + + Same with {@link #slice(int, int)} except that this method returns a list. + + + + + Return the current number of {@link IByteBuffer}'s that are composed in this instance + + + + + Return the max number of {@link IByteBuffer}'s that are composed in this instance + + + + + Return the index for the given offset + + + + + Return the {@link IByteBuffer} on the specified index + @param cIndex the index for which the {@link IByteBuffer} should be returned + @return buffer the {@link IByteBuffer} on the specified index + + + + + Return the {@link IByteBuffer} on the specified index + @param offset the offset for which the {@link IByteBuffer} should be returned + @return the {@link IByteBuffer} on the specified index + + + + + Return the internal {@link IByteBuffer} on the specified index. Note that updating the indexes of the returned + buffer will lead to an undefined behavior of this buffer. + @param cIndex the index for which the {@link IByteBuffer} should be returned + + + + + Return the internal {@link IByteBuffer} on the specified offset. Note that updating the indexes of the returned + buffer will lead to an undefined behavior of this buffer. + @param offset the offset for which the {@link IByteBuffer} should be returned + + + + + Consolidate the composed {@link IByteBuffer}s + + + + + Consolidate the composed {@link IByteBuffer}s + @param cIndex the index on which to start to compose + @param numComponents the number of components to compose + + + + + Discard all {@link IByteBuffer}s which are read. + + + + + + Represents an empty byte buffer + + + + + Inspired by the Netty ByteBuffer implementation + (https://github.com/netty/netty/blob/master/buffer/src/main/java/io/netty/buffer/ByteBuf.java) + Provides circular-buffer-esque security around a byte array, allowing reads and writes to occur independently. + In general, the guarantees: + /// LESS THAN OR EQUAL TO LESS THAN OR EQUAL TO + . + + + + + Expands the capacity of this buffer so long as it is less than . + + + + + The allocator who created this buffer + + + + + Sets the of this buffer + + thrown if exceeds the length of the buffer + + + + Sets the of this buffer + + + thrown if is greater than + or less than 0. + + + + + Sets both indexes + + + thrown if or exceeds + the length of the buffer + + + + + Returns true if - is greater than 0. + + + + + Is the buffer readable if and only if the buffer contains equal or more than the specified number of elements + + The number of elements we would like to read + + + + Returns true if and only if - is greater than zero. + + + + + Returns true if and only if the buffer has enough to accomodate + additional bytes. + + The number of additional elements we would like to write. + + + + Sets the and to 0. Does not erase any of the data + written into the buffer already, + but it will overwrite that data. + + + + + Marks the current in this buffer. You can reposition the current + + to the marked by calling . + The initial value of the marked is 0. + + + + + Repositions the current to the marked in this buffer. + + + is thrown if the current is less than the + marked + + + + + Marks the current in this buffer. You can reposition the current + + to the marked by calling . + The initial value of the marked is 0. + + + + + Repositions the current to the marked in this buffer. + + + is thrown if the current is greater than the + marked + + + + + Discards the bytes between the 0th index and . + It moves the bytes between and to the 0th index, + and sets and to 0 and + oldWriterIndex - oldReaderIndex respectively. + + + + + Similar to except that this method might discard + some, all, or none of read bytes depending on its internal implementation to reduce + overall memory bandwidth consumption at the cost of potentially additional memory + consumption. + + + + + Makes sure the number of is equal to or greater than + the specified value (.) If there is enough writable bytes in this buffer, + the method returns with no side effect. Otherwise, it raises an . + + The expected number of minimum writable bytes + + if + > + . + + + + + Tries to make sure the number of + is equal to or greater than the specified value. Unlike , + this method does not raise an exception but returns a code. + + the expected minimum number of writable bytes + + When + minWritableBytes > : +
    +
  • true - the capacity of the buffer is expanded to
  • +
  • false - the capacity of the buffer is unchanged
  • +
+ + + 0 if the buffer has enough writable bytes, and its capacity is unchanged. + 1 if the buffer does not have enough bytes, and its capacity is unchanged. + 2 if the buffer has enough writable bytes, and its capacity has been increased. + 3 if the buffer does not have enough bytes, but its capacity has been increased to its maximum. + +
+ + + Gets a boolean at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Gets a byte at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Gets a short at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Gets a short at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Gets an ushort at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Gets an ushort at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Gets an integer at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Gets an integer at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Gets an unsigned integer at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Gets an unsigned integer at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Gets a long integer at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Gets a long integer at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Gets a 24-bit medium integer at the specified absolute index in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 3 greater than + + + + + Gets a 24-bit medium integer at the specified absolute index in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 3 greater than + + + + + Gets an unsigned 24-bit medium integer at the specified absolute index in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 3 greater than + + + + + Gets an unsigned 24-bit medium integer at the specified absolute index in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 3 greater than + + + + + Gets a char at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Gets a float at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Gets a float at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Gets a double at the specified absolute in this buffer. + This method does not modify or + of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Gets a double at the specified absolute in this buffer + in Little Endian Byte Order. This method does not modify + or of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Transfers this buffers data to the specified buffer starting at the specified + absolute until the destination becomes non-writable. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Transfers this buffers data to the specified buffer starting at the specified + absolute until the destination becomes non-writable. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Transfers this buffers data to the specified buffer starting at the specified + absolute until the destination becomes non-writable. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Transfers this buffers data to the specified buffer starting at the specified + absolute until the destination becomes non-writable. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Transfers this buffers data to the specified buffer starting at the specified + absolute until the destination becomes non-writable. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Transfers this buffer's data to the specified stream starting at the + specified absolute index. + + + This method does not modify readerIndex or writerIndex of + this buffer. + + absolute index in this buffer to start getting bytes from + destination stream + the number of bytes to transfer + + if the specified index is less than 0 or + if index + length is greater than + this.capacity + + + + + Gets a string with the given length at the given index. + + + length the length to read + charset that should be use + the string value. + + if length is greater than readable bytes. + + + + + Sets the specified boolean at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Sets the specified byte at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 1 greater than + + + + + Sets the specified short at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Sets the specified short at the specified absolute in this buffer + in the Little Endian Byte Order. This method does not directly modify + or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Sets the specified unsigned short at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Sets the specified unsigned short at the specified absolute in this buffer + in the Little Endian Byte Order. This method does not directly modify + or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Sets the specified integer at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Sets the specified integer at the specified absolute in this buffer + in the Little Endian Byte Order. This method does not directly modify + or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Sets the specified unsigned integer at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Sets the specified unsigned integer at the specified absolute in this buffer + in the Little Endian Byte Order. This method does not directly modify or + of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Sets the specified 24-bit medium integer at the specified absolute in this buffer. + Note that the most significant byte is ignored in the specified value. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 3 greater than + + + + + Sets the specified 24-bit medium integer at the specified absolute in this buffer. + Note that the most significant byte is ignored in the specified value. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 3 greater than + + + + + Sets the specified long integer at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Sets the specified long integer at the specified absolute in this buffer + in the Little Endian Byte Order. This method does not directly modify or + of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Sets the specified UTF-16 char at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 2 greater than + + + + + Sets the specified double at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 8 greater than + + + + + Sets the specified float at the specified absolute in this buffer. + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Sets the specified float at the specified absolute in this buffer + in Little Endian Byte Order. This method does not directly modify + or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Sets the specified float at the specified absolute in this buffer + in Little Endian Byte Order. This method does not directly modify + or of this buffer. + + + if the specified is less than 0 or + index + 4 greater than + + + + + Transfers the byte buffer's contents starting at the specified absolute . + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + + .ReadableBytes greater than + + + + + Transfers the byte buffer's contents starting at the specified absolute . + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + is less than 0 or + + greater than + + + + + Transfers the byte buffer's contents starting at the specified absolute . + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + is less than 0 or + is less than 0 or + + greater than or + + greater than .Capacity + + + + + Transfers the byte buffer's contents starting at the specified absolute . + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + + .Length greater than + + + + + Transfers the byte buffer's contents starting at the specified absolute . + This method does not directly modify or of this buffer. + + + if the specified is less than 0 or + is less than 0 or + is less than 0 or + + greater than or + + greater than .Length + + + + + Transfers the content of the specified source stream to this buffer + starting at the specified absolute . + This method does not modify or of + this buffer. + + absolute index in this byte buffer to start writing to + + number of bytes to transfer + cancellation token + the actual number of bytes read in from the specified channel. + + if the specified index is less than 0 or + if index + length is greater than this.capacity + + + + + Fills this buffer with NULL (0x00) starting at the specified + absolute index. This method does not modify reader index + or writer index of this buffer + + absolute index in this byte buffer to start writing to + length the number of NULs to write to the buffer + + if the specified index is less than 0 or if index + length + is greater than capacity. + + + + + Writes the specified string at the current writer index and increases + the writer index by the written bytes. + + Index on which the string should be written + The string value. + Encoding that should be used. + The written number of bytes. + + if writable bytes is not large enough to write the whole string. + + + + + Gets a boolean at the current and increases the + by 1 in this buffer. + + if is less than 1 + + + + Gets a byte at the current and increases the + by 1 in this buffer. + + if is less than 1 + + + + Gets a short at the current and increases the + by 2 in this buffer. + + if is less than 2 + + + + Gets a short at the current in the Little Endian Byte Order and increases + the by 2 in this buffer. + + if is less than 2 + + + + Gets a 24-bit medium integer at the current and increases the + by 3 in this buffer. + + if is less than 3 + + + + Gets a 24-bit medium integer at the current in the Little Endian Byte Order and + increases the by 3 in this buffer. + + if is less than 3 + + + + Gets an unsigned 24-bit medium integer at the current and increases the + by 3 in this buffer. + + if is less than 3 + + + + Gets an unsigned 24-bit medium integer at the current in the Little Endian Byte Order + and increases the by 3 in this buffer. + + if is less than 3 + + + + Gets an unsigned short at the current and increases the + by 2 in this buffer. + + if is less than 2 + + + + Gets an unsigned short at the current in the Little Endian Byte Order and + increases the by 2 in this buffer. + + if is less than 2 + + + + Gets an integer at the current and increases the + by 4 in this buffer. + + if is less than 4 + + + + Gets an integer at the current in the Little Endian Byte Order and increases + the by 4 in this buffer. + + if is less than 4 + + + + Gets an unsigned integer at the current and increases the + by 4 in this buffer. + + if is less than 4 + + + + Gets an unsigned integer at the current in the Little Endian Byte Order and + increases the by 4 in this buffer. + + if is less than 4 + + + + Gets an long at the current and increases the + by 8 in this buffer. + + if is less than 4 + + + + Gets an long at the current in the Little Endian Byte Order and + increases the by 8 in this buffer. + + if is less than 4 + + + + Gets a 2-byte UTF-16 character at the current and increases the + + by 2 in this buffer. + + if is less than 2 + + + + Gets an 8-byte Decimaling integer at the current and increases the + + by 8 in this buffer. + + if is less than 8 + + + + Gets an 8-byte Decimaling integer at the current and increases the + by 8 in this buffer in Little Endian Byte Order. + + if is less than 8 + + + + Gets an 4-byte Decimaling integer at the current and increases the + + by 4 in this buffer. + + if is less than 4 + + + + Gets an 4-byte Decimaling integer at the current and increases the + by 4 in this buffer in Little Endian Byte Order. + + if is less than 4 + + + + Reads bytes from this buffer into a new destination buffer. + + + if is less than + + + + + Transfers bytes from this buffer's data into the specified destination buffer + starting at the current until the destination becomes + non-writable and increases the by the number of transferred bytes. + + + if destination. is greater than + . + + + + + Gets a string with the given length at the current reader index + and increases the reader index by the given length. + + The length to read + Encoding that should be used + The string value + + + + Increases the current by the specified in this buffer. + + if is greater than . + + + + Returns the maximum of that this buffer holds. Note that + + or might return a less number of s of + . + + + -1 if this buffer cannot represent its content as of . + the number of the underlying s if this buffer has at least one underlying segment. + Note that this method does not return 0 to avoid confusion. + + + + + + + + + Exposes this buffer's readable bytes as an of . Returned segment + shares the content with this buffer. This method is identical + to buf.GetIoBuffer(buf.ReaderIndex, buf.ReadableBytes). This method does not + modify or of this buffer. Please note that the + returned segment will not see the changes of this buffer if this buffer is a dynamic + buffer and it adjusted its capacity. + + + if this buffer cannot represent its content as + of + + + + + + + + Exposes this buffer's sub-region as an of . Returned segment + shares the content with this buffer. This method does not + modify or of this buffer. Please note that the + returned segment will not see the changes of this buffer if this buffer is a dynamic + buffer and it adjusted its capacity. + + + if this buffer cannot represent its content as + of + + + + + + + + Exposes this buffer's readable bytes as an array of of . Returned + segments + share the content with this buffer. This method does not + modify or of this buffer. Please note that + returned segments will not see the changes of this buffer if this buffer is a dynamic + buffer and it adjusted its capacity. + + + if this buffer cannot represent its content with + of + + + + + + + + Exposes this buffer's bytes as an array of of for the specified + index and length. + Returned segments share the content with this buffer. This method does + not modify or of this buffer. Please note that + returned segments will not see the changes of this buffer if this buffer is a dynamic + buffer and it adjusted its capacity. + + + if this buffer cannot represent its content with + of + + + + + + + + Flag that indicates if this is backed by a byte array or not + + + + + Grabs the underlying byte array for this buffer + + + + + Returns {@code true} if and only if this buffer has a reference to the low-level memory address that points + to the backing data. + + + + + Returns the low-level memory address that point to the first byte of ths backing data. + + The low-level memory address + + + + Returns the pointer address of the buffer if the memory is pinned. + + IntPtr.Zero if not pinned. + + + + Creates a deep clone of the existing byte array and returns it + + + + + Unwraps a nested buffer + + + + + Returns a copy of this buffer's readable bytes. Modifying the content of the + returned buffer or this buffer does not affect each other at all.This method is + identical to {@code buf.copy(buf.readerIndex(), buf.readableBytes())}. + This method does not modify {@code readerIndex} or {@code writerIndex} of this buffer. + + + + + Iterates over the readable bytes of this buffer with the specified processor in ascending order. + + + -1 if the processor iterated to or beyond the end of the readable bytes. + The last-visited index If the returned false. + + Processor. + + + + Iterates over the specified area of this buffer with the specified in ascending order. + (i.e. , (index + 1), .. (index + length - 1)) + + + -1 if the processor iterated to or beyond the end of the specified area. + The last-visited index If the returned false. + + Index. + Length. + Processor. + + + + Iterates over the readable bytes of this buffer with the specified in descending order. + + + -1 if the processor iterated to or beyond the beginning of the readable bytes. + The last-visited index If the returned false. + + Processor. + + + + Iterates over the specified area of this buffer with the specified in descending order. + (i.e. (index + length - 1), (index + length - 2), ... ) + + + -1 if the processor iterated to or beyond the beginning of the specified area. + The last-visited index If the returned false. + + Index. + Length. + Processor. + + + + Thread-safe interface for allocating /. + + + + + Returns the number of bytes of heap memory used by a {@link ByteBufAllocator} or {@code -1} if unknown. + + + + + Returns the number of bytes of direct memory used by a {@link ByteBufAllocator} or {@code -1} if unknown. + + + + + Returns a for a + + + + + Return the data which is held by this {@link ByteBufHolder}. + + + + + Create a deep copy of this {@link ByteBufHolder}. + + + + + Duplicate the {@link ByteBufHolder}. Be aware that this will not automatically call {@link #retain()}. + + + + + Duplicates this {@link ByteBufHolder}. This method returns a retained duplicate unlike {@link #duplicate()}. + + + + + Returns a new {@link ByteBufHolder} which contains the specified {@code content}. + + + + Returns the number of thread caches backed by this arena. + + + Returns the number of tiny sub-pages for the arena. + + + Returns the number of small sub-pages for the arena. + + + Returns the number of chunk lists for the arena. + + + Returns an unmodifiable {@link List} which holds {@link PoolSubpageMetric}s for tiny sub-pages. + + + Returns an unmodifiable {@link List} which holds {@link PoolSubpageMetric}s for small sub-pages. + + + Returns an unmodifiable {@link List} which holds {@link PoolChunkListMetric}s. + + + Return the number of allocations done via the arena. This includes all sizes. + + + Return the number of tiny allocations done via the arena. + + + Return the number of small allocations done via the arena. + + + Return the number of normal allocations done via the arena. + + + Return the number of huge allocations done via the arena. + + + Return the number of deallocations done via the arena. This includes all sizes. + + + Return the number of tiny deallocations done via the arena. + + + Return the number of small deallocations done via the arena. + + + Return the number of normal deallocations done via the arena. + + + Return the number of huge deallocations done via the arena. + + + Return the number of currently active allocations. + + + Return the number of currently active tiny allocations. + + + Return the number of currently active small allocations. + + + Return the number of currently active normal allocations. + + + Return the number of currently active huge allocations. + + + Return the number of active bytes that are currently allocated by the arena. + + + Return the minimum usage of the chunk list before which chunks are promoted to the previous list. + + + Return the maximum usage of the chunk list after which chunks are promoted to the next list. + + + Return the percentage of the current usage of the chunk. + + + Return the size of the chunk in bytes, this is the maximum of bytes that can be served out of the chunk. + + + Return the number of free bytes in the chunk. + + + Return the number of maximal elements that can be allocated out of the sub-page. + + + Return the number of available elements to be allocated. + + + Return the size (in bytes) of the elements that will be allocated. + + + Return the size (in bytes) of this page. + + + + Description of algorithm for PageRun/PoolSubpage allocation from PoolChunk + Notation: The following terms are important to understand the code + > page - a page is the smallest unit of memory chunk that can be allocated + > chunk - a chunk is a collection of pages + > in this code chunkSize = 2^{maxOrder} /// pageSize + To begin we allocate a byte array of size = chunkSize + Whenever a ByteBuf of given size needs to be created we search for the first position + in the byte array that has enough empty space to accommodate the requested size and + return a (long) handle that encodes this offset information, (this memory segment is then + marked as reserved so it is always used by exactly one ByteBuf and no more) + For simplicity all sizes are normalized according to PoolArena#normalizeCapacity method + This ensures that when we request for memory segments of size >= pageSize the normalizedCapacity + equals the next nearest power of 2 + To search for the first offset in chunk that has at least requested size available we construct a + complete balanced binary tree and store it in an array (just like heaps) - memoryMap + The tree looks like this (the size of each node being mentioned in the parenthesis) + depth=0 1 node (chunkSize) + depth=1 2 nodes (chunkSize/2) + .. + .. + depth=d 2^d nodes (chunkSize/2^d) + .. + depth=maxOrder 2^maxOrder nodes (chunkSize/2^{maxOrder} = pageSize) + depth=maxOrder is the last level and the leafs consist of pages + With this tree available searching in chunkArray translates like this: + To allocate a memory segment of size chunkSize/2^k we search for the first node (from left) at height k + which is unused + Algorithm: + ---------- + Encode the tree in memoryMap with the notation + memoryMap[id] = x => in the subtree rooted at id, the first node that is free to be allocated + is at depth x (counted from depth=0) i.e., at depths [depth_of_id, x), there is no node that is free + As we allocate and free nodes, we update values stored in memoryMap so that the property is maintained + Initialization - + In the beginning we construct the memoryMap array by storing the depth of a node at each node + i.e., memoryMap[id] = depth_of_id + Observations: + ------------- + 1) memoryMap[id] = depth_of_id => it is free / unallocated + 2) memoryMap[id] > depth_of_id => at least one of its child nodes is allocated, so we cannot allocate it, but + some of its children can still be allocated based on their availability + 3) memoryMap[id] = maxOrder + 1 => the node is fully allocated and thus none of its children can be allocated, it + is thus marked as unusable + Algorithm: [allocateNode(d) => we want to find the first node (from left) at height h that can be allocated] + ---------- + 1) start at root (i.e., depth = 0 or id = 1) + 2) if memoryMap[1] > d => cannot be allocated from this chunk + 3) if left node value <= h; we can allocate from left subtree so move to left and repeat until found + 4) else try in right subtree + Algorithm: [allocateRun(size)] + ---------- + 1) Compute d = log_2(chunkSize/size) + 2) Return allocateNode(d) + Algorithm: [allocateSubpage(size)] + ---------- + 1) use allocateNode(maxOrder) to find an empty (i.e., unused) leaf (i.e., page) + 2) use this handle to construct the PoolSubpage object or if it already exists just call init(normCapacity) + note that this PoolSubpage object is added to subpagesPool in the PoolArena when we init() it + Note: + ----- + In the implementation for improving cache coherence, + we store 2 pieces of information (i.e, 2 byte vals) as a short value in memoryMap + memoryMap[id]= (depth_of_id, x) + where as per convention defined above + the second value (i.e, x) indicates that the first node which is free to be allocated is at depth x (from root) + + + + Used to determine if the requested capacity is equal to or greater than pageSize. + + + Used to mark memory as unusable + + + Creates a special chunk that is not pooled. + + + Update method used by allocate + This is triggered only when a successor is allocated and all its predecessors + need to update their state + The minimal depth at which subtree rooted at id has some free space + + @param id id + + + Update method used by free + This needs to handle the special case when both children are completely free + in which case parent be directly allocated on request of size = child-size * 2 + + @param id id + + + Algorithm to allocate an index in memoryMap when we query for a free node + at depth d + + @param d depth + @return index in memoryMap + + + Allocate a run of pages (>=1) + + @param normCapacity normalized capacity + @return index in memoryMap + + + Create/ initialize a new PoolSubpage of normCapacity + Any PoolSubpage created/ initialized here is added to subpage pool in the PoolArena that owns this PoolChunk + + @param normCapacity normalized capacity + @return index in memoryMap + + + Free a subpage or a run of pages + When a subpage is freed from PoolSubpage, it might be added back to subpage pool of the owning PoolArena + If the subpage pool in PoolArena has at least one other PoolSubpage of given elemSize, we can + completely free the owning Page so it is available for subsequent allocations + + @param handle handle to free + + + represents the size in #bytes supported by node 'id' in the tree + + + Calculates the maximum capacity of a buffer that will ever be possible to allocate out of the {@link PoolChunk}s + that belong to the {@link PoolChunkList} with the given {@code minUsage} and {@code maxUsage} settings. + + + Moves the {@link PoolChunk} down the {@link PoolChunkList} linked-list so it will end up in the right + {@link PoolChunkList} that has the correct minUsage / maxUsage in respect to {@link PoolChunk#usage()}. + + + Adds the {@link PoolChunk} to this {@link PoolChunkList}. + + + Method must be called before reuse this {@link PooledByteBufAllocator} + + + Returns the status of the allocator (which contains all metrics) as string. Be aware this may be expensive + and so should not called too frequently. + + + Special constructor that creates a linked list head + + + Returns the bitmap index of the subpage allocation. + + + @return {@code true} if this subpage is in use. + {@code false} if this subpage is not used by its chunk and thus it's OK to be released. + + + + Acts a Thread cache for allocations. This implementation is moduled after + jemalloc and the descripted + technics of + + Scalable + memory allocation using jemalloc + + . + + + + Try to allocate a tiny buffer out of the cache. Returns {@code true} if successful {@code false} otherwise + + + Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise + + + Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise + + + Add {@link PoolChunk} and {@code handle} to the cache if there is enough room. + Returns {@code true} if it fit into the cache {@code false} otherwise. + + + Should be called if the Thread that uses this cache is about to exist to release resources out of the cache + + + Cache used for buffers which are backed by TINY or SMALL size. + + + Cache used for buffers which are backed by NORMAL size. + + + Init the {@link PooledByteBuffer} using the provided chunk and handle with the capacity restrictions. + + + Add to cache if not already full. + + + Allocate something out of the cache if possible and remove the entry from the cache. + + + Clear out this cache and free up all previous cached {@link PoolChunk}s and {@code handle}s. + + + Free up cached {@link PoolChunk}s if not allocated frequently enough. + + + + Utility class for managing and creating unpooled buffers + + + + + Creates a new big-endian buffer which wraps the specified array. + A modification on the specified array's content will be visible to the returned buffer. + + + + + Creates a new big-endian buffer which wraps the sub-region of the + specified array. A modification on the specified array's content + will be visible to the returned buffer. + + + + + Creates a new buffer which wraps the specified buffer's readable bytes. + A modification on the specified buffer's content will be visible to the returned buffer. + + The buffer to wrap. Reference count ownership of this variable is transfered to this method. + The readable portion of the buffer, or an empty buffer if there is no readable portion. + + + + Creates a new big-endian composite buffer which wraps the specified arrays without copying them. + A modification on the specified arrays' content will be visible to the returned buffer. + + + + + Creates a new big-endian composite buffer which wraps the readable bytes of the specified buffers without copying them. + A modification on the content of the specified buffers will be visible to the returned buffer. + + The buffers to wrap. Reference count ownership of all variables is transfered to this method. + The readable portion of the buffers. The caller is responsible for releasing this buffer. + + + + Creates a new big-endian composite buffer which wraps the specified arrays without copying them. + A modification on the specified arrays' content will be visible to the returned buffer. + + + + + Creates a new big-endian composite buffer which wraps the readable bytes of the specified buffers without copying them. + A modification on the content of the specified buffers will be visible to the returned buffer. + + Advisement as to how many independent buffers are allowed to exist before consolidation occurs. + The buffers to wrap. Reference count ownership of all variables is transfered to this method. + The readable portion of the buffers. The caller is responsible for releasing this buffer. + + + + Creates a new big-endian buffer whose content is a copy of the specified array + The new buffer's and + are 0 and respectively. + + A buffer we're going to copy. + The new buffer that copies the contents of array. + + + + Creates a new big-endian buffer whose content is a copy of the specified array. + The new buffer's and + are 0 and respectively. + + A buffer we're going to copy. + The index offset from which we're going to read array. + + The number of bytes we're going to read from array beginning from position offset. + + The new buffer that copies the contents of array. + + + + Creates a new big-endian buffer whose content is a copy of the specified . + The new buffer's and + are 0 and respectively. + + A buffer we're going to copy. + The new buffer that copies the contents of buffer. + + + + Creates a new big-endian buffer whose content is a merged copy of of the specified arrays. + The new buffer's and + are 0 and respectively. + + + + + + + Creates a new big-endian buffer whose content is a merged copy of the specified . + The new buffer's and + are 0 and respectively. + + Buffers we're going to copy. + The new buffer that copies the contents of buffers. + + + + Creates a new 4-byte big-endian buffer that holds the specified 32-bit integer. + + + + + Create a big-endian buffer that holds a sequence of the specified 32-bit integers. + + + + + Creates a new 2-byte big-endian buffer that holds the specified 16-bit integer. + + + + + Create a new big-endian buffer that holds a sequence of the specified 16-bit integers. + + + + + Create a new big-endian buffer that holds a sequence of the specified 16-bit integers. + + + + + Creates a new 3-byte big-endian buffer that holds the specified 24-bit integer. + + + + + Create a new big-endian buffer that holds a sequence of the specified 24-bit integers. + + + + + Creates a new 8-byte big-endian buffer that holds the specified 64-bit integer. + + + + + Create a new big-endian buffer that holds a sequence of the specified 64-bit integers. + + + + + Creates a new single-byte big-endian buffer that holds the specified boolean value. + + + + + Create a new big-endian buffer that holds a sequence of the specified boolean values. + + + + + Creates a new 4-byte big-endian buffer that holds the specified 32-bit floating point number. + + + + + Create a new big-endian buffer that holds a sequence of the specified 32-bit floating point numbers. + + + + + Creates a new 8-byte big-endian buffer that holds the specified 64-bit floating point number. + + + + + Create a new big-endian buffer that holds a sequence of the specified 64-bit floating point numbers. + + + + + Return a unreleasable view on the given {@link ByteBuf} which will just ignore release and retain calls. + + + + + Unpooled implementation of . + + + + Wraps another . + + It's important that the {@link #readerIndex()} and {@link #writerIndex()} will not do any adjustments on the + indices on the fly because of internal optimizations made by {@link ByteBufUtil#writeAscii(ByteBuf, CharSequence)} + and {@link ByteBufUtil#writeUtf8(ByteBuf, CharSequence)}. + + + diff --git a/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.xml.meta b/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.xml.meta new file mode 100644 index 0000000..a87b2b2 --- /dev/null +++ b/Runtime/csharp-kcp/Plugins/DotNetty.Buffers.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 991956ddfc61ef14ca46d3c8eae721e9 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/csharp-kcp/Plugins/DotNetty.Codecs.dll b/Runtime/csharp-kcp/Plugins/DotNetty.Codecs.dll new file mode 100644 index 0000000000000000000000000000000000000000..422cfd08d487671bdc9daebb4624584e87ae60a5 GIT binary patch literal 131584 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