From 0c8f3a5f9265f577ad3c55f9e3ceff21dec0dbf5 Mon Sep 17 00:00:00 2001 From: ALEXTANG <574809918@qq.com> Date: Thu, 13 Jul 2023 17:17:26 +0800 Subject: [PATCH] [+] TEngineServer [+] TEngineServer --- .gitignore | 4 +- .../ETTask.meta => DotNet/Core.meta} | 2 +- Assets/GameScripts/DotNet/Core/App.cs | 115 + .../Core/App.cs.meta} | 2 +- Assets/GameScripts/DotNet/Core/Assembly.meta | 8 + .../DotNet/Core/Assembly/AssemblyInfo.cs | 44 + .../Core/Assembly/AssemblyInfo.cs.meta} | 2 +- .../DotNet/Core/Assembly/AssemblyManager.cs | 164 + .../Core/Assembly/AssemblyManager.cs.meta} | 2 +- .../DotNet/Core/CommandLineOptions.cs | 67 + .../Core/CommandLineOptions.cs.meta} | 2 +- .../GameScripts/DotNet/Core/CoreErrorCode.cs | 10 + .../DotNet/Core/CoreErrorCode.cs.meta | 11 + .../DotNet/Core/CoroutineLock.meta | 8 + .../Core/CoroutineLock/CoroutineLockQueue.cs | 61 + .../CoroutineLock/CoroutineLockQueue.cs.meta | 11 + .../CoroutineLock/CoroutineLockQueueType.cs | 37 + .../CoroutineLockQueueType.cs.meta | 11 + .../Core/CoroutineLock/WaitCoroutineLock.cs | 93 + .../CoroutineLock/WaitCoroutineLock.cs.meta | 11 + Assets/GameScripts/DotNet/Core/DataBase.meta | 8 + .../DotNet/Core/DataBase/Base.meta | 8 + .../DotNet/Core/DataBase/Base/IDateBase.cs | 45 + .../Core/DataBase/Base/IDateBase.cs.meta | 11 + .../DotNet/Core/DataBase/Base/World.cs | 42 + .../DotNet/Core/DataBase/Base/World.cs.meta | 11 + .../Core/DataBase/Base/WorldConfigInfo.cs | 12 + .../DataBase/Base/WorldConfigInfo.cs.meta | 11 + .../DotNet/Core/DataBase/MongoDataBase.cs | 539 +++ .../Core/DataBase/MongoDataBase.cs.meta | 11 + .../DotNet/Core/DataStructure.meta | 8 + .../DotNet/Core/DataStructure/Collection.meta | 8 + .../Collection/CircularBuffer.cs | 230 ++ .../Collection/CircularBuffer.cs.meta | 11 + .../Collection/ConcurrentOneToManyList.cs | 115 + .../ConcurrentOneToManyList.cs.meta | 11 + .../Collection/ConcurrentOneToManyQueue.cs | 116 + .../ConcurrentOneToManyQueue.cs.meta | 11 + .../DataStructure/Collection/EntityList.cs | 44 + .../Collection/EntityList.cs.meta | 11 + .../DataStructure/Collection/HashSetPool.cs | 45 + .../Collection/HashSetPool.cs.meta | 11 + .../Core/DataStructure/Collection/ListPool.cs | 38 + .../DataStructure/Collection/ListPool.cs.meta | 11 + .../Collection/OneToManyHashSet.cs | 121 + .../Collection/OneToManyHashSet.cs.meta | 11 + .../DataStructure/Collection/OneToManyList.cs | 141 + .../Collection/OneToManyList.cs.meta | 11 + .../Collection/OneToManyQueue.cs | 120 + .../Collection/OneToManyQueue.cs.meta | 11 + .../DataStructure/Collection/PriorityQueue.cs | 537 +++ .../Collection/PriorityQueue.cs.meta | 11 + .../DataStructure/Collection/ReuseList.cs | 29 + .../Collection/ReuseList.cs.meta | 11 + .../SortedConcurrentOneToManyList.cs | 139 + .../SortedConcurrentOneToManyList.cs.meta | 11 + .../Collection/SortedOneToManyHashSet.cs | 111 + .../Collection/SortedOneToManyHashSet.cs.meta | 11 + .../Collection/SortedOneToManyList.cs | 128 + .../Collection/SortedOneToManyList.cs.meta | 11 + .../DotNet/Core/DataStructure/Dictionary.meta | 8 + .../Dictionary/DictionaryExtensions.cs | 19 + .../Dictionary/DictionaryExtensions.cs.meta | 11 + .../Dictionary/DictionaryPool.cs | 29 + .../Dictionary/DictionaryPool.cs.meta | 11 + .../Dictionary/DoubleMapDictionary.cs | 174 + .../Dictionary/DoubleMapDictionary.cs.meta | 11 + .../Dictionary/EntityDictionary.cs | 44 + .../Dictionary/EntityDictionary.cs.meta | 11 + .../Dictionary/OneToManyDictionary.cs | 147 + .../Dictionary/OneToManyDictionary.cs.meta | 11 + .../Dictionary/OneToManySortedDictionary.cs | 150 + .../OneToManySortedDictionary.cs.meta | 11 + .../Dictionary/ReuseDictionary.cs | 29 + .../Dictionary/ReuseDictionary.cs.meta | 11 + .../DotNet/Core/DataStructure/SkipTable.meta | 8 + .../Core/DataStructure/SkipTable/SkipTable.cs | 166 + .../DataStructure/SkipTable/SkipTable.cs.meta | 11 + .../DataStructure/SkipTable/SkipTableBase.cs | 181 + .../SkipTable/SkipTableBase.cs.meta | 11 + .../DataStructure/SkipTable/SkipTableDesc.cs | 166 + .../SkipTable/SkipTableDesc.cs.meta | 11 + .../DataStructure/SkipTable/SkipTableNode.cs | 29 + .../SkipTable/SkipTableNode.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Define.cs | 11 + Assets/GameScripts/DotNet/Core/Define.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Entitas.meta | 8 + .../DotNet/Core/Entitas/EntitiesSystem.cs | 184 + .../Core/Entitas/EntitiesSystem.cs.meta | 11 + .../GameScripts/DotNet/Core/Entitas/Entity.cs | 553 +++ .../DotNet/Core/Entitas/Entity.cs.meta | 11 + .../DotNet/Core/Entitas/Interface.meta | 8 + .../Core/Entitas/Interface/Supported.meta | 8 + .../Interface/Supported/INotSupportedPool.cs | 8 + .../Supported/INotSupportedPool.cs.meta | 11 + .../Interface/Supported/ISupportedDataBase.cs | 8 + .../Supported/ISupportedDataBase.cs.meta | 11 + .../Supported/ISupportedMultiEntity.cs | 9 + .../Supported/ISupportedMultiEntity.cs.meta | 11 + .../DotNet/Core/Entitas/Interface/System.meta | 8 + .../Entitas/Interface/System/AwakeSystem.cs | 18 + .../Interface/System/AwakeSystem.cs.meta | 11 + .../Interface/System/IDeserializeSystem.cs | 16 + .../System/IDeserializeSystem.cs.meta | 11 + .../Interface/System/IDestroySystem.cs | 16 + .../Interface/System/IDestroySystem.cs.meta | 11 + .../Interface/System/IEntitiesSystem.cs | 10 + .../Interface/System/IEntitiesSystem.cs.meta | 11 + .../Entitas/Interface/System/IUpdateSystem.cs | 16 + .../Interface/System/IUpdateSystem.cs.meta | 11 + .../DotNet/Core/Entitas/Scene.meta | 8 + .../DotNet/Core/Entitas/Scene/Scene.cs | 169 + .../DotNet/Core/Entitas/Scene/Scene.cs.meta | 11 + .../DotNet/Core/Entitas/Scene/SceneEvent.cs | 18 + .../Core/Entitas/Scene/SceneEvent.cs.meta | 11 + .../DotNet/Core/Entitas/Unity.meta | 8 + .../DotNet/Core/Entitas/Unity/Attributes.meta | 8 + .../Attributes/BsonDefaultValueAttribute.cs | 11 + .../BsonDefaultValueAttribute.cs.meta | 11 + .../Unity/Attributes/BsonElementAttribute.cs | 13 + .../Attributes/BsonElementAttribute.cs.meta | 11 + .../Unity/Attributes/BsonIdAttribute.cs | 13 + .../Unity/Attributes/BsonIdAttribute.cs.meta | 11 + .../Unity/Attributes/BsonIgnoreAttribute.cs | 11 + .../Attributes/BsonIgnoreAttribute.cs.meta | 11 + .../BsonIgnoreIfDefaultAttribute.cs | 13 + .../BsonIgnoreIfDefaultAttribute.cs.meta | 11 + .../Attributes/BsonIgnoreIfNullAttribute.cs | 11 + .../BsonIgnoreIfNullAttribute.cs.meta | 11 + .../GameScripts/DotNet/Core/EventSystem.meta | 8 + .../DotNet/Core/EventSystem/EventSystem.cs | 177 + .../Core/EventSystem/EventSystem.cs.meta | 11 + .../DotNet/Core/EventSystem/Interface.meta | 8 + .../Core/EventSystem/Interface/IEvent.cs | 64 + .../Core/EventSystem/Interface/IEvent.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Exporter.meta | 8 + .../DotNet/Core/Exporter/Excel.meta | 8 + .../DotNet/Core/Exporter/Excel/Base.meta | 8 + .../Exporter/Excel/Base/DynamicAssembly.cs | 144 + .../Excel/Base/DynamicAssembly.cs.meta | 11 + .../Excel/Base/DynamicConfigDataType.cs | 16 + .../Excel/Base/DynamicConfigDataType.cs.meta | 11 + .../Core/Exporter/Excel/Base/ExcelDefine.cs | 75 + .../Exporter/Excel/Base/ExcelDefine.cs.meta | 11 + .../Core/Exporter/Excel/Base/ExcelTable.cs | 15 + .../Exporter/Excel/Base/ExcelTable.cs.meta | 11 + .../Core/Exporter/Excel/Base/ExportInfo.cs | 9 + .../Exporter/Excel/Base/ExportInfo.cs.meta | 11 + .../Core/Exporter/Excel/Base/ExportType.cs | 12 + .../Exporter/Excel/Base/ExportType.cs.meta | 11 + .../Exporter/Excel/ClientConfigTableManage.cs | 84 + .../Excel/ClientConfigTableManage.cs.meta | 11 + .../Core/Exporter/Excel/ExcelExporter.cs | 788 ++++ .../Core/Exporter/Excel/ExcelExporter.cs.meta | 11 + .../Exporter/Excel/ServerConfigTableManage.cs | 102 + .../Excel/ServerConfigTableManage.cs.meta | 11 + .../DotNet/Core/Exporter/Exporter.cs | 130 + .../DotNet/Core/Exporter/Exporter.cs.meta | 11 + .../DotNet/Core/Exporter/Interface.meta | 8 + .../Core/Exporter/Interface/IConfigTable.cs | 10 + .../Exporter/Interface/IConfigTable.cs.meta | 11 + .../Core/Exporter/Interface/ICustomExport.cs | 44 + .../Exporter/Interface/ICustomExport.cs.meta | 11 + .../DotNet/Core/Exporter/ProtoBuf.meta | 8 + .../Core/Exporter/ProtoBuf/ProtoBufDefine.cs | 28 + .../Exporter/ProtoBuf/ProtoBufDefine.cs.meta | 11 + .../Exporter/ProtoBuf/ProtoBufExporter.cs | 505 +++ .../ProtoBuf/ProtoBufExporter.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Helper.meta | 8 + .../DotNet/Core/Helper/AudioHelper.cs | 432 ++ .../DotNet/Core/Helper/AudioHelper.cs.meta | 11 + .../DotNet/Core/Helper/ByteHelper.cs | 179 + .../DotNet/Core/Helper/ByteHelper.cs.meta | 11 + .../DotNet/Core/Helper/CryptHelper.cs | 77 + .../DotNet/Core/Helper/CryptHelper.cs.meta | 11 + .../DotNet/Core/Helper/FileHelper.cs | 112 + .../DotNet/Core/Helper/FileHelper.cs.meta | 11 + .../DotNet/Core/Helper/JsonHelper.cs | 29 + .../DotNet/Core/Helper/JsonHelper.cs.meta | 11 + .../DotNet/Core/Helper/MD5Helper.cs | 27 + .../DotNet/Core/Helper/MD5Helper.cs.meta | 11 + .../DotNet/Core/Helper/MemoryStreamHelper.cs | 15 + .../Core/Helper/MemoryStreamHelper.cs.meta | 11 + .../GameScripts/DotNet/Core/Helper/Mongo.meta | 8 + .../DotNet/Core/Helper/Mongo/MongoHelper.cs | 118 + .../Core/Helper/Mongo/MongoHelper.cs.meta | 11 + .../Core/Helper/Mongo/StructBsonSerialize.cs | 57 + .../Helper/Mongo/StructBsonSerialize.cs.meta | 11 + .../GameScripts/DotNet/Core/Helper/Proto.meta | 8 + .../DotNet/Core/Helper/Proto/AProto.cs | 11 + .../DotNet/Core/Helper/Proto/AProto.cs.meta | 11 + .../Core/Helper/Proto/ProtoBufHelper.cs | 58 + .../Core/Helper/Proto/ProtoBufHelper.cs.meta | 11 + .../DotNet/Core/Helper/RandomHelper.cs | 251 ++ .../DotNet/Core/Helper/RandomHelper.cs.meta | 11 + .../DotNet/Core/Helper/TimeHelper.cs | 36 + .../DotNet/Core/Helper/TimeHelper.cs.meta | 11 + Assets/GameScripts/DotNet/Core/IdFactory.meta | 8 + .../DotNet/Core/IdFactory/EntityIdStruct.cs | 55 + .../Core/IdFactory/EntityIdStruct.cs.meta | 11 + .../DotNet/Core/IdFactory/IdFactory.cs | 69 + .../DotNet/Core/IdFactory/IdFactory.cs.meta | 11 + .../DotNet/Core/IdFactory/RouteIdStruct.cs | 44 + .../Core/IdFactory/RouteIdStruct.cs.meta | 11 + .../DotNet/Core/IdFactory/RuntimeIdStruct.cs | 40 + .../Core/IdFactory/RuntimeIdStruct.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Log.meta | 8 + Assets/GameScripts/DotNet/Core/Log/ILog.cs | 17 + .../GameScripts/DotNet/Core/Log/ILog.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Log/Log.cs | 141 + .../GameScripts/DotNet/Core/Log/Log.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Log/LogCore.cs | 267 ++ .../DotNet/Core/Log/LogCore.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Log/NLog.cs | 75 + .../GameScripts/DotNet/Core/Log/NLog.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Network.meta | 8 + .../GameScripts/DotNet/Core/Network/Base.meta | 8 + .../DotNet/Core/Network/Base/Enum.meta | 8 + .../Network/Base/Enum/NetworkProtocolType.cs | 9 + .../Base/Enum/NetworkProtocolType.cs.meta | 11 + .../Core/Network/Base/Enum/NetworkTarget.cs | 9 + .../Network/Base/Enum/NetworkTarget.cs.meta | 11 + .../Core/Network/Base/Enum/NetworkType.cs | 9 + .../Network/Base/Enum/NetworkType.cs.meta | 11 + .../DotNet/Core/Network/Base/Helper.meta | 8 + .../Core/Network/Base/Helper/NetworkHelper.cs | 74 + .../Network/Base/Helper/NetworkHelper.cs.meta | 11 + .../DotNet/Core/Network/Base/Interface.meta | 8 + .../Network/Base/Interface/AClientNetwork.cs | 24 + .../Base/Interface/AClientNetwork.cs.meta | 11 + .../Core/Network/Base/Interface/ANetwork.cs | 139 + .../Network/Base/Interface/ANetwork.cs.meta | 11 + .../Network/Base/Interface/ANetworkChannel.cs | 57 + .../Base/Interface/ANetworkChannel.cs.meta | 11 + .../Network/Base/Interface/INetworkUpdate.cs | 7 + .../Base/Interface/INetworkUpdate.cs.meta | 11 + .../Core/Network/Base/LastMessageInfo.cs | 19 + .../Core/Network/Base/LastMessageInfo.cs.meta | 11 + .../DotNet/Core/Network/Base/Server.meta | 8 + .../Network/Base/Server/MachineConfigInfo.cs | 12 + .../Base/Server/MachineConfigInfo.cs.meta | 11 + .../Network/Base/Server/SceneConfigInfo.cs | 19 + .../Base/Server/SceneConfigInfo.cs.meta | 11 + .../DotNet/Core/Network/Base/Server/Server.cs | 173 + .../Core/Network/Base/Server/Server.cs.meta | 11 + .../Network/Base/Server/ServerConfigInfo.cs | 10 + .../Base/Server/ServerConfigInfo.cs.meta | 11 + .../Core/Network/Base/SocketExtensions.cs | 51 + .../Network/Base/SocketExtensions.cs.meta | 11 + .../DotNet/Core/Network/Entity.meta | 8 + .../Network/Entity/ClientNetworkComponent.cs | 57 + .../Entity/ClientNetworkComponent.cs.meta | 11 + .../Network/Entity/ServerNetworkComponent.cs | 46 + .../Entity/ServerNetworkComponent.cs.meta | 11 + .../DotNet/Core/Network/Entity/Session.meta | 8 + .../Entity/Session/ServerInnerSession.cs | 49 + .../Entity/Session/ServerInnerSession.cs.meta | 11 + .../Core/Network/Entity/Session/Session.cs | 212 + .../Network/Entity/Session/Session.cs.meta | 11 + .../DotNet/Core/Network/Exception.meta | 8 + .../Core/Network/Exception/ScanException.cs | 11 + .../Network/Exception/ScanException.cs.meta | 11 + .../DotNet/Core/Network/Message.meta | 8 + .../Core/Network/Message/Addressable.meta | 8 + .../Message/Addressable/AddressableHelper.cs | 104 + .../Addressable/AddressableHelper.cs.meta | 11 + .../Addressable/AddressableManageComponent.cs | 62 + .../AddressableManageComponent.cs.meta | 11 + .../AddressableMessageComponent.cs | 59 + .../AddressableMessageComponent.cs.meta | 11 + .../Addressable/AddressableRouteComponent.cs | 191 + .../AddressableRouteComponent.cs.meta | 11 + .../Network/Message/Addressable/Handler.meta | 8 + .../Handler/I_AddressableAddHandler.cs | 11 + .../Handler/I_AddressableAddHandler.cs.meta | 11 + .../Handler/I_AddressableGetHandler.cs | 11 + .../Handler/I_AddressableGetHandler.cs.meta | 11 + .../Handler/I_AddressableLockHandler.cs | 11 + .../Handler/I_AddressableLockHandler.cs.meta | 11 + .../Handler/I_AddressableRemoveHandler.cs | 11 + .../I_AddressableRemoveHandler.cs.meta | 11 + .../Handler/I_AddressableUnLockHandler.cs | 12 + .../I_AddressableUnLockHandler.cs.meta | 11 + .../Core/Network/Message/Dispatcher.meta | 8 + .../Dispatcher/MessageDispatcherSystem.cs | 265 ++ .../MessageDispatcherSystem.cs.meta | 11 + .../Core/Network/Message/Dispatcher/Opcode.cs | 50 + .../Network/Message/Dispatcher/Opcode.cs.meta | 11 + .../Network/Message/Dispatcher/Response.cs | 6 + .../Message/Dispatcher/Response.cs.meta | 11 + .../Core/Network/Message/Interface.meta | 8 + .../Network/Message/Interface/Handler.meta | 8 + .../Interface/Handler/IMessageHandler.cs | 87 + .../Interface/Handler/IMessageHandler.cs.meta | 11 + .../Interface/Handler/IRouteMessageHandler.cs | 222 ++ .../Handler/IRouteMessageHandler.cs.meta | 11 + .../Network/Message/Interface/Protocol.meta | 8 + .../Interface/Protocol/IBsonMessage.cs | 17 + .../Interface/Protocol/IBsonMessage.cs.meta | 11 + .../Message/Interface/Protocol/IMessage.cs | 17 + .../Interface/Protocol/IMessage.cs.meta | 11 + .../Interface/Protocol/IRouteMessage.cs | 26 + .../Interface/Protocol/IRouteMessage.cs.meta | 11 + .../Network/Message/Interface/Scheduler.meta | 8 + .../Scheduler/ANetworkMessageScheduler.cs | 91 + .../ANetworkMessageScheduler.cs.meta | 11 + .../INetworkMessageSchedulerHandler.cs | 21 + .../INetworkMessageSchedulerHandler.cs.meta | 11 + .../Core/Network/Message/MessageHelper.cs | 196 + .../Network/Message/MessageHelper.cs.meta | 11 + .../Core/Network/Message/MessageSender.cs | 59 + .../Network/Message/MessageSender.cs.meta | 11 + .../OnNetworkMessageUpdateCheckTimeout.cs | 71 + ...OnNetworkMessageUpdateCheckTimeout.cs.meta | 11 + .../Core/Network/Message/Protocols.meta | 8 + .../Message/Protocols/CoreMessageProtocols.cs | 160 + .../Protocols/CoreMessageProtocols.cs.meta | 11 + .../DotNet/Core/Network/Message/Route.meta | 8 + .../Network/Message/Route/RouteComponent.cs | 37 + .../Message/Route/RouteComponent.cs.meta | 11 + .../Core/Network/Message/Scheduler.meta | 8 + .../Scheduler/ClientMessageScheduler.cs | 69 + .../Scheduler/ClientMessageScheduler.cs.meta | 11 + .../Message/Scheduler/CoreRouteType.cs | 22 + .../Message/Scheduler/CoreRouteType.cs.meta | 11 + .../Scheduler/InnerMessageScheduler.cs | 115 + .../Scheduler/InnerMessageScheduler.cs.meta | 11 + .../Scheduler/OuterMessageScheduler.cs | 129 + .../Scheduler/OuterMessageScheduler.cs.meta | 11 + .../DotNet/Core/Network/NetworkProtocol.meta | 8 + .../Core/Network/NetworkProtocol/KCP.meta | 8 + .../Network/NetworkProtocol/KCP/Base.meta | 8 + .../Network/NetworkProtocol/KCP/Base/KCP.cs | 218 ++ .../NetworkProtocol/KCP/Base/KCP.cs.meta | 11 + .../NetworkProtocol/KCP/Base/KCPSettings.cs | 51 + .../KCP/Base/KCPSettings.cs.meta | 11 + .../NetworkProtocol/KCP/Base/KcpHeader.cs | 15 + .../KCP/Base/KcpHeader.cs.meta | 11 + .../KCP/Base/KcpProtocalType.cs | 14 + .../KCP/Base/KcpProtocalType.cs.meta | 3 + .../Network/NetworkProtocol/KCP/Client.meta | 8 + .../KCP/Client/KCPClientNetwork.cs | 622 +++ .../KCP/Client/KCPClientNetwork.cs.meta | 11 + .../Network/NetworkProtocol/KCP/Server.meta | 8 + .../KCP/Server/KCPServerNetwork.cs | 502 +++ .../KCP/Server/KCPServerNetwork.cs.meta | 11 + .../KCP/Server/KCPServerNetworkChannel.cs | 252 ++ .../Server/KCPServerNetworkChannel.cs.meta | 11 + .../Core/Network/NetworkProtocol/TCP.meta | 8 + .../Network/NetworkProtocol/TCP/Client.meta | 8 + .../TCP/Client/TCPClientNetwork.cs | 535 +++ .../TCP/Client/TCPClientNetwork.cs.meta | 11 + .../Network/NetworkProtocol/TCP/Server.meta | 8 + .../TCP/Server/TCPServerNetwork.cs | 238 ++ .../TCP/Server/TCPServerNetwork.cs.meta | 11 + .../TCP/Server/TCPServerNetworkChannel.cs | 349 ++ .../Server/TCPServerNetworkChannel.cs.meta | 11 + .../DotNet/Core/Network/NetworkThread.meta | 8 + .../Core/Network/NetworkThread/NetAction.cs | 63 + .../Network/NetworkThread/NetAction.cs.meta | 11 + .../Network/NetworkThread/NetworkThread.cs | 218 ++ .../NetworkThread/NetworkThread.cs.meta | 11 + .../DotNet/Core/Network/PacketParser.meta | 8 + .../Network/PacketParser/InnerPacketParser.cs | 262 ++ .../PacketParser/InnerPacketParser.cs.meta | 11 + .../Core/Network/PacketParser/Interface.meta | 8 + .../PacketParser/Interface/APackInfo.cs | 27 + .../PacketParser/Interface/APackInfo.cs.meta | 11 + .../PacketParser/Interface/APacketParser.cs | 43 + .../Interface/APacketParser.cs.meta | 11 + .../Network/PacketParser/OuterPacketParser.cs | 216 + .../PacketParser/OuterPacketParser.cs.meta | 11 + .../Core/Network/PacketParser/Packet.cs | 58 + .../Core/Network/PacketParser/Packet.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Pool.meta | 8 + .../DotNet/Core/Pool/ConcurrentPool.cs | 47 + .../DotNet/Core/Pool/ConcurrentPool.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Pool/Pool.cs | 52 + .../Kcp => DotNet/Core/Pool}/Pool.cs.meta | 2 +- .../GameScripts/DotNet/Core/Pool/PoolCore.cs | 46 + .../DotNet/Core/Pool/PoolCore.cs.meta | 11 + .../DotNet/Core/Pool/PoolWithDisposable.cs | 54 + .../Core/Pool/PoolWithDisposable.cs.meta | 11 + .../DotNet/Core/RecyclableMemoryStream.meta | 8 + .../Core/RecyclableMemoryStream/EventArgs.cs | 463 +++ .../RecyclableMemoryStream/EventArgs.cs.meta | 11 + .../Core/RecyclableMemoryStream/Events.cs | 258 ++ .../RecyclableMemoryStream/Events.cs.meta | 11 + .../RecyclableMemoryStream.cs | 1613 ++++++++ .../RecyclableMemoryStream.cs.meta | 11 + .../RecyclableMemoryStreamManager.cs | 1077 +++++ .../RecyclableMemoryStreamManager.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Singleton.meta | 8 + .../DotNet/Core/Singleton/Interface.meta | 8 + .../Core/Singleton/Interface/ISingleton.cs | 11 + .../Singleton/Interface/ISingleton.cs.meta | 11 + .../Singleton/Interface/IUpdateSingleton.cs | 7 + .../Interface/IUpdateSingleton.cs.meta | 11 + .../DotNet/Core/Singleton/Singleton.cs | 37 + .../DotNet/Core/Singleton/Singleton.cs.meta | 11 + .../DotNet/Core/Singleton/SingletonSystem.cs | 132 + .../Core/Singleton/SingletonSystem.cs.meta | 11 + .../DotNet/Core/SynchronizationContext.meta | 8 + .../ThreadSynchronizationContext.cs | 48 + .../ThreadSynchronizationContext.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Task.meta | 8 + .../GameScripts/DotNet/Core/Task/Builder.meta | 8 + .../AsyncFTaskCompletedMethodBuilder.cs} | 27 +- .../AsyncFTaskCompletedMethodBuilder.cs.meta | 11 + .../Task/Builder/AsyncFTaskMethodBuilder.cs | 151 + .../Builder/AsyncFTaskMethodBuilder.cs.meta | 11 + .../Task/Builder/AsyncFVoidMethodBuilder.cs | 72 + .../Builder/AsyncFVoidMethodBuilder.cs.meta | 11 + .../DotNet/Core/Task/FCancellationToken.cs | 46 + .../Core/Task/FCancellationToken.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Task/FTask.cs | 299 ++ .../DotNet/Core/Task/FTask.cs.meta | 11 + .../Core/Task/FTaskCompleted.cs} | 13 +- .../DotNet/Core/Task/FTaskCompleted.cs.meta | 11 + .../ETVoid.cs => DotNet/Core/Task/FVoid.cs} | 18 +- .../DotNet/Core/Task/FVoid.cs.meta | 11 + .../GameScripts/DotNet/Core/Task/Partial.meta | 8 + .../DotNet/Core/Task/Partial/FTask.Factory.cs | 26 + .../Core/Task/Partial/FTask.Factory.cs.meta | 11 + .../DotNet/Core/Task/Partial/FTask.Pool.cs | 29 + .../Core/Task/Partial/FTask.Pool.cs.meta | 11 + .../DotNet/Core/Task/Partial/FTask.WhenAll.cs | 157 + .../Core/Task/Partial/FTask.WhenAll.cs.meta | 11 + .../DotNet/Core/TimerScheduler.meta | 8 + .../DotNet/Core/TimerScheduler/Interface.meta | 8 + .../TimerScheduler/Interface/TimerHandler.cs | 4 + .../Interface/TimerHandler.cs.meta | 11 + .../DotNet/Core/TimerScheduler/TimerAction.cs | 31 + .../Core/TimerScheduler/TimerAction.cs.meta | 11 + .../Core/TimerScheduler/TimerScheduler.cs | 21 + .../TimerScheduler/TimerScheduler.cs.meta | 11 + .../Core/TimerScheduler/TimerSchedulerCore.cs | 264 ++ .../TimerScheduler/TimerSchedulerCore.cs.meta | 11 + .../DotNet/Core/TimerScheduler/TimerType.cs | 10 + .../Core/TimerScheduler/TimerType.cs.meta | 11 + Assets/GameScripts/DotNet/Core/Unity.meta | 8 + .../DotNet/Core/Unity/ApplicationContext.cs | 36 + .../Core/Unity/ApplicationContext.cs.meta | 11 + .../DotNet/Core/Unity/AssemblyName.cs | 16 + .../DotNet/Core/Unity/AssemblyName.cs.meta | 11 + .../DotNet/Core/Unity/GameAppEntry.cs | 40 + .../DotNet/Core/Unity/GameAppEntry.cs.meta | 11 + Assets/GameScripts/DotNet/DotNet.asmdef | 6 +- Assets/GameScripts/DotNet/Logic.meta | 8 + .../DotNet/Logic/CustomExport.meta | 8 + .../CustomExport/SceneTypeConfigToEnum.cs | 49 + .../SceneTypeConfigToEnum.cs.meta | 11 + Assets/GameScripts/DotNet/Logic/Entry.cs | 11 + Assets/GameScripts/DotNet/Logic/Entry.cs.meta | 11 + .../DotNet/Logic/Generate~/ConfigTable.meta | 8 + .../Logic/Generate~/ConfigTable/Entity.meta | 8 + .../ConfigTable/Entity/MachineConfig.cs | 88 + .../ConfigTable/Entity/MachineConfig.cs.meta | 11 + .../ConfigTable/Entity/SceneConfig.cs | 94 + .../ConfigTable/Entity/SceneConfig.cs.meta | 11 + .../ConfigTable/Entity/ServerConfig.cs | 86 + .../ConfigTable/Entity/ServerConfig.cs.meta | 11 + .../ConfigTable/Entity/WorldConfig.cs | 90 + .../ConfigTable/Entity/WorldConfig.cs.meta | 11 + .../DotNet/Logic/Generate~/CustomExport.meta | 8 + .../Logic/Generate~/CustomExport/SceneType.cs | 21 + .../Generate~/CustomExport/SceneType.cs.meta | 11 + .../Logic/Generate~/NetworkProtocol.meta | 8 + .../NetworkProtocol/InnerBsonMessage.cs | 9 + .../NetworkProtocol/InnerBsonMessage.cs.meta | 11 + .../NetworkProtocol/InnerBsonOpcode.cs | 6 + .../NetworkProtocol/InnerBsonOpcode.cs.meta | 11 + .../Generate~/NetworkProtocol/InnerMessage.cs | 31 + .../NetworkProtocol/InnerMessage.cs.meta | 11 + .../Generate~/NetworkProtocol/InnerOpcode.cs | 8 + .../NetworkProtocol/InnerOpcode.cs.meta | 11 + .../Generate~/NetworkProtocol/OuterMessage.cs | 157 + .../NetworkProtocol/OuterMessage.cs.meta | 11 + .../Generate~/NetworkProtocol/OuterOpcode.cs | 20 + .../NetworkProtocol/OuterOpcode.cs.meta | 11 + .../Generate~/NetworkProtocol/RouteType.cs | 8 + .../NetworkProtocol/RouteType.cs.meta | 11 + Assets/GameScripts/DotNet/Logic/Handler.meta | 8 + .../DotNet/Logic/Handler/Address.meta | 8 + .../Handler/Address/H_C2M_MessageHandler.cs | 15 + .../Address/H_C2M_MessageHandler.cs.meta | 11 + .../Address/H_C2M_MessageRequestHandler.cs | 16 + .../H_C2M_MessageRequestHandler.cs.meta | 11 + ...H_C2M_PushAddressMessageToClientHandler.cs | 21 + ..._PushAddressMessageToClientHandler.cs.meta | 11 + .../H_C2G_LoginAddressRequestHandler.cs | 55 + .../H_C2G_LoginAddressRequestHandler.cs.meta | 11 + .../Handler/H_C2G_LoginRequestHandler.cs | 18 + .../Handler/H_C2G_LoginRequestHandler.cs.meta | 11 + .../Logic/Handler/H_C2G_MessageHandler.cs | 16 + .../Handler/H_C2G_MessageHandler.cs.meta | 11 + .../Handler/H_C2G_MessageRequestHandler.cs | 18 + .../H_C2G_MessageRequestHandler.cs.meta | 11 + .../H_C2G_PushMessageToClientHandler.cs | 20 + .../H_C2G_PushMessageToClientHandler.cs.meta | 11 + .../I_G2M_LoginAddressRequestHandler.cs | 46 + .../I_G2M_LoginAddressRequestHandler.cs.meta | 11 + Assets/GameScripts/DotNet/Logic/Helper.meta | 8 + .../DotNet/Logic/Helper/AssemblySystem.cs | 27 + .../Logic/Helper/AssemblySystem.cs.meta | 11 + .../DotNet/Logic/Helper/ConfigTableSystem.cs | 139 + .../Logic/Helper/ConfigTableSystem.cs.meta | 11 + .../GameScripts/DotNet/Logic/OnCreateScene.cs | 32 + .../DotNet/Logic/OnCreateScene.cs.meta | 11 + Assets/GameScripts/DotNet/csc.rsp | 1 + Assets/GameScripts/DotNet/csc.rsp.meta | 7 + .../GameScripts/HotFix/GameLogic/GameApp.cs | 5 + .../GameLogic/GameApp_RegisterSystem.cs | 8 + .../HotFix/GameLogic/GameLogic.asmdef | 3 +- .../ETTask/AsyncETTaskMethodBuilder.cs | 125 - .../ETTask/AsyncETVoidMethodBuilder.cs | 64 - .../ThirdParty/ETTask/ETCancellationToken.cs | 54 - .../GameScripts/ThirdParty/ETTask/ETTask.cs | 313 -- .../ThirdParty/ETTask/ETTask.cs.meta | 11 - .../ThirdParty/ETTask/ETTaskCompleted.cs.meta | 11 - .../ThirdParty/ETTask/ETTaskHelper.cs | 126 - .../ThirdParty/ETTask/ETTaskHelper.cs.meta | 11 - .../ThirdParty/ETTask/ETVoid.cs.meta | 11 - .../GameScripts/ThirdParty/ETTask/IAwaiter.cs | 14 - .../ThirdParty/ETTask/IAwaiter.cs.meta | 11 - .../GameScripts/ThirdParty/KCP/Plugins.meta | 8 + .../ThirdParty/KCP/Plugins/Android.meta | 8 + .../KCP/Plugins/Android/arm64_v8a.meta | 8 + .../KCP/Plugins/Android/arm64_v8a/libkcp.so | Bin 0 -> 32096 bytes .../Plugins/Android/arm64_v8a/libkcp.so.meta | 33 + .../KCP/Plugins/Android/armeabi-v7a.meta | 8 + .../KCP/Plugins/Android/armeabi-v7a/libkcp.so | Bin 0 -> 68152 bytes .../Android/armeabi-v7a/libkcp.so.meta | 33 + .../ThirdParty/KCP/Plugins/Android/x86.meta | 8 + .../KCP/Plugins/Android/x86/libkcp.so | Bin 0 -> 28708 bytes .../KCP/Plugins/Android/x86/libkcp.so.meta | 33 + .../ThirdParty/KCP/Plugins/IOS.meta | 8 + .../ThirdParty/KCP/Plugins/IOS/libkcp.a | Bin 0 -> 43328 bytes .../ThirdParty/KCP/Plugins/IOS/libkcp.a.meta | 33 + .../ThirdParty/KCP/Plugins/MacOS.meta | 8 + .../KCP/Plugins/MacOS/kcp.bundle.meta | 33 + .../Plugins/MacOS/kcp.bundle/Contents.meta | 8 + .../MacOS/kcp.bundle/Contents/MacOS.meta | 8 + .../MacOS/kcp.bundle/Contents/MacOS/kcp | Bin 0 -> 135808 bytes .../MacOS/kcp.bundle/Contents/MacOS/kcp.meta | 7 + .../ThirdParty/KCP/Plugins/x86_64.meta | 8 + .../ThirdParty/KCP/Plugins/x86_64/kcp.dll | Bin 0 -> 146432 bytes .../KCP/Plugins/x86_64/kcp.dll.meta | 52 + Assets/GameScripts/ThirdParty/Kcp/AckItem.cs | 8 - .../ThirdParty/Kcp/AckItem.cs.meta | 11 - Assets/GameScripts/ThirdParty/Kcp/Kcp.cs | 1244 ------ Assets/GameScripts/ThirdParty/Kcp/Kcp.cs.meta | 11 - .../GameScripts/ThirdParty/Kcp/KcpPatial.cs | 23 - .../ThirdParty/Kcp/KcpPatial.cs.meta | 11 - Assets/GameScripts/ThirdParty/Kcp/Pool.cs | 51 - Assets/GameScripts/ThirdParty/Kcp/Segment.cs | 132 - .../ThirdParty/Kcp/Segment.cs.meta | 11 - Assets/GameScripts/ThirdParty/Kcp/Utils.cs | 106 - .../GameScripts/ThirdParty/Kcp/Utils.cs.meta | 11 - .../GameScripts/ThirdParty/Protobuf-net.meta | 8 + .../ThirdParty/Protobuf-net/BclHelpers.cs | 712 ++++ .../Protobuf-net/BclHelpers.cs.meta | 11 + .../Protobuf-net/BufferExtension.cs | 78 + .../Protobuf-net/BufferExtension.cs.meta | 11 + .../ThirdParty/Protobuf-net/BufferPool.cs | 149 + .../Protobuf-net/BufferPool.cs.meta | 11 + .../Protobuf-net/CallbackAttributes.cs | 33 + .../Protobuf-net/CallbackAttributes.cs.meta | 11 + .../ThirdParty/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 + .../ThirdParty/Protobuf-net/Compiler/Local.cs | 58 + .../Protobuf-net/Compiler/Local.cs.meta | 11 + .../ThirdParty/Protobuf-net/DataFormat.cs | 49 + .../Protobuf-net/DataFormat.cs.meta | 11 + .../DiscriminatedUnion.Serializable.cs | 176 + .../DiscriminatedUnion.Serializable.cs.meta | 11 + .../Protobuf-net/DiscriminatedUnion.cs | 416 ++ .../Protobuf-net/DiscriminatedUnion.cs.meta | 11 + .../ThirdParty/Protobuf-net/Extensible.cs | 284 ++ .../Protobuf-net/Extensible.cs.meta | 11 + .../ThirdParty/Protobuf-net/ExtensibleUtil.cs | 118 + .../Protobuf-net/ExtensibleUtil.cs.meta | 11 + .../Protobuf-net/GlobalSuppressions.cs | Bin 0 -> 2750 bytes .../Protobuf-net/GlobalSuppressions.cs.meta | 11 + .../ThirdParty/Protobuf-net/Helpers.cs | 638 +++ .../ThirdParty/Protobuf-net/Helpers.cs.meta | 11 + .../ThirdParty/Protobuf-net/IExtensible.cs | 23 + .../Protobuf-net/IExtensible.cs.meta | 11 + .../ThirdParty/Protobuf-net/IExtension.cs | 58 + .../Protobuf-net/IExtension.cs.meta | 11 + .../ThirdParty/Protobuf-net/IProtoInputT.cs | 13 + .../Protobuf-net/IProtoInputT.cs.meta | 11 + .../ThirdParty/Protobuf-net/IProtoOutputT.cs | 55 + .../Protobuf-net/IProtoOutputT.cs.meta | 11 + .../ThirdParty/Protobuf-net/ImplicitFields.cs | 29 + .../Protobuf-net/ImplicitFields.cs.meta | 11 + .../Protobuf-net/KeyValuePairProxy.cs | 44 + .../Protobuf-net/KeyValuePairProxy.cs.meta | 11 + .../ThirdParty/Protobuf-net/Meta.meta | 8 + .../Protobuf-net/Meta/AttributeMap.cs | 108 + .../Protobuf-net/Meta/AttributeMap.cs.meta | 11 + .../ThirdParty/Protobuf-net/Meta/BasicList.cs | 267 ++ .../Protobuf-net/Meta/BasicList.cs.meta | 11 + .../Protobuf-net/Meta/CallbackSet.cs | 110 + .../Protobuf-net/Meta/CallbackSet.cs.meta | 11 + .../ThirdParty/Protobuf-net/Meta/MetaType.cs | 2171 ++++++++++ .../Protobuf-net/Meta/MetaType.cs.meta | 11 + .../Protobuf-net/Meta/ProtoSyntax.cs | 17 + .../Protobuf-net/Meta/ProtoSyntax.cs.meta | 11 + .../Protobuf-net/Meta/RuntimeTypeModel.cs | 2036 ++++++++++ .../Meta/RuntimeTypeModel.cs.meta | 11 + .../ThirdParty/Protobuf-net/Meta/SubType.cs | 97 + .../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 + .../ThirdParty/Protobuf-net/Meta/TypeModel.cs | 1696 ++++++++ .../Protobuf-net/Meta/TypeModel.cs.meta | 11 + .../Protobuf-net/Meta/ValueMember.cs | 855 ++++ .../Protobuf-net/Meta/ValueMember.cs.meta | 11 + .../ThirdParty/Protobuf-net/NetObjectCache.cs | 190 + .../Protobuf-net/NetObjectCache.cs.meta | 11 + .../ThirdParty/Protobuf-net/PrefixStyle.cs | 26 + .../Protobuf-net/PrefixStyle.cs.meta | 11 + .../Protobuf-net/ProtoContractAttribute.cs | 175 + .../ProtoContractAttribute.cs.meta | 11 + .../Protobuf-net/ProtoConverterAttribute.cs | 13 + .../ProtoConverterAttribute.cs.meta | 11 + .../Protobuf-net/ProtoEnumAttribute.cs | 36 + .../Protobuf-net/ProtoEnumAttribute.cs.meta | 11 + .../ThirdParty/Protobuf-net/ProtoException.cs | 30 + .../Protobuf-net/ProtoException.cs.meta | 11 + .../Protobuf-net/ProtoIgnoreAttribute.cs | 40 + .../Protobuf-net/ProtoIgnoreAttribute.cs.meta | 11 + .../Protobuf-net/ProtoIncludeAttribute.cs | 60 + .../ProtoIncludeAttribute.cs.meta | 11 + .../Protobuf-net/ProtoMapAttribute.cs | 29 + .../Protobuf-net/ProtoMapAttribute.cs.meta | 11 + .../Protobuf-net/ProtoMemberAttribute.cs | 228 ++ .../Protobuf-net/ProtoMemberAttribute.cs.meta | 11 + .../ThirdParty/Protobuf-net/ProtoReader.cs | 1444 +++++++ .../Protobuf-net/ProtoReader.cs.meta | 11 + .../ThirdParty/Protobuf-net/ProtoWriter.cs | 1003 +++++ .../Protobuf-net/ProtoWriter.cs.meta | 11 + .../Protobuf-net/SerializationContext.cs | 76 + .../Protobuf-net/SerializationContext.cs.meta | 11 + .../ThirdParty/Protobuf-net/Serializer.cs | 514 +++ .../Protobuf-net/Serializer.cs.meta | 11 + .../ThirdParty/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 + .../ThirdParty/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 + .../ThirdParty/Protobuf-net/SubItemToken.cs | 16 + .../Protobuf-net/SubItemToken.cs.meta | 11 + .../ThirdParty/Protobuf-net/WireType.cs | 50 + .../ThirdParty/Protobuf-net/WireType.cs.meta | 11 + .../Protobuf-net/protobuf-net.csproj.meta | 7 + .../GameScripts/ThirdParty/Recast/Recast.cs | 2 +- DotNet/App/NLog.config | 90 + DotNet/App/NLog.xsd | 3483 +++++++++++++++++ DotNet/App/Program.cs | 32 + DotNet/App/ProgramInfo.cs | 62 + DotNet/App/TEngineSettings.json | 64 + DotNet/Config/Binary/MachineConfigData.bytes | 2 + DotNet/Config/Binary/SceneConfigData.bytes | Bin 0 -> 140 bytes DotNet/Config/Binary/ServerConfigData.bytes | Bin 0 -> 48 bytes DotNet/Config/Binary/WorldConfigData.bytes | 2 + DotNet/Config/Excel/Server/MachineConfig.xlsx | Bin 0 -> 10179 bytes DotNet/Config/Excel/Server/SceneConfig.xlsx | Bin 0 -> 11335 bytes DotNet/Config/Excel/Server/ServerConfig.xlsx | Bin 0 -> 11009 bytes DotNet/Config/Excel/Server/WorldConfig.xlsx | Bin 0 -> 9772 bytes DotNet/Config/Excel/Version.txt | 1 + .../Config/Json/Client/SceneConfigData.Json | 12 + .../Config/Json/Server/MachineConfigData.Json | 11 + .../Config/Json/Server/SceneConfigData.Json | 44 + .../Config/Json/Server/ServerConfigData.Json | 28 + .../Config/Json/Server/WorldConfigData.Json | 12 + DotNet/Config/ProtoBuf/InnerBsonMessage.proto | 2 + DotNet/Config/ProtoBuf/InnerMessage.proto | 12 + DotNet/Config/ProtoBuf/OuterMessage.proto | 69 + DotNet/Config/ProtoBuf/RouteType.Config | 2 + DotNet/Config/Template/ExcelTemplate.txt | 78 + DotNet/Config/Template/ProtoTemplate.txt | 20 + DotNet/DotNet.sln | 43 - ...rectory.Build.props => Server.Build.props} | 0 ...sln.DotSettings => Server.sln.DotSettings} | 0 DotNet/ThirdParty/DotNet.ThirdParty.csproj | 81 - DotNet/ThirdParty/KCPLibrary/kcp.dll | Bin 0 -> 146432 bytes DotNet/ThirdParty/KCPLibrary/kcp.dylib | Bin 0 -> 135808 bytes DotNet/ThirdParty/KCPLibrary/kcp.so | Bin 0 -> 31360 bytes 790 files changed, 52737 insertions(+), 2533 deletions(-) rename Assets/GameScripts/{ThirdParty/ETTask.meta => DotNet/Core.meta} (77%) create mode 100644 Assets/GameScripts/DotNet/Core/App.cs rename Assets/GameScripts/{ThirdParty/ETTask/ETCancellationToken.cs.meta => DotNet/Core/App.cs.meta} (83%) create mode 100644 Assets/GameScripts/DotNet/Core/Assembly.meta create mode 100644 Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs rename Assets/GameScripts/{ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs.meta => DotNet/Core/Assembly/AssemblyInfo.cs.meta} (83%) create mode 100644 Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs rename Assets/GameScripts/{ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs.meta => DotNet/Core/Assembly/AssemblyManager.cs.meta} (83%) create mode 100644 Assets/GameScripts/DotNet/Core/CommandLineOptions.cs rename Assets/GameScripts/{ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs.meta => DotNet/Core/CommandLineOptions.cs.meta} (83%) create mode 100644 Assets/GameScripts/DotNet/Core/CoreErrorCode.cs create mode 100644 Assets/GameScripts/DotNet/Core/CoreErrorCode.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock.meta create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs create mode 100644 Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataBase.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs create mode 100644 Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Define.cs create mode 100644 Assets/GameScripts/DotNet/Core/Define.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Entity.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Entity.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Scene.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs create mode 100644 Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/EventSystem.meta create mode 100644 Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/EventSystem/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs create mode 100644 Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs create mode 100644 Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Mongo.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Proto.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory.meta create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs create mode 100644 Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Log.meta create mode 100644 Assets/GameScripts/DotNet/Core/Log/ILog.cs create mode 100644 Assets/GameScripts/DotNet/Core/Log/ILog.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Log/Log.cs create mode 100644 Assets/GameScripts/DotNet/Core/Log/Log.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Log/LogCore.cs create mode 100644 Assets/GameScripts/DotNet/Core/Log/LogCore.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Log/NLog.cs create mode 100644 Assets/GameScripts/DotNet/Core/Log/NLog.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Helper.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/Session.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Exception.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Protocols.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Route.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkThread.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs create mode 100644 Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Pool.meta create mode 100644 Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs create mode 100644 Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Pool/Pool.cs rename Assets/GameScripts/{ThirdParty/Kcp => DotNet/Core/Pool}/Pool.cs.meta (83%) create mode 100644 Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs create mode 100644 Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs create mode 100644 Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream.meta create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs create mode 100644 Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Singleton.meta create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs create mode 100644 Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/SynchronizationContext.meta create mode 100644 Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs create mode 100644 Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Builder.meta rename Assets/GameScripts/{ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs => DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs} (60%) create mode 100644 Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/FTask.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/FTask.cs.meta rename Assets/GameScripts/{ThirdParty/ETTask/ETTaskCompleted.cs => DotNet/Core/Task/FTaskCompleted.cs} (59%) create mode 100644 Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs.meta rename Assets/GameScripts/{ThirdParty/ETTask/ETVoid.cs => DotNet/Core/Task/FVoid.cs} (59%) create mode 100644 Assets/GameScripts/DotNet/Core/Task/FVoid.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs create mode 100644 Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/Interface.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs create mode 100644 Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Unity.meta create mode 100644 Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs create mode 100644 Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs create mode 100644 Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs.meta create mode 100644 Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs create mode 100644 Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic.meta create mode 100644 Assets/GameScripts/DotNet/Logic/CustomExport.meta create mode 100644 Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs create mode 100644 Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Entry.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Entry.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/CustomExport.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Helper.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs create mode 100644 Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs.meta create mode 100644 Assets/GameScripts/DotNet/Logic/OnCreateScene.cs create mode 100644 Assets/GameScripts/DotNet/Logic/OnCreateScene.cs.meta create mode 100644 Assets/GameScripts/DotNet/csc.rsp create mode 100644 Assets/GameScripts/DotNet/csc.rsp.meta delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETTask.cs delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETTask.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs delete mode 100644 Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll create mode 100644 Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/AckItem.cs delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/AckItem.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Kcp.cs delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Kcp.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Pool.cs delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Segment.cs delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Segment.cs.meta delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Utils.cs delete mode 100644 Assets/GameScripts/ThirdParty/Kcp/Utils.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/BclHelpers.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/BclHelpers.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/BufferExtension.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/BufferExtension.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/BufferPool.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/BufferPool.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/CallbackAttributes.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/CallbackAttributes.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerContext.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerContext.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerDelegates.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerDelegates.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/Local.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/Local.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/DataFormat.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/DataFormat.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.Serializable.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Extensible.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Extensible.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ExtensibleUtil.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ExtensibleUtil.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/GlobalSuppressions.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/GlobalSuppressions.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Helpers.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Helpers.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IExtensible.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IExtensible.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IExtension.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IExtension.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IProtoInputT.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IProtoInputT.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IProtoOutputT.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/IProtoOutputT.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ImplicitFields.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ImplicitFields.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/KeyValuePairProxy.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/KeyValuePairProxy.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/AttributeMap.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/AttributeMap.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/BasicList.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/BasicList.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/CallbackSet.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/CallbackSet.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/MetaType.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/MetaType.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ProtoSyntax.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ProtoSyntax.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/RuntimeTypeModel.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/RuntimeTypeModel.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/SubType.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/SubType.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeAddedEventArgs.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeFormatEventArgs.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.InputOutput.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ValueMember.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ValueMember.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/NetObjectCache.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/NetObjectCache.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/PrefixStyle.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/PrefixStyle.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoContractAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoContractAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoConverterAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoConverterAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoEnumAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoEnumAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoException.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoException.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIgnoreAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIgnoreAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIncludeAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIncludeAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMapAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMapAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMemberAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMemberAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoReader.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoReader.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoWriter.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ProtoWriter.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/SerializationContext.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/SerializationContext.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ArrayDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ArrayDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BlobSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BlobSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BooleanSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BooleanSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ByteSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ByteSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CharSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CharSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CompiledSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CompiledSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DateTimeSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DateTimeSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DecimalSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DecimalSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DefaultValueDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DoubleSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DoubleSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/EnumSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/EnumSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/FieldDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/FieldDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/GuidSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/GuidSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoTypeSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ISerializerProxy.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ISerializerProxy.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int16Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int16Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int32Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int32Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int64Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int64Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ListDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ListDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MapDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MapDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NetObjectSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NetObjectSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NullDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NullDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ParseableSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ParseableSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/PropertyDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/PropertyDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ProtoDecoratorBase.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ReflectedUriDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SByteSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SByteSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SingleSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SingleSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/StringSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/StringSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SubItemSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SubItemSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SurrogateSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SurrogateSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SystemTypeSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TagDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TagDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TimeSpanSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TupleSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TupleSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TypeSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TypeSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt16Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt16Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt32Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt32Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt64Serializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt64Serializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UriDecorator.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UriDecorator.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/XmlProtoSerializer.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/SubItemToken.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/SubItemToken.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/WireType.cs create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/WireType.cs.meta create mode 100644 Assets/GameScripts/ThirdParty/Protobuf-net/protobuf-net.csproj.meta create mode 100644 DotNet/App/NLog.config create mode 100644 DotNet/App/NLog.xsd create mode 100644 DotNet/App/Program.cs create mode 100644 DotNet/App/ProgramInfo.cs create mode 100644 DotNet/App/TEngineSettings.json create mode 100644 DotNet/Config/Binary/MachineConfigData.bytes create mode 100644 DotNet/Config/Binary/SceneConfigData.bytes create mode 100644 DotNet/Config/Binary/ServerConfigData.bytes create mode 100644 DotNet/Config/Binary/WorldConfigData.bytes create mode 100644 DotNet/Config/Excel/Server/MachineConfig.xlsx create mode 100644 DotNet/Config/Excel/Server/SceneConfig.xlsx create mode 100644 DotNet/Config/Excel/Server/ServerConfig.xlsx create mode 100644 DotNet/Config/Excel/Server/WorldConfig.xlsx create mode 100644 DotNet/Config/Excel/Version.txt create mode 100644 DotNet/Config/Json/Client/SceneConfigData.Json create mode 100644 DotNet/Config/Json/Server/MachineConfigData.Json create mode 100644 DotNet/Config/Json/Server/SceneConfigData.Json create mode 100644 DotNet/Config/Json/Server/ServerConfigData.Json create mode 100644 DotNet/Config/Json/Server/WorldConfigData.Json create mode 100644 DotNet/Config/ProtoBuf/InnerBsonMessage.proto create mode 100644 DotNet/Config/ProtoBuf/InnerMessage.proto create mode 100644 DotNet/Config/ProtoBuf/OuterMessage.proto create mode 100644 DotNet/Config/ProtoBuf/RouteType.Config create mode 100644 DotNet/Config/Template/ExcelTemplate.txt create mode 100644 DotNet/Config/Template/ProtoTemplate.txt delete mode 100644 DotNet/DotNet.sln rename DotNet/{Directory.Build.props => Server.Build.props} (100%) rename DotNet/{DotNet.sln.DotSettings => Server.sln.DotSettings} (100%) delete mode 100644 DotNet/ThirdParty/DotNet.ThirdParty.csproj create mode 100644 DotNet/ThirdParty/KCPLibrary/kcp.dll create mode 100644 DotNet/ThirdParty/KCPLibrary/kcp.dylib create mode 100644 DotNet/ThirdParty/KCPLibrary/kcp.so diff --git a/.gitignore b/.gitignore index f333547b..4d3ebdf0 100644 --- a/.gitignore +++ b/.gitignore @@ -122,10 +122,8 @@ Assets/UnityOnlineServiceData #Server_sln DotNet/.idea/ DotNet/App/obj/ -DotNet/Loader/obj/ DotNet/Core/obj/ -DotNet/Hotfix/obj/ -DotNet/Model/obj/ +DotNet/Logic/obj/ DotNet/ThirdParty/obj/ Bin/ \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask.meta b/Assets/GameScripts/DotNet/Core.meta similarity index 77% rename from Assets/GameScripts/ThirdParty/ETTask.meta rename to Assets/GameScripts/DotNet/Core.meta index 0ad9d567..1bda6d1c 100644 --- a/Assets/GameScripts/ThirdParty/ETTask.meta +++ b/Assets/GameScripts/DotNet/Core.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 618c9dd369f43e84296ac09b6f468e9d +guid: fdae3569c5cbfe54e97778a1f7e7af21 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/GameScripts/DotNet/Core/App.cs b/Assets/GameScripts/DotNet/Core/App.cs new file mode 100644 index 00000000..2d18606b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/App.cs @@ -0,0 +1,115 @@ +#if TENGINE_NET +using CommandLine; +using TEngine.Core; +using NLog; + +namespace TEngine +{ + public static class App + { + public static void Init() + { + try + { + // è®¾ç½®é»˜è®¤çš„çº¿ç¨‹çš„åŒæ­¥ä¸Šä¸‹æ–‡ + SynchronizationContext.SetSynchronizationContext(ThreadSynchronizationContext.Main); + // è§£æžå‘½ä»¤è¡Œå‚æ•° + Parser.Default.ParseArguments(Environment.GetCommandLineArgs()) + .WithNotParsed(error => throw new Exception("Command line format error!")) + .WithParsed(option => Define.Options = option); + // 检查å¯åЍ傿•° + switch (Define.Options.AppType) + { + case AppType.Game: + { + break; + } + case AppType.Export: + { + new Exporter().Start(); + return; + } + default: + { + throw new NotSupportedException($"AppType is {Define.Options.AppType} Unrecognized!"); + } + } + + // æ ¹æ®ä¸åŒçš„è¿è¡Œæ¨¡å¼æ¥é€‰æ‹©æ—¥å¿—çš„æ–¹å¼ + switch (Define.Options.Mode) + { + case Mode.Develop: + { + LogManager.Configuration.RemoveRuleByName("ConsoleTrace"); + LogManager.Configuration.RemoveRuleByName("ConsoleDebug"); + LogManager.Configuration.RemoveRuleByName("ConsoleInfo"); + LogManager.Configuration.RemoveRuleByName("ConsoleWarn"); + LogManager.Configuration.RemoveRuleByName("ConsoleError"); + + LogManager.Configuration.RemoveRuleByName("ServerDebug"); + LogManager.Configuration.RemoveRuleByName("ServerTrace"); + LogManager.Configuration.RemoveRuleByName("ServerInfo"); + LogManager.Configuration.RemoveRuleByName("ServerWarn"); + LogManager.Configuration.RemoveRuleByName("ServerError"); + break; + } + case Mode.Release: + { + LogManager.Configuration.RemoveRuleByName("ConsoleTrace"); + LogManager.Configuration.RemoveRuleByName("ConsoleDebug"); + LogManager.Configuration.RemoveRuleByName("ConsoleInfo"); + LogManager.Configuration.RemoveRuleByName("ConsoleWarn"); + LogManager.Configuration.RemoveRuleByName("ConsoleError"); + break; + } + } + + // åˆå§‹åŒ–SingletonSystemCenterè¿™ä¸ªä¸€å®šè¦æ”¾åˆ°æœ€å‰é¢ + // 因为SingletonSystem会注册AssemblyManagerçš„OnLoadAssemblyEventå’ŒOnUnLoadAssemblyEvent的事件 + // 如果ä¸è¿™æ ·ã€ä¼šæ— æ³•把程åºé›†çš„å•例注册到SingletonManager中 + SingletonSystem.Initialize(); + // 加载核心程åºé›† + AssemblyManager.Initialize(); + + Log.Info($"Start Server Param => {Parser.Default.FormatCommandLine(Define.Options)}"); + } + catch (Exception exception) + { + Log.Error(exception); + } + } + + public static async FTask Start() + { + switch (Define.Options.Mode) + { + case Mode.Develop: + { + // 开呿¨¡å¼é»˜è®¤æ‰€æœ‰Serveréƒ½åœ¨ä¸€ä¸ªè¿›ç¨‹ä¸­ã€æ–¹ä¾¿è°ƒè¯•ã€ä½†ç½‘络还都是独立的 + var serverConfigInfos = ConfigTableManage.AllServerConfig(); + + foreach (var serverConfig in serverConfigInfos) + { + await Server.Create(serverConfig.Id); + } + + return; + } + case Mode.Release: + { + // å‘布模å¼åªä¼šå¯åЍå¯åЍ傿•°ä¼ é€’çš„Serverã€ä¹Ÿå°±æ˜¯åªä¼šå¯åŠ¨ä¸€ä¸ªServer + // 您å¯ä»¥åšä¸€ä¸ªServer专门用于管ç†å¯åŠ¨æ‰€æœ‰Server的工作 + await Server.Create(Define.Options.AppId); + return; + } + } + } + + public static void Close() + { + SingletonSystem.Dispose(); + AssemblyManager.Dispose(); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs.meta b/Assets/GameScripts/DotNet/Core/App.cs.meta similarity index 83% rename from Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs.meta rename to Assets/GameScripts/DotNet/Core/App.cs.meta index 21ada269..2f2fa4d9 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs.meta +++ b/Assets/GameScripts/DotNet/Core/App.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 841f2f7b2ba44e24689645e00f9ed245 +guid: f6b4f60ee2b59b649835c9b25028d9e4 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameScripts/DotNet/Core/Assembly.meta b/Assets/GameScripts/DotNet/Core/Assembly.meta new file mode 100644 index 00000000..1554674a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Assembly.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9d838cbc15b0f034bb83f3aa5ea5b72d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs new file mode 100644 index 00000000..cf8e6939 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using TEngine.DataStructure; + +namespace TEngine.Core +{ + public sealed class AssemblyInfo + { + public Assembly Assembly { get; private set; } + public readonly List AssemblyTypeList = new List(); + public readonly OneToManyList AssemblyTypeGroupList = new OneToManyList(); + + public void Load(Assembly assembly) + { + Assembly = assembly; + var assemblyTypes = assembly.GetTypes().ToList(); + + foreach (var type in assemblyTypes) + { + if (type.IsAbstract || type.IsInterface) + { + continue; + } + + var interfaces = type.GetInterfaces(); + + foreach (var interfaceType in interfaces) + { + AssemblyTypeGroupList.Add(interfaceType, type); + } + } + + AssemblyTypeList.AddRange(assemblyTypes); + } + + public void Unload() + { + AssemblyTypeList.Clear(); + AssemblyTypeGroupList.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs.meta b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs.meta similarity index 83% rename from Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs.meta rename to Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs.meta index f450d71f..2d1bf2bf 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs.meta +++ b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyInfo.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a61c2db12429afb40b78a3f953e46510 +guid: aee73bf0d744afd439ef9b6a5d531951 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs new file mode 100644 index 00000000..6db25d36 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +#if TENGINE_NET +using System.Runtime.Loader; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#endif +#pragma warning disable CS8603 +#pragma warning disable CS8618 +namespace TEngine.Core +{ + public static class AssemblyManager + { + public static event Action OnLoadAssemblyEvent; + public static event Action OnUnLoadAssemblyEvent; + public static event Action OnReLoadAssemblyEvent; + private static readonly Dictionary AssemblyList = new Dictionary(); + + public static void Initialize() + { + LoadAssembly(int.MaxValue, typeof(AssemblyManager).Assembly); + } + + public static void LoadAssembly(int assemblyName, Assembly assembly) + { + var isReLoad = false; + + if (!AssemblyList.TryGetValue(assemblyName, out var assemblyInfo)) + { + assemblyInfo = new AssemblyInfo(); + AssemblyList.Add(assemblyName, assemblyInfo); + } + else + { + isReLoad = true; + assemblyInfo.Unload(); + + if (OnUnLoadAssemblyEvent != null) + { + OnUnLoadAssemblyEvent(assemblyName); + } + } + + assemblyInfo.Load(assembly); + + if (OnLoadAssemblyEvent != null) + { + OnLoadAssemblyEvent(assemblyName); + } + + if (isReLoad && OnReLoadAssemblyEvent != null) + { + OnReLoadAssemblyEvent(assemblyName); + } + } + + public static void Load(int assemblyName, Assembly assembly) + { + if (int.MaxValue == assemblyName) + { + throw new NotSupportedException("AssemblyName cannot be 2147483647"); + } + + LoadAssembly(assemblyName, assembly); + } + + public static IEnumerable ForEach() + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + foreach (var type in assemblyInfo.AssemblyTypeList) + { + yield return type; + } + } + } + + public static IEnumerable ForEach(int assemblyName) + { + if (!AssemblyList.TryGetValue(assemblyName, out var assemblyInfo)) + { + yield break; + } + + foreach (var type in assemblyInfo.AssemblyTypeList) + { + yield return type; + } + } + + public static IEnumerable ForEach(Type findType) + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad)) + { + yield break; + } + + foreach (var type in assemblyLoad) + { + yield return type; + } + } + } + + public static IEnumerable ForEach(int assemblyName, Type findType) + { + if (!AssemblyList.TryGetValue(assemblyName, out var assemblyInfo)) + { + yield break; + } + + if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad)) + { + yield break; + } + + foreach (var type in assemblyLoad) + { + yield return type; + } + } + + public static Assembly GetAssembly(int assemblyName) + { + return !AssemblyList.TryGetValue(assemblyName, out var assemblyInfo) ? null : assemblyInfo.Assembly; + } + + public static void Dispose() + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + assemblyInfo.Unload(); + } + + AssemblyList.Clear(); + + if (OnLoadAssemblyEvent != null) + { + foreach (var @delegate in OnLoadAssemblyEvent.GetInvocationList()) + { + OnLoadAssemblyEvent -= @delegate as Action; + } + } + + if (OnUnLoadAssemblyEvent != null) + { + foreach (var @delegate in OnUnLoadAssemblyEvent.GetInvocationList()) + { + OnUnLoadAssemblyEvent -= @delegate as Action; + } + } + + if (OnReLoadAssemblyEvent != null) + { + foreach (var @delegate in OnReLoadAssemblyEvent.GetInvocationList()) + { + OnReLoadAssemblyEvent -= @delegate as Action; + } + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs.meta b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs.meta similarity index 83% rename from Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs.meta rename to Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs.meta index 3d563ca8..91e2a27d 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs.meta +++ b/Assets/GameScripts/DotNet/Core/Assembly/AssemblyManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: bf2682587b9394aa89238524a4ef1ef9 +guid: 8d7a578f700ea034a9b98a5f63491eb3 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameScripts/DotNet/Core/CommandLineOptions.cs b/Assets/GameScripts/DotNet/Core/CommandLineOptions.cs new file mode 100644 index 00000000..6b077e52 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CommandLineOptions.cs @@ -0,0 +1,67 @@ +#if TENGINE_NET +using CommandLine; +using TEngine.Core; + +#pragma warning disable CS8618 + +namespace TEngine; + +public enum AppType +{ + Game, + + Export, + + /// + /// æ¯å°ç‰©ç†æœºä¸€ä¸ªå®ˆæŠ¤è¿›ç¨‹ï¼Œç”¨æ¥å¯åŠ¨è¯¥ç‰©ç†æœºä¸Šçš„æ‰€æœ‰è¿›ç¨‹ã€‚ + /// + Watcher, +} + + +public enum Mode +{ + /// + /// Develop:所有Server都在一个进程中,Release:æ¯ä¸ªServer都在独立的进程中。 + /// + Release, + + Develop, +} + +public class CommandLineOptions +{ + /// + /// 进程Id + /// + [Option("AppId", Required = false, Default = (uint)0, HelpText = "Enter an AppId such as 1")] + public uint AppId { get; set; } + + /// + /// App类型 + /// Game - æ¸¸æˆæœåС噍App + /// Export - 导表App + /// + [Option("AppType", Required = false, Default = AppType.Game, HelpText = "AppType enum")] + public AppType AppType { get; set; } + + /// + /// æœåС噍è¿è¡Œæ¨¡å¼ + /// Develop - 开呿¨¡å¼ï¼ˆæ‰€æœ‰Server都在一个进程中) + /// Release - å‘布模å¼ï¼ˆæ¯ä¸ªServer都在独立的进程中) + /// + [Option("Mode", Required = false, Default = Mode.Develop, HelpText = "Mode enum")] + public Mode Mode { get; set; } + + [Option("LogLevel", Required = false, Default = 2)] + public int LogLevel { get; set; } + +#if TENGINE_NET + /// + /// 导表的类型 + /// + [Option("ExcelExportType", Required = false, Default = ExportType.None, HelpText = "Increment,All")] + public ExportType ExportType { get; set; } +#endif +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs.meta b/Assets/GameScripts/DotNet/Core/CommandLineOptions.cs.meta similarity index 83% rename from Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs.meta rename to Assets/GameScripts/DotNet/Core/CommandLineOptions.cs.meta index 702bed32..a1467fa8 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs.meta +++ b/Assets/GameScripts/DotNet/Core/CommandLineOptions.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9a7cde4b1a6c34b49a309698cdfb233f +guid: c644dc25b4098d24683e949b5a14f754 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameScripts/DotNet/Core/CoreErrorCode.cs b/Assets/GameScripts/DotNet/Core/CoreErrorCode.cs new file mode 100644 index 00000000..76f6ff45 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoreErrorCode.cs @@ -0,0 +1,10 @@ +namespace TEngine.Core +{ + public class CoreErrorCode + { + public const int ErrRpcFail = 100000002; // Rpc消æ¯å‘é€å¤±è´¥ + public const int ErrNotFoundRoute = 100000003; // 没有找到Routeæ¶ˆæ¯ + public const int ErrRouteTimeout = 100000004; // å‘é€Route消æ¯è¶…æ—¶ + public const int Error_NotFindEntity = 100000008; // 没有找到Entity + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/CoreErrorCode.cs.meta b/Assets/GameScripts/DotNet/Core/CoreErrorCode.cs.meta new file mode 100644 index 00000000..250051d3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoreErrorCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0b2fb99ac2580c418659e6a1328206b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock.meta b/Assets/GameScripts/DotNet/Core/CoroutineLock.meta new file mode 100644 index 00000000..ef9cb45c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21519f62191bd1447bfafd815de9046d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs new file mode 100644 index 00000000..13a68e35 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.Core +{ + public sealed class CoroutineLockQueue : IDisposable + { + public long Key { get; private set; } + public CoroutineLockQueueType CoroutineLockQueueType { get; private set; } + private readonly Queue _waitCoroutineLocks = new Queue(); + + public static CoroutineLockQueue Create(long key, int time, CoroutineLockQueueType coroutineLockQueueType) + { + var coroutineLockQueue = Pool.Rent(); + coroutineLockQueue.Key = key; + coroutineLockQueue.CoroutineLockQueueType = coroutineLockQueueType; + return coroutineLockQueue; + } + + public void Dispose() + { + Key = 0; + CoroutineLockQueueType = null; + Pool.Return(this); + } + + public async FTask Lock(string tag, int time) + { +#if TENGINE_DEVELOP + if (_waitCoroutineLocks.Count >= 100) + { + // 当等待队列超过100个ã€è¡¨ç¤ºè¿™ä¸ªå程é”å¯èƒ½æœ‰é—®é¢˜ã€æ‰“å°ä¸€ä¸ªè­¦å‘Šæ–¹ä¾¿æŽ’查错误 + Log.Warning($"too much waitCoroutineLock CoroutineLockQueueType:{CoroutineLockQueueType.Name} Key:{Key} Count: {_waitCoroutineLocks.Count} "); + } +#endif + var waitCoroutineLock = WaitCoroutineLock.Create(this, tag, time); + _waitCoroutineLocks.Enqueue(waitCoroutineLock); + return await waitCoroutineLock.Tcs; + } + + public void Release() + { + if (_waitCoroutineLocks.Count == 0) + { + CoroutineLockQueueType.Remove(Key); + return; + } + + while (_waitCoroutineLocks.TryDequeue(out var waitCoroutineLock)) + { + if (waitCoroutineLock.IsDisposed) + { + continue; + } + + waitCoroutineLock.SetResult(); + break; + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs.meta b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs.meta new file mode 100644 index 00000000..77d1b02d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f07b80619d455d4499aefbc3eab9f65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs new file mode 100644 index 00000000..fabaac49 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.Core +{ + public sealed class CoroutineLockQueueType + { + public readonly string Name; + private readonly Dictionary _coroutineLockQueues = new Dictionary(); + + private CoroutineLockQueueType() { } + public CoroutineLockQueueType(string name) + { + Name = name; + } + + public async FTask Lock(long key, string tag = null, int time = 10000) + { + if (_coroutineLockQueues.TryGetValue(key, out var coroutineLockQueue)) + { + return await coroutineLockQueue.Lock(tag,time); + } + + coroutineLockQueue = CoroutineLockQueue.Create(key, time, this); + _coroutineLockQueues.Add(key, coroutineLockQueue); + return WaitCoroutineLock.Create(coroutineLockQueue, tag, time); + } + + public void Remove(long key) + { + if (_coroutineLockQueues.Remove(key, out var coroutineLockQueue)) + { + coroutineLockQueue.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs.meta b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs.meta new file mode 100644 index 00000000..f582eb35 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock/CoroutineLockQueueType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2067a4304a800324bbe9c547eefac9fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs b/Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs new file mode 100644 index 00000000..97bc7bf6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs @@ -0,0 +1,93 @@ +using System; +using TEngine.Core; + +namespace TEngine.Core +{ + public struct CoroutineLockTimeout + { + public long LockId; + public WaitCoroutineLock WaitCoroutineLock; + } + + public sealed class OnCoroutineLockTimeout : EventSystem + { + public override void Handler(CoroutineLockTimeout self) + { + if (self.LockId != self.WaitCoroutineLock.LockId) + { + return; + } + + var coroutineLockQueue = self.WaitCoroutineLock.CoroutineLockQueue; + var coroutineLockQueueType = coroutineLockQueue.CoroutineLockQueueType; + var key = coroutineLockQueue.Key; + Log.Error($"coroutine lock timeout CoroutineLockQueueType:{coroutineLockQueueType.Name} Key:{key} Tag:{self.WaitCoroutineLock.Tag}"); + } + } + + public sealed class WaitCoroutineLock : IDisposable + { + private long _timerId; + public bool IsDisposed => LockId == 0; + public string Tag { get; private set; } + public long LockId { get; private set; } + public FTask Tcs { get; private set; } + public CoroutineLockQueue CoroutineLockQueue{ get; private set; } + + public static WaitCoroutineLock Create(CoroutineLockQueue coroutineLockQueue, string tag, int timeOut) + { + var lockId = IdFactory.NextRunTimeId(); + var waitCoroutineLock = Pool.Rent(); + + waitCoroutineLock.Tag = tag; + waitCoroutineLock.LockId = lockId; + waitCoroutineLock.CoroutineLockQueue = coroutineLockQueue; + waitCoroutineLock.Tcs = FTask.Create(); + waitCoroutineLock._timerId = TimerScheduler.Instance.Core.OnceTimer(timeOut, new CoroutineLockTimeout() + { + LockId = lockId, WaitCoroutineLock = waitCoroutineLock + }); + + return waitCoroutineLock; + } + + public void Dispose() + { + if (IsDisposed) + { + Log.Error("WaitCoroutineLock is Dispose"); + return; + } + + Release(CoroutineLockQueue); + + LockId = 0; + Tcs = null; + Tag = null; + CoroutineLockQueue = null; + + if (_timerId != 0) + { + TimerScheduler.Instance.Core.RemoveByRef(ref _timerId); + } + + Pool.Return(this); + } + + private static void Release(CoroutineLockQueue coroutineLockQueue) + { + // 放到下一帧执行释放é”ã€å¦‚æžœä¸è¿™æ ·ã€ä¼šå¯¼è‡´é€»è¾‘的顺åºä¸æ­£å¸¸ + ThreadSynchronizationContext.Main.Post(coroutineLockQueue.Release); + } + + public void SetResult() + { + if (Tcs == null) + { + throw new NullReferenceException("SetResult tcs is null"); + } + + Tcs.SetResult(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs.meta b/Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs.meta new file mode 100644 index 00000000..86602f5a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/CoroutineLock/WaitCoroutineLock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ee0d61ec53909c4695fe2ecc97f42bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataBase.meta b/Assets/GameScripts/DotNet/Core/DataBase.meta new file mode 100644 index 00000000..862bcd66 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9f6012de0e164ce4da102c93fa2df9ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base.meta b/Assets/GameScripts/DotNet/Core/DataBase/Base.meta new file mode 100644 index 00000000..83280e41 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd10fb6ee217e1e4a82db518e161aaec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs b/Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs new file mode 100644 index 00000000..986ca79e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs @@ -0,0 +1,45 @@ +#if TENGINE_NET +using System.Linq.Expressions; +#pragma warning disable CS8625 + +namespace TEngine.Core.DataBase; + +public interface IDateBase +{ + IDateBase Initialize(string connectionString, string dbName); + FTask Count(string collection = null) where T : Entity; + FTask Count(Expression> filter, string collection = null) where T : Entity; + FTask Exist(string collection = null) where T : Entity; + FTask Exist(Expression> filter, string collection = null) where T : Entity; + FTask Query(long id, string collection = null) where T : Entity; + FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity; + FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity; + FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity; + FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity; + FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity; + FTask First(Expression> filter, string collection = null) where T : Entity; + FTask First(string json, string[] cols, string collection = null) where T : Entity; + FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity; + FTask> Query(Expression> filter, string collection = null) where T : Entity; + FTask Query(long id, List collectionNames, List result); + FTask> QueryJson(string json, string collection = null) where T : Entity; + FTask> QueryJson(string json, string[] cols, string collection = null) where T : Entity; + FTask> QueryJson(long taskId, string json, string collection = null) where T : Entity; + FTask> Query(Expression> filter, string[] cols, string collection = null) where T : class; + FTask Save(T entity, string collection = null) where T : Entity, new(); + FTask Save(long id, List entities); + FTask Save(object transactionSession, T entity, string collection = null) where T : Entity; + FTask Insert(T entity, string collection = null) where T : Entity, new(); + FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new(); + FTask InsertBatch(object transactionSession, IEnumerable list, string collection = null) where T : Entity, new(); + FTask Remove(object transactionSession, long id, string collection = null) where T : Entity, new(); + FTask Remove(long id, string collection = null) where T : Entity, new(); + FTask Remove(long id,object transactionSession, Expression> filter, string collection = null) where T : Entity, new(); + FTask Remove(long id, Expression> filter, string collection = null) where T : Entity, new(); + FTask Sum(Expression> filter, Expression> sumExpression, string collection = null) where T : Entity; + FTask CreateIndex(string collection, params object[] keys) where T : Entity; + FTask CreateIndex(params object[] keys) where T : Entity; + FTask CreateDB() where T : Entity; + FTask CreateDB(Type type); +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs.meta b/Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs.meta new file mode 100644 index 00000000..7b7158f2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base/IDateBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 801a4f2fbf5d7944480423ade9d8a998 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs b/Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs new file mode 100644 index 00000000..123893b6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs @@ -0,0 +1,42 @@ +#if TENGINE_NET +namespace TEngine.Core.DataBase; + +public sealed class World +{ + public uint Id { get; private init; } + public IDateBase DateBase { get; private init; } + public WorldConfigInfo Config => ConfigTableManage.WorldConfigInfo(Id); + private static readonly Dictionary Worlds = new(); + + public World(WorldConfigInfo worldConfigInfo) + { + Id = worldConfigInfo.Id; + var dbType = worldConfigInfo.DbType.ToLower(); + + switch (dbType) + { + case "mongodb": + { + DateBase = new MongoDataBase(); + DateBase.Initialize(worldConfigInfo.DbConnection, worldConfigInfo.DbName); + break; + } + default: + throw new Exception("No supported database"); + } + } + + public static World Create(uint id) + { + if (Worlds.TryGetValue(id, out var world)) + { + return world; + } + + var worldConfigInfo = ConfigTableManage.WorldConfigInfo(id); + world = new World(worldConfigInfo); + Worlds.Add(id, world); + return world; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs.meta b/Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs.meta new file mode 100644 index 00000000..eb2803da --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base/World.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e207b62400a4d2742945b50eb84f8233 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs b/Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs new file mode 100644 index 00000000..f54b36bb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs @@ -0,0 +1,12 @@ +#if TENGINE_NET +namespace TEngine.Core.DataBase; + +public class WorldConfigInfo +{ + public uint Id; + public string WorldName; + public string DbConnection; + public string DbName; + public string DbType; +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs.meta b/Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs.meta new file mode 100644 index 00000000..25691db1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/Base/WorldConfigInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c1d63a41ee591348a2aeab0a1ff5b2f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs b/Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs new file mode 100644 index 00000000..5ddab076 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs @@ -0,0 +1,539 @@ +#if TENGINE_NET +using System.Linq.Expressions; +using TEngine.Core; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace TEngine.Core.DataBase; + +public sealed class MongoDataBase : IDateBase +{ + private string _dbName; + private string _connectionString; + private MongoClient _mongoClient; + private IMongoDatabase _mongoDatabase; + private readonly HashSet _collections = new HashSet(); + private readonly CoroutineLockQueueType _mongoDataBaseLock = new CoroutineLockQueueType("MongoDataBaseLock"); + + public IDateBase Initialize(string connectionString, string dbName) + { + _dbName = dbName; + _connectionString = connectionString; + _mongoClient = new MongoClient(connectionString); + _mongoDatabase = _mongoClient.GetDatabase(dbName); + // 记录所有集åˆå + _collections.UnionWith(_mongoDatabase.ListCollectionNames().ToList()); + return this; + } + + #region Other + + public async FTask Sum(Expression> filter, Expression> sumExpression, string collection = null) where T : Entity + { + var member = (MemberExpression) ((UnaryExpression) sumExpression.Body).Operand; + + var projection = + new BsonDocument("_id", "null").Add("Result", new BsonDocument("$sum", $"${member.Member.Name}")); + + var data = await GetCollection(collection).Aggregate().Match(filter).Group(projection) + .FirstOrDefaultAsync(); + + return data == null ? 0 : Convert.ToInt64(data["Result"]); + } + + #endregion + + #region GetCollection + + private IMongoCollection GetCollection(string collection = null) + { + return _mongoDatabase.GetCollection(collection ?? typeof(T).Name); + } + + private IMongoCollection GetCollection(string name) + { + return _mongoDatabase.GetCollection(name); + } + + #endregion + + #region Count + + public async FTask Count(string collection = null) where T : Entity + { + return await GetCollection(collection).CountDocumentsAsync(d => true); + } + + public async FTask Count(Expression> filter, string collection = null) where T : Entity + { + return await GetCollection(collection).CountDocumentsAsync(filter); + } + + #endregion + + #region Exist + + public async FTask Exist(string collection = null) where T : Entity + { + return await Count(collection) > 0; + } + + public async FTask Exist(Expression> filter, string collection = null) where T : Entity + { + return await Count(filter, collection) > 0; + } + + #endregion + + #region Query + + public async FTask Query(long id, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(id)) + { + var cursor = await GetCollection(collection).FindAsync(d => d.Id == id); + var v = await cursor.FirstOrDefaultAsync(); + return v; + } + } + + public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var count = await Count(filter); + var dates = await QueryByPage(filter, pageIndex, pageSize, collection); + return ((int)count, dates); + } + } + + public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var count = await Count(filter); + + var dates = await QueryByPage(filter, pageIndex, pageSize, cols, collection); + + return ((int) count, dates); + } + } + + public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + return await GetCollection(collection).Find(filter).Skip((pageIndex - 1) * pageSize) + .Limit(pageSize) + .ToListAsync(); + } + } + + public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var projection = Builders.Projection.Include(""); + + foreach (var col in cols) + { + projection = projection.Include(col); + } + + return await GetCollection(collection).Find(filter).Project(projection) + .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); + } + } + + public async FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + if (isAsc) + { + return await GetCollection(collection).Find(filter).SortBy(orderByExpression) + .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); + } + + return await GetCollection(collection).Find(filter).SortByDescending(orderByExpression) + .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); + } + } + + public async FTask First(Expression> filter, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var cursor = await GetCollection(collection).FindAsync(filter); + + return await cursor.FirstOrDefaultAsync(); + } + } + + public async FTask First(string json, string[] cols, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var projection = Builders.Projection.Include(""); + + foreach (var col in cols) + { + projection = projection.Include(col); + } + + var options = new FindOptions {Projection = projection}; + + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + + var cursor = await GetCollection(collection).FindAsync(filterDefinition, options); + + return await cursor.FirstOrDefaultAsync(); + } + } + + public async FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + if (isAsc) + { + return await GetCollection(collection).Find(filter).SortBy(orderByExpression).ToListAsync(); + } + + return await GetCollection(collection).Find(filter).SortByDescending(orderByExpression) + .ToListAsync(); + } + } + + public async FTask> Query(Expression> filter, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var cursor = await GetCollection(collection).FindAsync(filter); + var v = await cursor.ToListAsync(); + return v; + } + } + + public async FTask Query(long id, List collectionNames, List result) + { + using (await _mongoDataBaseLock.Lock(id)) + { + if (collectionNames == null || collectionNames.Count == 0) + { + return; + } + + foreach (var collectionName in collectionNames) + { + var cursor = await GetCollection(collectionName).FindAsync(d => d.Id == id); + + var e = await cursor.FirstOrDefaultAsync(); + + if (e == null) + { + continue; + } + + result.Add(e); + } + } + } + + public async FTask> QueryJson(string json, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + var cursor = await GetCollection(collection).FindAsync(filterDefinition); + var v = await cursor.ToListAsync(); + return v; + } + } + + public async FTask> QueryJson(string json, string[] cols, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var projection = Builders.Projection.Include(""); + + foreach (var col in cols) + { + projection = projection.Include(col); + } + + var options = new FindOptions {Projection = projection}; + + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + + var cursor = await GetCollection(collection).FindAsync(filterDefinition, options); + var v = await cursor.ToListAsync(); + return v; + } + } + + public async FTask> QueryJson(long taskId, string json, string collection = null) where T : Entity + { + using (await _mongoDataBaseLock.Lock(taskId)) + { + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + var cursor = await GetCollection(collection).FindAsync(filterDefinition); + var v = await cursor.ToListAsync(); + return v; + } + } + + public async FTask> Query(Expression> filter, string[] cols, string collection = null) where T : class + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + var projection = Builders.Projection.Include(cols[0]); + + for (var i = 1; i < cols.Length; i++) + { + projection = projection.Include(cols[i]); + } + + return await GetCollection(collection).Find(filter).Project(projection).ToListAsync(); + } + } + + #endregion + + #region Save + + public async FTask Save(object transactionSession, T entity, string collection = null) where T : Entity + { + if (entity == null) + { + Log.Error($"save entity is null: {typeof(T).Name}"); + return; + } + + var clone = MongoHelper.Instance.Clone(entity); + + using (await _mongoDataBaseLock.Lock(clone.Id)) + { + await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync( + (IClientSessionHandle) transactionSession, d => d.Id == clone.Id, clone, + new ReplaceOptions {IsUpsert = true}); + } + } + + public async FTask Save(T entity, string collection = null) where T : Entity, new() + { + if (entity == null) + { + Log.Error($"save entity is null: {typeof(T).Name}"); + + return; + } + + var clone = MongoHelper.Instance.Clone(entity); + + using (await _mongoDataBaseLock.Lock(clone.Id)) + { + await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone, + new ReplaceOptions {IsUpsert = true}); + } + } + + private async FTask SaveBase(T entity, string collection = null) where T : Entity + { + if (entity == null) + { + Log.Error($"save entity is null: {typeof(T).Name}"); + + return; + } + + var clone = MongoHelper.Instance.Clone(entity); + + using (await _mongoDataBaseLock.Lock(clone.Id)) + { + await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone, new ReplaceOptions {IsUpsert = true}); + } + } + + public async FTask Save(long id, List entities) + { + if (entities == null) + { + Log.Error("save entity is null"); + return; + } + + var clones = MongoHelper.Instance.Clone(entities); + + using (await _mongoDataBaseLock.Lock(id)) + { + foreach (Entity clone in clones) + { + try + { + await GetCollection(clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone, + new ReplaceOptions {IsUpsert = true}); + } + catch (Exception e) + { + Log.Error($"Save List Entity Error: {clone.GetType().Name} {clone}\n{e}"); + } + } + } + } + + #endregion + + #region Insert + + public FTask Insert(T entity, string collection = null) where T : Entity, new() + { + return Save(entity); + } + + public async FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new() + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + await GetCollection(collection ?? typeof(T).Name).InsertManyAsync(list); + } + } + + public async FTask InsertBatch(object transactionSession, IEnumerable list, string collection = null) where T : Entity, new() + { + using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64())) + { + await GetCollection(collection ?? typeof(T).Name).InsertManyAsync((IClientSessionHandle) transactionSession, list); + } + } + + #endregion + + #region Remove + + public async FTask Remove(object transactionSession, long id, string collection = null) where T : Entity, new() + { + using (await _mongoDataBaseLock.Lock(id)) + { + var result = await GetCollection(collection).DeleteOneAsync((IClientSessionHandle) transactionSession, d => d.Id == id); + return result.DeletedCount; + } + } + + public async FTask Remove(long id, string collection = null) where T : Entity, new() + { + using (await _mongoDataBaseLock.Lock(id)) + { + var result = await GetCollection(collection).DeleteOneAsync(d => d.Id == id); + return result.DeletedCount; + } + } + + public async FTask Remove(long id, object transactionSession, Expression> filter, string collection = null) where T : Entity, new() + { + using (await _mongoDataBaseLock.Lock(id)) + { + var result = await GetCollection(collection).DeleteManyAsync((IClientSessionHandle) transactionSession, filter); + return result.DeletedCount; + } + } + + public async FTask Remove(long id, Expression> filter, string collection = null) where T : Entity, new() + { + using (await _mongoDataBaseLock.Lock(id)) + { + var result = await GetCollection(collection).DeleteManyAsync(filter); + return result.DeletedCount; + } + } + + #endregion + + #region Index + + /// + /// 创建数æ®åº“索引 + /// + /// + /// + /// + /// + /// 使用例å­(å¯å¤šä¸ª): + /// 1 : Builders.IndexKeys.Ascending(d=>d.Id) + /// 2 : Builders.IndexKeys.Descending(d=>d.Id).Ascending(d=>d.Name) + /// 3 : Builders.IndexKeys.Descending(d=>d.Id),Builders.IndexKeys.Descending(d=>d.Name) + /// + public async FTask CreateIndex(string collection, params object[] keys) where T : Entity + { + if (keys == null || keys.Length <= 0) + { + return; + } + + var indexModels = new List>(); + + foreach (object key in keys) + { + IndexKeysDefinition indexKeysDefinition = (IndexKeysDefinition) key; + + indexModels.Add(new CreateIndexModel(indexKeysDefinition)); + } + + await GetCollection(collection).Indexes.CreateManyAsync(indexModels); + } + + public async FTask CreateIndex(params object[] keys) where T : Entity + { + if (keys == null) + { + return; + } + + List> indexModels = new List>(); + + foreach (object key in keys) + { + IndexKeysDefinition indexKeysDefinition = (IndexKeysDefinition) key; + + indexModels.Add(new CreateIndexModel(indexKeysDefinition)); + } + + await GetCollection().Indexes.CreateManyAsync(indexModels); + } + + #endregion + + #region CreateDB + + public async FTask CreateDB() where T : Entity + { + // å·²ç»å­˜åœ¨æ•°æ®åº“表 + string name = typeof(T).Name; + + if (_collections.Contains(name)) + { + return; + } + + await _mongoDatabase.CreateCollectionAsync(name); + + _collections.Add(name); + } + + public async FTask CreateDB(Type type) + { + string name = type.Name; + + if (_collections.Contains(name)) + { + return; + } + + await _mongoDatabase.CreateCollectionAsync(name); + + _collections.Add(name); + } + + #endregion +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs.meta b/Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs.meta new file mode 100644 index 00000000..7a37758a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataBase/MongoDataBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0aaa971dc58538e46b0e43df7cc72a2f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure.meta b/Assets/GameScripts/DotNet/Core/DataStructure.meta new file mode 100644 index 00000000..36e901a7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ddb0edd718602754083d07d649e65d8f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection.meta new file mode 100644 index 00000000..6887d895 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e9c6ac6ade3864b42887435f6eac263f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs new file mode 100644 index 00000000..22c86792 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.IO; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.DataStructure +{ + /// + /// 环形缓存(自动扩充ã€ä¸ä¼šæ”¶ç¼©ç¼“å­˜ã€æ‰€ä»¥ä¸è¦ç”¨è¿™ä¸ªæ“作过大的IOæµï¼‰ + /// 1ã€çޝ大å°8192,溢出的会自动增加环的大å°ã€‚ + /// 2ã€æ¯ä¸ªå—都是一个环形缓存,当溢出的时候会自动添加到下一个环中。 + /// 3ã€å½“读å–完æˆåŽç”¨è¿‡çš„环会放在缓存中,ä¸ä¼šé”€æ¯æŽ‰ã€‚ + /// + public sealed class CircularBuffer : Stream, IDisposable + { + private byte[] _lastBuffer; + public const int ChunkSize = 8192; // 环形缓存å—å¤§å° + private readonly Queue _bufferCache = new Queue(); + private readonly Queue _bufferQueue = new Queue(); + public int FirstIndex { get; set; } + public int LastIndex { get; set; } + public override long Length + { + get + { + if (_bufferQueue.Count == 0) + { + return 0; + } + + return (_bufferQueue.Count - 1) * ChunkSize + LastIndex - FirstIndex; + } + } + public byte[] First + { + get + { + if (_bufferQueue.Count == 0) + { + AddLast(); + } + + return _bufferQueue.Peek(); + } + } + public byte[] Last + { + get + { + if (_bufferQueue.Count == 0) + { + AddLast(); + } + + return _lastBuffer; + } + } + + public void AddLast() + { + var buffer = _bufferCache.Count > 0 ? _bufferCache.Dequeue() : new byte[ChunkSize]; + _bufferQueue.Enqueue(buffer); + _lastBuffer = buffer; + } + + public void RemoveFirst() + { + _bufferCache.Enqueue(_bufferQueue.Dequeue()); + } + + public void Read(Stream stream, int count) + { + if (count > Length) + { + throw new Exception($"bufferList length < count, {Length} {count}"); + } + + var copyCount = 0; + while (copyCount < count) + { + var n = count - copyCount; + if (ChunkSize - FirstIndex > n) + { + stream.Write(First, FirstIndex, n); + FirstIndex += n; + copyCount += n; + } + else + { + stream.Write(First, FirstIndex, ChunkSize - FirstIndex); + copyCount += ChunkSize - FirstIndex; + FirstIndex = 0; + RemoveFirst(); + } + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer.Length < offset + count) + { + throw new Exception($"buffer length < count, buffer length: {buffer.Length} {offset} {count}"); + } + + var length = Length; + if (length < count) + { + count = (int) length; + } + + var copyCount = 0; + while (copyCount < count) + { + var copyLength = count - copyCount; + + if (ChunkSize - FirstIndex > copyLength) + { + Array.Copy(First, FirstIndex, buffer, copyCount + offset, copyLength); + + FirstIndex += copyLength; + copyCount += copyLength; + continue; + } + + Array.Copy(First, FirstIndex, buffer, copyCount + offset, ChunkSize - FirstIndex); + + copyCount += ChunkSize - FirstIndex; + FirstIndex = 0; + + RemoveFirst(); + } + + return count; + } + + public void Write(byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + public void Write(Stream stream) + { + var copyCount = 0; + var count = (int) (stream.Length - stream.Position); + + while (copyCount < count) + { + if (LastIndex == ChunkSize) + { + AddLast(); + LastIndex = 0; + } + + var n = count - copyCount; + + if (ChunkSize - LastIndex > n) + { + _ = stream.Read(Last, LastIndex, n); + LastIndex += count - copyCount; + copyCount += n; + } + else + { + _ = stream.Read(Last, LastIndex, ChunkSize - LastIndex); + copyCount += ChunkSize - LastIndex; + LastIndex = ChunkSize; + } + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + var copyCount = 0; + + while (copyCount < count) + { + if (ChunkSize == LastIndex) + { + AddLast(); + LastIndex = 0; + } + + var byteLength = count - copyCount; + + if (ChunkSize - LastIndex > byteLength) + { + Array.Copy(buffer, copyCount + offset, Last, LastIndex, byteLength); + LastIndex += byteLength; + copyCount += byteLength; + } + else + { + Array.Copy(buffer, copyCount + offset, _lastBuffer, LastIndex, ChunkSize - LastIndex); + copyCount += ChunkSize - LastIndex; + LastIndex = ChunkSize; + } + } + } + + public override bool CanRead { get; } = true; + public override bool CanSeek { get; } = false; + public override bool CanWrite { get; } = true; + public override long Position { get; set; } + + public override void Flush() + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public new void Dispose() + { + _bufferQueue.Clear(); + _lastBuffer = null; + FirstIndex = 0; + LastIndex = 0; + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs.meta new file mode 100644 index 00000000..ae54acd9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28188b5e2acefe14a996fb93df6b2fc6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs new file mode 100644 index 00000000..a91d8297 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class ConcurrentOneToManyListPool : ConcurrentOneToManyList, IDisposable where TKey : notnull + { + private bool _isDispose; + + public static ConcurrentOneToManyListPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class ConcurrentOneToManyList : ConcurrentDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + + public ConcurrentOneToManyList() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public ConcurrentOneToManyList(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + base[key] = list; + return; + } + + list.Add(value); + } + + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + + public void RemoveKey(TKey key) + { + if (!TryRemove(key, out var list)) return; + + Recycle(list); + } + + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs.meta new file mode 100644 index 00000000..1c3e2821 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f35e267f0b797464e856a9b9244b4215 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs new file mode 100644 index 00000000..177aac11 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class ConcurrentOneToManyQueuePool : ConcurrentOneToManyQueue, IDisposable + where TKey : notnull + { + private bool _isDispose; + + public static ConcurrentOneToManyQueuePool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class ConcurrentOneToManyQueue : ConcurrentDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public ConcurrentOneToManyQueue(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Enqueue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Enqueue(value); + TryAdd(key, list); + return; + } + + list.Enqueue(value); + } + + public TValue Dequeue(TKey key) + { + if (!TryGetValue(key, out var list) || list.Count == 0) return default; + + var value = list.Dequeue(); + + if (list.Count == 0) RemoveKey(key); + + return value; + } + + public bool TryDequeue(TKey key, out TValue value) + { + value = Dequeue(key); + + return value != null; + } + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + TryRemove(key, out _); + Recycle(list); + } + + private Queue Fetch() + { + return _queue.Count <= 0 ? new Queue() : _queue.Dequeue(); + } + + private void Recycle(Queue list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs.meta new file mode 100644 index 00000000..70c46829 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ConcurrentOneToManyQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 162f7b8459ead1940bfaef049f6d8e2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs new file mode 100644 index 00000000..4dc36284 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class EntityList : List, IDisposable where T : IDisposable + { + private bool _isDispose; + + public static EntityList Create() + { + var list = Pool>.Rent(); + list._isDispose = false; + return list; + } + + public new void Clear() + { + for (var i = 0; i < this.Count; i++) + { + this[i].Dispose(); + } + + base.Clear(); + } + + public void ClearNotDispose() + { + base.Clear(); + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs.meta new file mode 100644 index 00000000..5dcf0472 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/EntityList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 763e9615aee0ac1428f141df39057bf7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs new file mode 100644 index 00000000..971b9fc2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class HashSetPool : HashSet, IDisposable + { + private bool _isDispose; + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + + public static HashSetPool Create() + { + var list = Pool>.Rent(); + list._isDispose = false; + return list; + } + } + + public sealed class HashSetBasePool : IDisposable + { + public HashSet Set = new HashSet(); + + public static HashSetBasePool Create() + { + return Pool>.Rent(); + } + + public void Dispose() + { + Set.Clear(); + Pool>.Return(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs.meta new file mode 100644 index 00000000..0c7c2d3d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/HashSetPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f2f1a1f1a3f5f246a40be1e7bff871a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs new file mode 100644 index 00000000..e7960911 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class ListPool : List, IDisposable + { + private bool _isDispose; + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + + public static ListPool Create(params T[] args) + { + var list = Pool>.Rent(); + list._isDispose = false; + if (args != null) list.AddRange(args); + return list; + } + + public static ListPool Create(List args) + { + var list = Pool>.Rent(); + list._isDispose = false; + if (args != null) list.AddRange(args); + return list; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs.meta new file mode 100644 index 00000000..8d0fa06c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ListPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3014ce89f46d33742acca54a28995242 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs new file mode 100644 index 00000000..3cd6317d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +#pragma warning disable CS8600 + +namespace TEngine.DataStructure +{ + public class OneToManyHashSetPool : OneToManyHashSet, IDisposable where TKey : notnull + { + private bool _isDispose; + + public static OneToManyHashSetPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class OneToManyHashSet : Dictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + private static HashSet _empty = new HashSet(); + + public OneToManyHashSet() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public OneToManyHashSet(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + Recycle(list); + } + + public HashSet GetValue(TKey key) + { + if (TryGetValue(key, out HashSet value)) + { + return value; + } + + return _empty; + } + + private HashSet Fetch() + { + return _queue.Count <= 0 ? new HashSet() : _queue.Dequeue(); + } + + private void Recycle(HashSet list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs.meta new file mode 100644 index 00000000..a8ee7fc7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyHashSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 393302e735c4b2f4885c21dd4235b64a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs new file mode 100644 index 00000000..35d29353 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#pragma warning disable CS8600 +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class OneToManyListPool : OneToManyList, IDisposable where TKey : notnull + { + private bool _isDispose; + + public static OneToManyListPool Create() + { + var list = Pool>.Rent(); + list._isDispose = false; + return list; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class OneToManyList : Dictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + private static List _empty = new List(); + + public OneToManyList() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public OneToManyList(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + public bool RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + return true; + } + + var isRemove = list.Remove(value); + + if (list.Count == 0) + { + isRemove = RemoveByKey(key); + } + + return isRemove; + } + + public bool RemoveByKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return false; + } + + Remove(key); + Recycle(list); + return true; + } + + public List GetValues(TKey key) + { + if (TryGetValue(key, out List list)) + { + return list; + } + + return _empty; + } + + public new void Clear() + { + foreach (var keyValuePair in this) Recycle(keyValuePair.Value); + + base.Clear(); + } + + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs.meta new file mode 100644 index 00000000..c548cb74 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a6fdf7c8423c0e41804022df95a399d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs new file mode 100644 index 00000000..6fb4580e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class OneToManyQueuePool : OneToManyQueue, IDisposable where TKey : notnull + { + private bool _isDispose; + + public static OneToManyQueuePool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class OneToManyQueue : Dictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public OneToManyQueue(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Enqueue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Enqueue(value); + Add(key, list); + return; + } + + list.Enqueue(value); + } + + public TValue Dequeue(TKey key) + { + if (!TryGetValue(key, out var list) || list.Count == 0) + { + return default; + } + + var value = list.Dequeue(); + + if (list.Count == 0) + { + RemoveKey(key); + } + + return value; + } + + public bool TryDequeue(TKey key, out TValue value) + { + value = Dequeue(key); + + return value != null; + } + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + Recycle(list); + } + + private Queue Fetch() + { + return _queue.Count <= 0 ? new Queue() : _queue.Dequeue(); + } + + private void Recycle(Queue list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs.meta new file mode 100644 index 00000000..135cbb39 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/OneToManyQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 35d6a9fde65a99143b0abf6d9569c462 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs new file mode 100644 index 00000000..8c191ca3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs @@ -0,0 +1,537 @@ +// #if UNITY_5_3_OR_NEWER +// using System; +// using System.Collections; +// using System.Collections.Generic; +// using System.Diagnostics; +// using System.Diagnostics.CodeAnalysis; +// using System.Linq; +// using System.Runtime.CompilerServices; +// #pragma warning disable CS8600 +// +// namespace System.Collections.Generic +// { +// public class PriorityQueue +// { +// private const int DefaultCapacity = 4; +// +// private readonly IComparer _priorityComparer; +// +// private HeapEntry[] _heap; +// private int _count; +// private int _version; +// +// private UnorderedItemsCollection? _unorderedItemsCollection; +// +// #region Constructors +// public PriorityQueue() : this(0, null) +// { +// +// } +// +// // public PriorityQueue(int initialCapacity) : this(initialCapacity, null) +// // { +// // +// // } +// +// // public PriorityQueue(IComparer? comparer) : this(0, comparer) +// // { +// // +// // } +// +// // public PriorityQueue(int initialCapacity, IComparer? comparer) +// // { +// // if (initialCapacity < 0) +// // { +// // throw new ArgumentOutOfRangeException(nameof(initialCapacity)); +// // } +// // +// // if (initialCapacity == 0) +// // { +// // _heap = Array.Empty(); +// // } +// // else +// // { +// // _heap = new HeapEntry[initialCapacity]; +// // } +// // +// // _priorityComparer = comparer ?? Comparer.Default; +// // } +// +// public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> values) : this(values, null) +// { +// +// } +// +// public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> values, IComparer? comparer) +// { +// _priorityComparer = comparer ?? Comparer.Default; +// _heap = Array.Empty(); +// _count = 0; +// +// AppendRaw(values); +// Heapify(); +// } +// #endregion +// +// public int Count => _count; +// public IComparer Comparer => _priorityComparer; +// +// public void Enqueue(TElement element, TPriority priority) +// { +// _version++; +// if (_count == _heap.Length) +// { +// Resize(ref _heap); +// } +// +// SiftUp(index: _count++, in element, in priority); +// } +// +// public void EnqueueRange(IEnumerable<(TElement Element, TPriority Priority)> values) +// { +// _version++; +// if (_count == 0) +// { +// AppendRaw(values); +// Heapify(); +// } +// else +// { +// foreach ((TElement element, TPriority priority) in values) +// { +// if (_count == _heap.Length) +// { +// Resize(ref _heap); +// } +// +// SiftUp(index: _count++, in element, in priority); +// } +// } +// } +// +// // TODO optimize +// public void EnqueueRange(IEnumerable elements, TPriority priority) => EnqueueRange(elements.Select(e => (e, priority))); +// +// public TElement Peek() +// { +// if (_count == 0) +// { +// throw new InvalidOperationException(); +// } +// +// return _heap[0].Element; +// } +// +// public bool TryPeek([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority) +// { +// if (_count == 0) +// { +// element = default; +// priority = default; +// return false; +// } +// +// (element, priority) = _heap[0]; +// return true; +// } +// +// public TElement Dequeue() +// { +// if (_count == 0) +// { +// throw new InvalidOperationException(); +// } +// +// _version++; +// RemoveIndex(index: 0, out TElement result, out _); +// return result; +// } +// +// public bool TryDequeue([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority) +// { +// if (_count == 0) +// { +// element = default; +// priority = default; +// return false; +// } +// +// _version++; +// RemoveIndex(index: 0, out element, out priority); +// return true; +// } +// +// public TElement EnqueueDequeue(TElement element, TPriority priority) +// { +// if (_count == 0) +// { +// return element; +// } +// +// ref HeapEntry minEntry = ref _heap[0]; +// if (_priorityComparer.Compare(priority, minEntry.Priority) <= 0) +// { +// return element; +// } +// +// _version++; +// TElement minElement = minEntry.Element; +// #if SIFTDOWN_EMPTY_NODES +// SiftDownHeapPropertyRequired(index: 0, in element, in priority); +// #else +// SiftDown(index: 0, in element, in priority); +// #endif +// return minElement; +// } +// +// public void Clear() +// { +// _version++; +// if (_count > 0) +// { +// //if (RuntimeHelpers.IsReferenceOrContainsReferences()) +// { +// Array.Clear(_heap, 0, _count); +// } +// +// _count = 0; +// } +// } +// +// public void TrimExcess() +// { +// int count = _count; +// int threshold = (int)(((double)_heap.Length) * 0.9); +// if (count < threshold) +// { +// Array.Resize(ref _heap, count); +// } +// } +// +// public void EnsureCapacity(int capacity) +// { +// if (capacity < 0) +// { +// throw new ArgumentOutOfRangeException(); +// } +// +// if (capacity > _heap.Length) +// { +// Array.Resize(ref _heap, capacity); +// } +// } +// +// public UnorderedItemsCollection UnorderedItems => _unorderedItemsCollection ??= new UnorderedItemsCollection(this); +// +// public class UnorderedItemsCollection : IReadOnlyCollection<(TElement Element, TPriority Priority)>, ICollection +// { +// private readonly PriorityQueue _priorityQueue; +// +// internal UnorderedItemsCollection(PriorityQueue priorityQueue) +// { +// _priorityQueue = priorityQueue; +// } +// +// public int Count => _priorityQueue.Count; +// public bool IsSynchronized => false; +// public object SyncRoot => _priorityQueue; +// +// public Enumerator GetEnumerator() => new Enumerator(_priorityQueue); +// IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.GetEnumerator() => new Enumerator(_priorityQueue); +// IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_priorityQueue); +// +// bool ICollection.IsSynchronized => false; +// object ICollection.SyncRoot => this; +// void ICollection.CopyTo(Array array, int index) +// { +// if (array == null) +// throw new ArgumentNullException(nameof(array)); +// if (array.Rank != 1) +// throw new ArgumentException("SR.Arg_RankMultiDimNotSupported", nameof(array)); +// if (index < 0) +// throw new ArgumentOutOfRangeException(nameof(index), "SR.ArgumentOutOfRange_Index"); +// +// int arrayLen = array.Length; +// if (arrayLen - index < _priorityQueue._count) +// throw new ArgumentException("SR.Argument_InvalidOffLen"); +// +// int numToCopy = _priorityQueue._count; +// HeapEntry[] heap = _priorityQueue._heap; +// +// for (int i = 0; i < numToCopy; i++) +// { +// ref HeapEntry entry = ref heap[i]; +// array.SetValue((entry.Element, entry.Priority), index + i); +// } +// } +// +// public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>, IEnumerator +// { +// private readonly PriorityQueue _queue; +// private readonly int _version; +// private int _index; +// private (TElement Element, TPriority Priority) _current; +// +// internal Enumerator(PriorityQueue queue) +// { +// _version = queue._version; +// _queue = queue; +// _index = 0; +// _current = default; +// } +// +// public bool MoveNext() +// { +// PriorityQueue queue = _queue; +// +// if (queue._version == _version && _index < queue._count) +// { +// ref HeapEntry entry = ref queue._heap[_index]; +// _current = (entry.Element, entry.Priority); +// _index++; +// return true; +// } +// +// if (queue._version != _version) +// { +// throw new InvalidOperationException("collection was modified"); +// } +// +// return false; +// } +// +// public (TElement Element, TPriority Priority) Current => _current; +// object IEnumerator.Current => _current; +// +// public void Reset() +// { +// if (_queue._version != _version) +// { +// throw new InvalidOperationException("collection was modified"); +// } +// +// _index = 0; +// _current = default; +// } +// +// public void Dispose() +// { +// } +// } +// } +// +// #region Private Methods +// private void Heapify() +// { +// HeapEntry[] heap = _heap; +// +// for (int i = (_count - 1) >> 2; i >= 0; i--) +// { +// HeapEntry entry = heap[i]; // ensure struct is copied before sifting +// SiftDown(i, in entry.Element, in entry.Priority); +// } +// } +// +// private void AppendRaw(IEnumerable<(TElement Element, TPriority Priority)> values) +// { +// // TODO: specialize on ICollection types +// var heap = _heap; +// int count = _count; +// +// foreach ((TElement element, TPriority priority) in values) +// { +// if (count == heap.Length) +// { +// Resize(ref heap); +// } +// +// ref HeapEntry entry = ref heap[count]; +// entry.Element = element; +// entry.Priority = priority; +// count++; +// } +// +// _heap = heap; +// _count = count; +// } +// +// private void RemoveIndex(int index, out TElement element, out TPriority priority) +// { +// Debug.Assert(index < _count); +// +// (element, priority) = _heap[index]; +// +// int lastElementPos = --_count; +// ref HeapEntry lastElement = ref _heap[lastElementPos]; +// +// if (lastElementPos > 0) +// { +// #if SIFTDOWN_EMPTY_NODES +// SiftDownHeapPropertyRequired(index, in lastElement.Element, in lastElement.Priority); +// #else +// SiftDown(index, in lastElement.Element, in lastElement.Priority); +// #endif +// } +// +// //if (RuntimeHelpers.IsReferenceOrContainsReferences()) +// { +// lastElement = default; +// } +// } +// +// private void SiftUp(int index, in TElement element, in TPriority priority) +// { +// while (index > 0) +// { +// int parentIndex = (index - 1) >> 2; +// ref HeapEntry parent = ref _heap[parentIndex]; +// +// if (_priorityComparer.Compare(parent.Priority, priority) <= 0) +// { +// // parentPriority <= priority, heap property is satisfed +// break; +// } +// +// _heap[index] = parent; +// index = parentIndex; +// } +// +// ref HeapEntry entry = ref _heap[index]; +// entry.Element = element; +// entry.Priority = priority; +// } +// +// private void SiftDown(int index, in TElement element, in TPriority priority) +// { +// int minChildIndex; +// int count = _count; +// HeapEntry[] heap = _heap; +// +// while ((minChildIndex = (index << 2) + 1) < count) +// { +// // find the child with the minimal priority +// ref HeapEntry minChild = ref heap[minChildIndex]; +// int childUpperBound = Math.Min(count, minChildIndex + 4); +// +// for (int nextChildIndex = minChildIndex + 1; nextChildIndex < childUpperBound; nextChildIndex++) +// { +// ref HeapEntry nextChild = ref heap[nextChildIndex]; +// if (_priorityComparer.Compare(nextChild.Priority, minChild.Priority) < 0) +// { +// minChildIndex = nextChildIndex; +// minChild = ref nextChild; +// } +// } +// +// // compare with inserted priority +// if (_priorityComparer.Compare(priority, minChild.Priority) <= 0) +// { +// // priority <= minChild, heap property is satisfied +// break; +// } +// +// heap[index] = minChild; +// index = minChildIndex; +// } +// +// ref HeapEntry entry = ref heap[index]; +// entry.Element = element; +// entry.Priority = priority; +// } +// +// #if SIFTDOWN_EMPTY_NODES +// private void SiftDownHeapPropertyRequired(int index, in TElement element, in TPriority priority) +// { +// int emptyNodeIndex = SiftDownEmptyNode(index); +// SiftUp(emptyNodeIndex, in element, in priority); +// } +// +// private int SiftDownEmptyNode(int emptyNodeIndex) +// { +// int count = _count; +// int minChildIndex; +// HeapEntry[] heap = _heap; +// +// while ((minChildIndex = (emptyNodeIndex << 2) + 1) < count) +// { +// // find the child with the minimal priority +// ref HeapEntry minChild = ref heap[minChildIndex]; +// int childUpperBound = Math.Min(count, minChildIndex + 4); +// +// for (int nextChildIndex = minChildIndex + 1; nextChildIndex < childUpperBound; nextChildIndex++) +// { +// ref HeapEntry nextChild = ref heap[nextChildIndex]; +// if (_priorityComparer.Compare(nextChild.Priority, minChild.Priority) < 0) +// { +// minChildIndex = nextChildIndex; +// minChild = ref nextChild; +// } +// } +// +// heap[emptyNodeIndex] = minChild; +// emptyNodeIndex = minChildIndex; +// } +// +// return emptyNodeIndex; +// } +// #endif +// +// private void Resize(ref HeapEntry[] heap) +// { +// int newSize = heap.Length == 0 ? DefaultCapacity : 2 * heap.Length; +// Array.Resize(ref heap, newSize); +// } +// +// private struct HeapEntry +// { +// public TElement Element; +// public TPriority Priority; +// +// public void Deconstruct(out TElement element, out TPriority priority) +// { +// element = Element; +// priority = Priority; +// } +// } +// +// #if DEBUG +// public void ValidateInternalState() +// { +// if (_heap.Length < _count) +// { +// throw new Exception("invalid elements array length"); +// } +// +// foreach ((var element, var idx) in _heap.Select((x, i) => (x.Element, i)).Skip(_count)) +// { +// if (!IsDefault(element)) +// { +// throw new Exception($"Non-zero element '{element}' at index {idx}."); +// } +// } +// +// foreach ((var priority, var idx) in _heap.Select((x, i) => (x.Priority, i)).Skip(_count)) +// { +// if (!IsDefault(priority)) +// { +// throw new Exception($"Non-zero priority '{priority}' at index {idx}."); +// } +// } +// +// static bool IsDefault(T value) +// { +// T defaultVal = default; +// +// if (defaultVal is null) +// { +// return value is null; +// } +// +// return value!.Equals(defaultVal); +// } +// } +// #endif +// #endregion +// } +// } +// #endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs.meta new file mode 100644 index 00000000..48c9a39f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/PriorityQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e7e9292b9c27384d9aed58ea3d5098d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs new file mode 100644 index 00000000..8905104b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class ReuseList : List, IDisposable + { + private bool _isDispose; + + public static ReuseList Create() + { + var list = Pool>.Rent(); + list._isDispose = false; + return list; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs.meta new file mode 100644 index 00000000..17e47eac --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/ReuseList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe34244ec94ec0240a92d11c66f9d94e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs new file mode 100644 index 00000000..9a638732 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class SortedConcurrentOneToManyListPool : SortedConcurrentOneToManyList, + IDisposable where TKey : notnull + { + private bool _isDispose; + + public static SortedConcurrentOneToManyListPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class SortedConcurrentOneToManyList : SortedDictionary> where TKey : notnull + { + private readonly object _lockObject = new object(); + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + public SortedConcurrentOneToManyList() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public SortedConcurrentOneToManyList(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + lock (_lockObject) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + } + + public void Add(TKey key, TValue value) + { + lock (_lockObject) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + base[key] = list; + return; + } + + list.Add(value); + } + } + + public TValue First(TKey key) + { + lock (_lockObject) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + } + + public void RemoveValue(TKey key, TValue value) + { + lock (_lockObject) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + } + + public void RemoveKey(TKey key) + { + lock (_lockObject) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + + Recycle(list); + } + } + + private List Fetch() + { + lock (_lockObject) + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + } + + private void Recycle(List list) + { + lock (_lockObject) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs.meta new file mode 100644 index 00000000..50fdb007 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedConcurrentOneToManyList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24aa8bc8ac4d8584cb9ceb23a51c78fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs new file mode 100644 index 00000000..93027527 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public class SortedOneToManyHashSetPool : SortedOneToManyHashSet, IDisposable + where TKey : notnull + { + private bool _isDispose; + + public static SortedOneToManyHashSetPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class SortedOneToManyHashSet : SortedDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + + public SortedOneToManyHashSet() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public SortedOneToManyHashSet(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + + Recycle(list); + } + + private HashSet Fetch() + { + return _queue.Count <= 0 ? new HashSet() : _queue.Dequeue(); + } + + private void Recycle(HashSet list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs.meta new file mode 100644 index 00000000..6f6c11d0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyHashSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ddf73601eadf11349b77b78a4d91f35a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs new file mode 100644 index 00000000..432feca6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class SortedOneToManyListPool : SortedOneToManyList, IDisposable + where TKey : notnull + { + private bool _isDispose; + + public static SortedOneToManyListPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class SortedOneToManyList : SortedDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + public SortedOneToManyList() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public SortedOneToManyList(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + base[key] = list; + return; + } + + list.Add(value); + } + + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + return; + } + + list.Remove(value); + + if (list.Count == 0) + { + RemoveKey(key); + } + } + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return; + } + + Remove(key); + Recycle(list); + } + + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) + { + return; + } + + _queue.Enqueue(list); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs.meta new file mode 100644 index 00000000..e2e1459c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/SortedOneToManyList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db62c1998de4d5048bde5d5e09d6f284 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary.meta new file mode 100644 index 00000000..9a527253 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3217a5df0b4fae04cad4694ae07948b8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs new file mode 100644 index 00000000..0f7d031f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +#pragma warning disable CS8601 + +namespace TEngine.DataStructure +{ + public static class DictionaryExtensions + { + public static bool TryRemove(this IDictionary self, T key, out TV value) + { + if (!self.TryGetValue(key, out value)) + { + return false; + } + + self.Remove(key); + return true; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs.meta new file mode 100644 index 00000000..477a6f3d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2906692879c7825458790e8cb128e7f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs new file mode 100644 index 00000000..e48f8764 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class DictionaryPool : Dictionary, IDisposable where TM : notnull + { + private bool _isDispose; + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + + public static DictionaryPool Create() + { + var dictionary = Pool>.Rent(); + dictionary._isDispose = false; + return dictionary; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs.meta new file mode 100644 index 00000000..112d7f84 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DictionaryPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 45285f160861f9c4bb96634e744e2c74 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs new file mode 100644 index 00000000..b4259f45 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +#pragma warning disable CS8601 +#pragma warning disable CS8604 +#pragma warning disable CS8603 + +namespace TEngine.DataStructure +{ + public class DoubleMapDictionaryPool : DoubleMapDictionary, IDisposable + where TKey : notnull where TValue : notnull + { + private bool _isDispose; + + public static DoubleMapDictionaryPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class DoubleMapDictionary where TK : notnull where TV : notnull + { + private readonly Dictionary _kv = new Dictionary(); + private readonly Dictionary _vk = new Dictionary(); + + public DoubleMapDictionary() + { + } + + public DoubleMapDictionary(int capacity) + { + _kv = new Dictionary(capacity); + _vk = new Dictionary(capacity); + } + + public List Keys => new List(_kv.Keys); + + public List Values => new List(_vk.Keys); + + public void ForEach(Action action) + { + if (action == null) + { + return; + } + + var keys = _kv.Keys; + foreach (var key in keys) + { + action(key, _kv[key]); + } + } + + public void Add(TK key, TV value) + { + if (key == null || value == null || _kv.ContainsKey(key) || _vk.ContainsKey(value)) + { + return; + } + + _kv.Add(key, value); + _vk.Add(value, key); + } + + public TV GetValueByKey(TK key) + { + if (key != null && _kv.ContainsKey(key)) + { + return _kv[key]; + } + + return default; + } + + public bool TryGetValueByKey(TK key, out TV value) + { + var result = key != null && _kv.ContainsKey(key); + + value = result ? _kv[key] : default; + + return result; + } + + public TK GetKeyByValue(TV value) + { + if (value != null && _vk.ContainsKey(value)) + { + return _vk[value]; + } + + return default; + } + + public bool TryGetKeyByValue(TV value, out TK key) + { + var result = value != null && _vk.ContainsKey(value); + + key = result ? _vk[value] : default; + + return result; + } + + public void RemoveByKey(TK key) + { + if (key == null) + { + return; + } + + if (!_kv.TryGetValue(key, out var value)) + { + return; + } + + _kv.Remove(key); + _vk.Remove(value); + } + + public void RemoveByValue(TV value) + { + if (value == null) + { + return; + } + + if (!_vk.TryGetValue(value, out var key)) + { + return; + } + + _kv.Remove(key); + _vk.Remove(value); + } + + public void Clear() + { + _kv.Clear(); + _vk.Clear(); + } + + public bool ContainsKey(TK key) + { + return key != null && _kv.ContainsKey(key); + } + + public bool ContainsValue(TV value) + { + return value != null && _vk.ContainsKey(value); + } + + public bool Contains(TK key, TV value) + { + if (key == null || value == null) + { + return false; + } + + return _kv.ContainsKey(key) && _vk.ContainsKey(value); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs.meta new file mode 100644 index 00000000..a258a377 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/DoubleMapDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9b1abd0ffc450842bea6ec96e84425f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs new file mode 100644 index 00000000..ec103299 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class EntityDictionary : Dictionary, IDisposable where TN : IDisposable where TM : notnull + { + private bool _isDispose; + + public static EntityDictionary Create() + { + var entityDictionary = Pool>.Rent(); + entityDictionary._isDispose = false; + return entityDictionary; + } + + public new void Clear() + { + foreach (var keyValuePair in this) + { + keyValuePair.Value.Dispose(); + } + + base.Clear(); + } + + public void ClearNotDispose() + { + base.Clear(); + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs.meta new file mode 100644 index 00000000..97e564f5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/EntityDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e08b2b0077e40143a1f772a0f7b1ad5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs new file mode 100644 index 00000000..e6cc73bf --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#pragma warning disable CS8603 +#pragma warning disable CS8601 + +namespace TEngine.DataStructure +{ + public class OneToManyDictionaryPool : OneToManyDictionary, + IDisposable where TKey : notnull where TValueKey : notnull + { + private bool _isDispose; + + public static OneToManyDictionaryPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class OneToManyDictionary : Dictionary> + where TKey : notnull where TValueKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + + public OneToManyDictionary() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public OneToManyDictionary(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key, TValueKey valueKey) + { + TryGetValue(key, out var dic); + + return dic != null && dic.ContainsKey(valueKey); + } + + public bool TryGetValue(TKey key, TValueKey valueKey, out TValue value) + { + value = default; + return TryGetValue(key, out var dic) && dic.TryGetValue(valueKey, out value); + } + + public TValue First(TKey key) + { + return !TryGetValue(key, out var dic) ? default : dic.First().Value; + } + + public void Add(TKey key, TValueKey valueKey, TValue value) + { + if (!TryGetValue(key, out var dic)) + { + dic = Fetch(); + dic[valueKey] = value; + // dic.Add(valueKey, value); + Add(key, dic); + + return; + } + + dic[valueKey] = value; + // dic.Add(valueKey, value); + } + + public bool Remove(TKey key, TValueKey valueKey) + { + if (!TryGetValue(key, out var dic)) return false; + + var result = dic.Remove(valueKey); + + if (dic.Count == 0) RemoveKey(key); + + return result; + } + + public bool Remove(TKey key, TValueKey valueKey, out TValue value) + { + if (!TryGetValue(key, out var dic)) + { + value = default; + return false; + } + + var result = dic.TryGetValue(valueKey, out value); + + if (result) dic.Remove(valueKey); + + if (dic.Count == 0) RemoveKey(key); + + return result; + } + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var dic)) return; + + Remove(key); + Recycle(dic); + } + + private Dictionary Fetch() + { + return _queue.Count <= 0 ? new Dictionary() : _queue.Dequeue(); + } + + private void Recycle(Dictionary dic) + { + dic.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(dic); + } + + public new void Clear() + { + foreach (var keyValuePair in this) Recycle(keyValuePair.Value); + + base.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs.meta new file mode 100644 index 00000000..3c465392 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManyDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5db437bfa1f9b8b438b7260196e01934 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs new file mode 100644 index 00000000..4dc4afab --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +#pragma warning disable CS8601 + +namespace TEngine.DataStructure +{ + public class + OneToManySortedDictionaryPool : OneToManySortedDictionary, + IDisposable where TKey : notnull where TSortedKey : notnull + { + private bool _isDispose; + + public static OneToManySortedDictionaryPool Create() + { + var a = Pool>.Rent(); + a._isDispose = false; + return a; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } + + public class + OneToManySortedDictionary : Dictionary> + where TSortedKey : notnull where TKey : notnull + { + private readonly int _recyclingLimit = 120; + + private readonly Queue> _queue = + new Queue>(); + + protected OneToManySortedDictionary() + { + } + + /// + /// è®¾ç½®æœ€å¤§ç¼“å­˜æ•°é‡ + /// + /// + /// 1:防止数æ®é‡è¿‡å¤§ã€æ‰€ä»¥è¶…过recyclingLimit的数æ®è¿˜æ˜¯èµ°GC. + /// 2:设置æˆ0ä¸æŽ§åˆ¶æ•°é‡ï¼Œå…¨éƒ¨ç¼“å­˜ + /// + public OneToManySortedDictionary(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + public bool Contains(TKey key) + { + return this.ContainsKey(key); + } + + public bool Contains(TKey key, TSortedKey sortedKey) + { + return TryGetValue(key, out var dic) && dic.ContainsKey(sortedKey); + } + + public new bool TryGetValue(TKey key, out SortedDictionary dic) + { + return base.TryGetValue(key, out dic); + } + + public bool TryGetValueBySortedKey(TKey key, TSortedKey sortedKey, out TValue value) + { + if (base.TryGetValue(key, out var dic)) + { + return dic.TryGetValue(sortedKey, out value); + } + + value = default; + return false; + } + + public void Add(TKey key, TSortedKey sortedKey, TValue value) + { + if (!TryGetValue(key, out var dic)) + { + dic = Fetch(); + dic.Add(sortedKey, value); + Add(key, dic); + + return; + } + + dic.Add(sortedKey, value); + } + + public bool RemoveSortedKey(TKey key, TSortedKey sortedKey) + { + if (!TryGetValue(key, out var dic)) + { + return false; + } + + var isRemove = dic.Remove(sortedKey); + + if (dic.Count == 0) + { + isRemove = RemoveKey(key); + } + + return isRemove; + } + + public bool RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return false; + } + + Remove(key); + Recycle(list); + return true; + } + + private SortedDictionary Fetch() + { + return _queue.Count <= 0 ? new SortedDictionary() : _queue.Dequeue(); + } + + private void Recycle(SortedDictionary dic) + { + dic.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) + { + return; + } + + _queue.Enqueue(dic); + } + + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs.meta new file mode 100644 index 00000000..354acedf --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/OneToManySortedDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0ac9ed7abbf4e4408901274ee344642 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs new file mode 100644 index 00000000..4df0b125 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.DataStructure +{ + public sealed class ReuseDictionary : Dictionary, IDisposable where TM : notnull + { + private bool _isDispose; + + public static ReuseDictionary Create() + { + var entityDictionary = Pool>.Rent(); + entityDictionary._isDispose = false; + return entityDictionary; + } + + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs.meta new file mode 100644 index 00000000..1e76d601 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Dictionary/ReuseDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf01018dd07bd2243b8636a1321f50c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable.meta b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable.meta new file mode 100644 index 00000000..d4889032 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a7e0baed1cdd57b4e916270c39382710 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs new file mode 100644 index 00000000..55e9ba0a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs @@ -0,0 +1,166 @@ +#pragma warning disable CS8602 +#pragma warning disable CS8601 +#pragma warning disable CS8625 +#pragma warning disable CS8604 +#pragma warning disable CS8600 +namespace TEngine.DataStructure +{ + /// + /// 跳表å‡åºç‰ˆ + /// + /// + public class SkipTable : SkipTableBase + { + public SkipTable(int maxLayer = 8) : base(maxLayer) { } + public override void Add(long sortKey, long viceKey, long key, TValue value) + { + var rLevel = 1; + + while (rLevel <= MaxLayer && Random.Next(3) == 0) + { + ++rLevel; + } + + SkipTableNode cur = TopHeader, last = null; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 节点有next节点,且 (next主键 < æ’入主键) 或 (next主键 == æ’入主键 且 next副键 < æ’入副键) + while (cur.Right != null && ((cur.Right.SortKey < sortKey) || + (cur.Right.SortKey == sortKey && cur.Right.ViceKey < viceKey))) + { + cur = cur.Right; + } + + if (layer <= rLevel) + { + var currentRight = cur.Right; + cur.Right = new SkipTableNode(sortKey, viceKey, key, value, + layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null); + + if (currentRight != null) + { + currentRight.Left = cur.Right; + } + + if (last != null) + { + last.Down = cur.Right; + } + + if (layer == 1) + { + cur.Right.Index = cur.Index + 1; + Node.Add(key, cur.Right); + + SkipTableNode v = cur.Right.Right; + + while (v != null) + { + v.Index++; + v = v.Right; + } + } + + last = cur.Right; + } + + cur = cur.Down; + } + } + public override bool Remove(long sortKey, long viceKey, long key, out TValue value) + { + value = default; + var seen = false; + var cur = TopHeader; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 先按照主键查找 å† æŒ‰å‰¯é”®æŸ¥æ‰¾ + while (cur.Right != null && cur.Right.SortKey < sortKey && cur.Right.Key != key) cur = cur.Right; + while (cur.Right != null && (cur.Right.SortKey == sortKey && cur.Right.ViceKey <= viceKey) && + cur.Right.Key != key) cur = cur.Right; + + var isFind = false; + var currentCur = cur; + SkipTableNode removeCur = null; + // 如果当å‰ä¸æ˜¯è¦åˆ é™¤çš„节点ã€ä½†ä¸»é”®å’Œå‰¯é”®éƒ½ä¸€æ ·ã€éœ€è¦ç‰¹æ®Šå¤„ç†ä¸‹ã€‚ + if (cur.Right != null && cur.Right.Key == key) + { + isFind = true; + removeCur = cur.Right; + currentCur = cur; + } + else + { + // å…ˆå‘左查找下 + var currentNode = cur.Left; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Left; + } + + // å†å‘峿Ÿ¥æ‰¾ä¸‹ + if (!isFind) + { + currentNode = cur.Right; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Right; + } + } + } + + if (isFind && currentCur != null) + { + value = removeCur.Value; + currentCur.Right = removeCur.Right; + + if (removeCur.Right != null) + { + removeCur.Right.Left = currentCur; + removeCur.Right = null; + } + + removeCur.Left = null; + removeCur.Down = null; + removeCur.Value = default; + + if (layer == 1) + { + var tempCur = currentCur.Right; + while (tempCur != null) + { + tempCur.Index--; + tempCur = tempCur.Right; + } + + Node.Remove(removeCur.Key); + } + + seen = true; + } + + cur = cur.Down; + } + + return seen; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs.meta new file mode 100644 index 00000000..13f9f83d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 94af5a03823f82246a24b4e5dfdc2be5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs new file mode 100644 index 00000000..9e9a635a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections; +using System.Collections.Generic; +#pragma warning disable CS8601 +#pragma warning disable CS8603 +#pragma warning disable CS8625 +#pragma warning disable CS8604 + +namespace TEngine.DataStructure +{ + public abstract class SkipTableBase : IEnumerable> + { + public readonly int MaxLayer; + public readonly SkipTableNode TopHeader; + public SkipTableNode BottomHeader; + + public int Count => Node.Count; + protected readonly Random Random = new Random(); + protected readonly Dictionary> Node = new(); + protected readonly Stack> AntiFindStack = new Stack>(); + + protected SkipTableBase(int maxLayer = 8) + { + MaxLayer = maxLayer; + var cur = TopHeader = new SkipTableNode(long.MinValue, 0, 0, default, 0, null, null, null); + + for (var layer = MaxLayer - 1; layer >= 1; --layer) + { + cur.Down = new SkipTableNode(long.MinValue, 0, 0, default, 0, null, null, null); + cur = cur.Down; + } + + BottomHeader = cur; + } + + public TValue this[long key] => !TryGetValueByKey(key, out TValue value) ? default : value; + + public int GetRanking(long key) + { + if (!Node.TryGetValue(key, out var node)) + { + return 0; + } + + return node.Index; + } + + public int GetAntiRanking(long key) + { + var ranking = GetRanking(key); + + if (ranking == 0) + { + return 0; + } + + return Count + 1 - ranking; + } + + public bool TryGetValueByKey(long key, out TValue value) + { + if (!Node.TryGetValue(key, out var node)) + { + value = default; + return false; + } + + value = node.Value; + return true; + } + + public bool TryGetNodeByKey(long key, out SkipTableNode node) + { + if (Node.TryGetValue(key, out node)) + { + return true; + } + + return false; + } + + public void Find(int start, int end, ListPool> list) + { + var cur = BottomHeader; + var count = end - start; + + for (var i = 0; i < start; i++) + { + cur = cur.Right; + } + + for (var i = 0; i <= count; i++) + { + if (cur == null) + { + break; + } + + list.Add(cur); + cur = cur.Right; + } + } + + public void AntiFind(int start, int end, ListPool> list) + { + var cur = BottomHeader; + start = Count + 1 - start; + end = start - end; + + for (var i = 0; i < start; i++) + { + cur = cur.Right; + + if (cur == null) + { + break; + } + + if (i < end) + { + continue; + } + + AntiFindStack.Push(cur); + } + + while (AntiFindStack.TryPop(out var node)) + { + list.Add(node); + } + } + + public TValue GetLastValue() + { + var cur = TopHeader; + + while (cur.Right != null || cur.Down != null) + { + while (cur.Right != null) + { + cur = cur.Right; + } + + if (cur.Down != null) + { + cur = cur.Down; + } + } + + return cur.Value; + } + + public bool Remove(long key) + { + if (!Node.TryGetValue(key, out var node)) + { + return false; + } + + return Remove(node.SortKey, node.ViceKey, key, out _); + } + + public abstract void Add(long sortKey, long viceKey, long key, TValue value); + public abstract bool Remove(long sortKey, long viceKey, long key, out TValue value); + + public IEnumerator> GetEnumerator() + { + var cur = BottomHeader.Right; + while (cur != null) + { + yield return cur; + cur = cur.Right; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs.meta new file mode 100644 index 00000000..1b8d4294 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3dacacc147f7c8499e328284a0542ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs new file mode 100644 index 00000000..73162e9a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs @@ -0,0 +1,166 @@ +#pragma warning disable CS8602 +#pragma warning disable CS8601 +#pragma warning disable CS8625 +#pragma warning disable CS8604 +#pragma warning disable CS8600 +namespace TEngine.DataStructure +{ + /// + /// 跳表é™åºç‰ˆ + /// + /// + public class SkipTableDesc : SkipTableBase + { + public SkipTableDesc(int maxLayer = 8) : base(maxLayer) { } + public override void Add(long sortKey, long viceKey, long key, TValue value) + { + var rLevel = 1; + + while (rLevel <= MaxLayer && Random.Next(3) == 0) + { + ++rLevel; + } + + SkipTableNode cur = TopHeader, last = null; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 节点有next节点,且 (next主键 > æ’入主键) 或 (next主键 == æ’入主键 且 next副键 > æ’入副键) + while (cur.Right != null && ((cur.Right.SortKey > sortKey) || + (cur.Right.SortKey == sortKey && cur.Right.ViceKey > viceKey))) + { + cur = cur.Right; + } + + if (layer <= rLevel) + { + var currentRight = cur.Right; + cur.Right = new SkipTableNode(sortKey, viceKey, key, value, + layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null); + + if (currentRight != null) + { + currentRight.Left = cur.Right; + } + + if (last != null) + { + last.Down = cur.Right; + } + + if (layer == 1) + { + cur.Right.Index = cur.Index + 1; + Node.Add(key, cur.Right); + + SkipTableNode v = cur.Right.Right; + + while (v != null) + { + v.Index++; + v = v.Right; + } + } + + last = cur.Right; + } + + cur = cur.Down; + } + } + public override bool Remove(long sortKey, long viceKey, long key, out TValue value) + { + value = default; + var seen = false; + var cur = TopHeader; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 先按照主键查找 å† æŒ‰å‰¯é”®æŸ¥æ‰¾ + while (cur.Right != null && cur.Right.SortKey > sortKey && cur.Right.Key != key) cur = cur.Right; + while (cur.Right != null && (cur.Right.SortKey == sortKey && cur.Right.ViceKey >= viceKey) && + cur.Right.Key != key) cur = cur.Right; + + var isFind = false; + var currentCur = cur; + SkipTableNode removeCur = null; + // 如果当å‰ä¸æ˜¯è¦åˆ é™¤çš„节点ã€ä½†ä¸»é”®å’Œå‰¯é”®éƒ½ä¸€æ ·ã€éœ€è¦ç‰¹æ®Šå¤„ç†ä¸‹ã€‚ + if (cur.Right != null && cur.Right.Key == key) + { + isFind = true; + removeCur = cur.Right; + currentCur = cur; + } + else + { + // å…ˆå‘左查找下 + var currentNode = cur.Left; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Left; + } + + // å†å‘峿Ÿ¥æ‰¾ä¸‹ + if (!isFind) + { + currentNode = cur.Right; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Right; + } + } + } + + if (isFind && currentCur != null) + { + value = removeCur.Value; + currentCur.Right = removeCur.Right; + + if (removeCur.Right != null) + { + removeCur.Right.Left = currentCur; + removeCur.Right = null; + } + + removeCur.Left = null; + removeCur.Down = null; + removeCur.Value = default; + + if (layer == 1) + { + var tempCur = currentCur.Right; + while (tempCur != null) + { + tempCur.Index--; + tempCur = tempCur.Right; + } + + Node.Remove(removeCur.Key); + } + + seen = true; + } + + cur = cur.Down; + } + + return seen; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs.meta new file mode 100644 index 00000000..1acbed79 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableDesc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5dd291e3d391b8842a15ea93f72e4771 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs new file mode 100644 index 00000000..e318bbc6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs @@ -0,0 +1,29 @@ +namespace TEngine.DataStructure +{ + public class SkipTableNode + { + public int Index; + public long Key; + public long SortKey; + public long ViceKey; + public TValue Value; + public SkipTableNode Left; + public SkipTableNode Right; + public SkipTableNode Down; + + public SkipTableNode(long sortKey, long viceKey, long key, TValue value, int index, + SkipTableNode l, + SkipTableNode r, + SkipTableNode d) + { + Left = l; + Right = r; + Down = d; + Value = value; + Key = key; + Index = index; + SortKey = sortKey; + ViceKey = viceKey; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs.meta b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs.meta new file mode 100644 index 00000000..beed5522 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/DataStructure/SkipTable/SkipTableNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2dcae58948d93a74bb7234d2a88fee46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Define.cs b/Assets/GameScripts/DotNet/Core/Define.cs new file mode 100644 index 00000000..49def5d1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Define.cs @@ -0,0 +1,11 @@ +#if TENGINE_NET +#pragma warning disable CS8618 +namespace TEngine +{ + public static class Define + { + public static CommandLineOptions Options; + public static uint AppId => Options.AppId; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Define.cs.meta b/Assets/GameScripts/DotNet/Core/Define.cs.meta new file mode 100644 index 00000000..1334d185 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Define.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e08a4791989b16843a924bf798cdaa34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas.meta b/Assets/GameScripts/DotNet/Core/Entitas.meta new file mode 100644 index 00000000..0740d37c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: adeda901c300a6544bc4c134eb42a5e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs b/Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs new file mode 100644 index 00000000..b206984d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using TEngine.DataStructure; +using TEngine.Core; + +namespace TEngine +{ + public sealed class EntitiesSystem : Singleton, IUpdateSingleton + { + private readonly OneToManyList _assemblyList = new(); + private readonly Dictionary _awakeSystems = new(); + private readonly Dictionary _updateSystems = new(); + private readonly Dictionary _destroySystems = new(); + private readonly Dictionary _deserializeSystems = new(); + + private readonly Queue _updateQueue = new Queue(); + + protected override void OnLoad(int assemblyName) + { + foreach (var entitiesSystemType in AssemblyManager.ForEach(assemblyName, typeof(IEntitiesSystem))) + { + var entity = Activator.CreateInstance(entitiesSystemType); + + switch (entity) + { + case IAwakeSystem iAwakeSystem: + { + _awakeSystems.Add(iAwakeSystem.EntitiesType(), iAwakeSystem); + break; + } + case IDestroySystem iDestroySystem: + { + _destroySystems.Add(iDestroySystem.EntitiesType(), iDestroySystem); + break; + } + case IDeserializeSystem iDeserializeSystem: + { + _deserializeSystems.Add(iDeserializeSystem.EntitiesType(), iDeserializeSystem); + break; + } + case IUpdateSystem iUpdateSystem: + { + _updateSystems.Add(iUpdateSystem.EntitiesType(), iUpdateSystem); + break; + } + } + + _assemblyList.Add(assemblyName, entitiesSystemType); + } + } + + protected override void OnUnLoad(int assemblyName) + { + if (!_assemblyList.TryGetValue(assemblyName, out var assembly)) + { + return; + } + + _assemblyList.RemoveByKey(assemblyName); + + foreach (var type in assembly) + { + _awakeSystems.Remove(type); + _updateSystems.Remove(type); + _destroySystems.Remove(type); + _deserializeSystems.Remove(type); + } + } + + public void Awake(T entity) where T : Entity + { + var type = entity.GetType(); + + if (!_awakeSystems.TryGetValue(type, out var awakeSystem)) + { + return; + } + + try + { + awakeSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{type.Name} Error {e}"); + } + } + + public void Destroy(T entity) where T : Entity + { + var type = entity.GetType(); + + if (!_destroySystems.TryGetValue(type, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{type.Name} Error {e}"); + } + } + + public void Deserialize(T entity) where T : Entity + { + var type = entity.GetType(); + + if (!_deserializeSystems.TryGetValue(type, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{type.Name} Error {e}"); + } + } + + public void StartUpdate(Entity entity) + { + if (!_updateSystems.ContainsKey(entity.GetType())) + { + return; + } + + _updateQueue.Enqueue(entity.RuntimeId); + } + + public void Update() + { + var updateQueueCount = _updateQueue.Count; + + while (updateQueueCount-- > 0) + { + var runtimeId = _updateQueue.Dequeue(); + var entity = Entity.GetEntity(runtimeId); + + if (entity == null || entity.IsDisposed) + { + continue; + } + + var type = entity.GetType(); + + if (!_updateSystems.TryGetValue(type, out var updateSystem)) + { + continue; + } + + _updateQueue.Enqueue(runtimeId); + + try + { + updateSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{type} Error {e}"); + } + } + } + + public override void Dispose() + { + _assemblyList.Clear(); + _awakeSystems.Clear(); + _updateSystems.Clear(); + _destroySystems.Clear(); + _deserializeSystems.Clear(); + AssemblyManager.OnLoadAssemblyEvent -= OnLoad; + AssemblyManager.OnUnLoadAssemblyEvent -= OnUnLoad; + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs.meta new file mode 100644 index 00000000..c3b67c66 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/EntitiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ebd4150be3b67b4bafd633a97a738c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Entity.cs b/Assets/GameScripts/DotNet/Core/Entitas/Entity.cs new file mode 100644 index 00000000..fcd2bf9c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Entity.cs @@ -0,0 +1,553 @@ +using System; +using System.Collections.Generic; +using TEngine.Core; +using System.Runtime.Serialization; +using TEngine.DataStructure; +using MongoDB.Bson.Serialization.Attributes; +using Newtonsoft.Json; +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8601 +#pragma warning disable CS8603 +#pragma warning disable CS0436 + +// ReSharper disable SuspiciousTypeConversion.Global +// ReSharper disable InconsistentNaming + +namespace TEngine +{ + public abstract class Entity : IDisposable + { + #region Entities + + private static readonly Dictionary Entities = new Dictionary(); + private static readonly OneToManyQueue Pool = new OneToManyQueue(); + + public static Entity GetEntity(long runTimeId) + { + return Entities.TryGetValue(runTimeId, out var entity) ? entity : null; + } + + public static bool TryGetEntity(long runTimeId, out Entity entity) + { + return Entities.TryGetValue(runTimeId, out entity); + } + + public static T GetEntity(long runTimeId) where T : Entity, new() + { + if (!Entities.TryGetValue(runTimeId, out var entity)) + { + return default; + } + + return (T) entity; + } + + public static bool TryGetEntity(long runTimeId, out T outEntity) where T : Entity, new() + { + if (!Entities.TryGetValue(runTimeId, out var entity)) + { + outEntity = default; + return false; + } + + outEntity = (T) entity; + return true; + } + + private static T Rent(Type entityType) where T : Entity, new() + { + if (typeof(INotSupportedPool).IsAssignableFrom(entityType)) + { + return Activator.CreateInstance(); + } + + T entity; + + if (Pool.TryDequeue(entityType, out var poolEntity)) + { + entity = (T) poolEntity; + } + else + { + entity = Activator.CreateInstance(); + } + + entity._isFromPool = true; + return entity; + } + + private static void Return(Entity entity) + { + entity.Id = 0; + + if (!entity._isFromPool) + { + return; + } + + entity._isFromPool = false; + Pool.Enqueue(entity.GetType(), entity); + } + + #endregion + + #region Create + + public static T Create(Scene scene, bool isRunEvent = true) where T : Entity, new() + { + var entity = Create(scene.RouteId, isRunEvent); + entity.Scene = scene; + return entity; + } + + public static T Create(Scene scene, long id, bool isRunEvent = true) where T : Entity, new() + { + var entity = Create(id, scene.RouteId, isRunEvent); + entity.Scene = scene; + return entity; + } + + private static T Create(uint routeId, bool isRunEvent = true) where T : Entity, new() + { + var entity = Rent(typeof(T)); +#if TENGINE_NET + entity.Id = entity.RuntimeId = IdFactory.NextEntityId(routeId); +#else + entity.Id = entity.RuntimeId = IdFactory.NextRunTimeId(); +#endif + Entities.Add(entity.RuntimeId, entity); + + if (isRunEvent) + { + EntitiesSystem.Instance.Awake(entity); + EntitiesSystem.Instance.StartUpdate(entity); + } + + return entity; + } + + private static T Create(long id, uint routeId, bool isRunEvent = true) where T : Entity, new() + { + return Create(id, IdFactory.NextEntityId(routeId), isRunEvent); + } + + private static T Create(long id, long runtimeId, bool isRunEvent = true) where T : Entity, new() + { + var entity = Rent(typeof(T)); + entity.Id = id; + entity.RuntimeId = runtimeId; + Entities.Add(entity.RuntimeId, entity); + + if (isRunEvent) + { + EntitiesSystem.Instance.Awake(entity); + EntitiesSystem.Instance.StartUpdate(entity); + } + + return entity; + } + + protected static Scene CreateScene(long id, bool isRunEvent = true) + { + var entity = Create(id, id, isRunEvent); + entity.Scene = entity; + return entity; + } + + #endregion + + #region Members + + [BsonId] + [BsonElement] + [BsonIgnoreIfDefault] + [BsonDefaultValue(0L)] + public long Id { get; private set; } + + [BsonIgnore] + [IgnoreDataMember] + public long RuntimeId { get; private set; } + + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + public bool IsDisposed => RuntimeId == 0; + + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + public Scene Scene { get; private set; } + + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + public Entity Parent { get; private set; } + + [BsonElement("t")] + [BsonIgnoreIfNull] + private ListPool _treeDb; + + [BsonIgnore] + [IgnoreDataMember] + private DictionaryPool _tree; + + [BsonElement("m")] + [BsonIgnoreIfNull] + private ListPool _multiDb; + + [BsonIgnore] + [IgnoreDataMember] + private DictionaryPool _multi; + + [BsonIgnore] + [IgnoreDataMember] + private bool _isFromPool; + + #endregion + + #region AddComponent + + public T AddComponent() where T : Entity, new() + { + var entity = Create(Scene.RouteId, false); + AddComponent(entity); + EntitiesSystem.Instance.Awake(entity); + EntitiesSystem.Instance.StartUpdate(entity); + return entity; + } + + public T AddComponent(long id) where T : Entity, new() + { + var entity = Create(id, Scene.RouteId, false); + AddComponent(entity); + EntitiesSystem.Instance.Awake(entity); + EntitiesSystem.Instance.StartUpdate(entity); + return entity; + } + + public void AddComponent(Entity component) + { + if (this == component) + { + Log.Error("Cannot add oneself to one's own components"); + return; + } + + if (component.IsDisposed) + { + Log.Error($"component is Disposed {component.GetType().FullName}"); + return; + } + + var type = component.GetType(); + component.Parent?.RemoveComponent(component, false); + + if (component is ISupportedMultiEntity multiEntity) + { + _multi ??= DictionaryPool.Create(); + _multi.Add(component.Id, multiEntity); + + if (component is ISupportedDataBase) + { + _multiDb ??= ListPool.Create(); + _multiDb.Add(component); + } + } + else + { + if (_tree == null) + { + _tree = DictionaryPool.Create(); + } + else if(_tree.ContainsKey(type)) + { + Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity"); + return; + } + + _tree.Add(type, component); + + if (component is ISupportedDataBase) + { + _treeDb ??= ListPool.Create(); + _treeDb.Add(component); + } + } + + component.Parent = this; + component.Scene = Scene; + } + + #endregion + + #region GetComponent + + public T GetComponent() where T : Entity, new() + { + return GetComponent(typeof(T)) as T; + } + + public Entity GetComponent(Type componentType) + { + if (_tree == null) + { + return default; + } + + return _tree.TryGetValue(componentType, out var component) ? component : default; + } + + public T GetComponent(long id) where T : ISupportedMultiEntity, new() + { + if (_multi == null) + { + return default; + } + + return _multi.TryGetValue(id, out var entity) ? (T) entity : default; + } + + #endregion + + #region RemoveComponent + + public void RemoveComponent(bool isDispose = true) where T : Entity, new() + { + if (_tree == null || !_tree.TryGetValue(typeof(T), out var component)) + { + return; + } + + RemoveComponent(component, isDispose); + } + + public void RemoveComponent(long id, bool isDispose = true) where T : ISupportedMultiEntity, new() + { + if (_multi == null || !_multi.TryGetValue(id, out var component)) + { + return; + } + + RemoveComponent((Entity)component, isDispose); + } + + public void RemoveComponent(Entity component, bool isDispose = true) + { + if (this == component) + { + return; + } + + if (component is ISupportedMultiEntity) + { + if (_multi != null) + { +#if TENGINE_NET + if (component is ISupportedDataBase && _multiDb != null) + { + _multiDb.Remove(component); + + if (_multiDb.Count == 0) + { + _multiDb.Dispose(); + _multiDb = null; + } + } +#endif + _multi.Remove(component.Id); + + if (_multi.Count == 0) + { + _multi.Dispose(); + _multi = null; + } + } + } + else if (_tree != null) + { +#if TENGINE_NET + if (component is ISupportedDataBase && _treeDb != null) + { + _treeDb.Remove(component); + + if (_treeDb.Count == 0) + { + _treeDb.Dispose(); + _treeDb = null; + } + } +#endif + _tree.Remove(component.GetType()); + + if (_tree.Count == 0) + { + _tree.Dispose(); + _tree = null; + } + } + + if (isDispose) + { + component.Dispose(); + } + } + + #endregion + + #region Deserialize + + public void Deserialize(Scene scene, bool resetId = false) + { + if (IsDisposed) + { + Log.Error($"component is Disposed {this.GetType().FullName}"); + return; + } + + if (RuntimeId != 0) + { + return; + } + + try + { +#if TENGINE_NET + RuntimeId = IdFactory.NextEntityId(scene.RouteId); +#else + RuntimeId = IdFactory.NextRunTimeId(); +#endif + if (resetId) + { + Id = RuntimeId; + } + + Entities.Add(RuntimeId, this); + + if (_treeDb != null && _treeDb.Count > 0) + { + _tree = DictionaryPool.Create(); + foreach (var entity in _treeDb) + { + entity.Parent = this; + entity.Scene = scene; + entity.Deserialize(scene, resetId); + _tree.Add(entity.GetType(), entity); + } + } + + if (_multiDb != null && _multiDb.Count > 0) + { + _multi = DictionaryPool.Create(); + foreach (var entity in _multiDb) + { + entity.Parent = this; + entity.Scene = scene; + entity.Deserialize(scene, resetId); + _multi.Add(entity.Id, (ISupportedMultiEntity)entity); + } + } + } + catch (Exception e) + { + if (RuntimeId != 0) + { + Entities.Remove(RuntimeId); + } + + Log.Error(e); + } + } + + #endregion + + #region Clone + + public Entity Clone() + { +#if TENGINE_NET + var entity = MongoHelper.Instance.Clone(this); + entity.Deserialize(Scene, true); + return entity; +#elif TENGINE_UNITY + var entity = ProtoBufHelper.Clone(this); + entity.Deserialize(Scene, true); + return entity; +#endif + } + + #endregion + + #region Dispose + + public virtual void Dispose() + { + if (IsDisposed) + { + return; + } + + var runtimeId = RuntimeId; + RuntimeId = 0; + + if (_tree != null) + { + foreach (var (_, entity) in _tree) + { + entity.Dispose(); + } + + _tree.Dispose(); + _tree = null; + } + + if (_multi != null) + { + foreach (var (_, entity) in _multi) + { + entity.Dispose(); + } + + _multi.Dispose(); + _multi = null; + } + +#if TENGINE_NET + if (_treeDb != null) + { + foreach (var entity in _treeDb) + { + entity.Dispose(); + } + + _treeDb.Dispose(); + _treeDb = null; + } + + if (_multiDb != null) + { + foreach (var entity in _multiDb) + { + entity.Dispose(); + } + + _multiDb.Dispose(); + _multiDb = null; + } +#endif + EntitiesSystem.Instance?.Destroy(this); + + if (Parent != null && Parent != this && !Parent.IsDisposed) + { + Parent.RemoveComponent(this, false); + Parent = null; + } + + Entities.Remove(runtimeId); + Scene = null; + Return(this); + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Entity.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Entity.cs.meta new file mode 100644 index 00000000..878271b0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Entity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4ac3a7a2c87dab48819ea8ad865ec76 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface.meta new file mode 100644 index 00000000..5d29d816 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c5e09a29bcfe7c4f9f84422354a6d2c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported.meta new file mode 100644 index 00000000..635398fc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c3ff31e66c8ca44b8e99c068cdcc04a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs new file mode 100644 index 00000000..43e65f2e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs @@ -0,0 +1,8 @@ +namespace TEngine +{ + /// + /// Entity䏿”¯æŒå¯¹è±¡æ± åˆ›å»ºå’Œå›žæ”¶ + /// + public interface INotSupportedPool { } +} + diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs.meta new file mode 100644 index 00000000..e8f6f11c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/INotSupportedPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9eceeefe7ee19f47bac590bc44d57d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs new file mode 100644 index 00000000..fb3782a4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs @@ -0,0 +1,8 @@ +namespace TEngine +{ + /// + /// Entityæ”¯æŒæ•°æ®åº“ + /// + // ReSharper disable once InconsistentNaming + public interface ISupportedDataBase { } +} diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs.meta new file mode 100644 index 00000000..108aa26d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedDataBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa02a2282c8866746a60eaaa35afb0de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs new file mode 100644 index 00000000..320cdf83 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs @@ -0,0 +1,9 @@ +using System; + +namespace TEngine +{ + /// + /// 支æŒå†ä¸€ä¸ªç»„件里添加多个åŒç±»åž‹ç»„ä»¶ + /// + public interface ISupportedMultiEntity : IDisposable { } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs.meta new file mode 100644 index 00000000..22ef8d96 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17d0f9fb53e07414991160d50cf133e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System.meta new file mode 100644 index 00000000..9090f9ea --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3473ee41e6d3cfa489bcf20bc8f6d546 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs new file mode 100644 index 00000000..e7dc4d55 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs @@ -0,0 +1,18 @@ +using System; + +namespace TEngine +{ + public interface IAwakeSystem : IEntitiesSystem { } + + public abstract class AwakeSystem : IAwakeSystem where T : Entity + { + public Type EntitiesType() => typeof(T); + + protected abstract void Awake(T self); + + public void Invoke(Entity self) + { + Awake((T) self); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs.meta new file mode 100644 index 00000000..5d27f576 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/AwakeSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e152a700f49f0748bfbdd03108a4a5e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs new file mode 100644 index 00000000..1ebc772e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs @@ -0,0 +1,16 @@ +using System; + +namespace TEngine +{ + public interface IDeserializeSystem : IEntitiesSystem { } + + public abstract class DeserializeSystem : IDeserializeSystem where T : Entity + { + public Type EntitiesType() => typeof(T); + protected abstract void Deserialize(T self); + public void Invoke(Entity self) + { + Deserialize((T) self); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs.meta new file mode 100644 index 00000000..aa496030 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDeserializeSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be8b21b9058e78343abfeea9277be75e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs new file mode 100644 index 00000000..852deb7d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs @@ -0,0 +1,16 @@ +using System; + +namespace TEngine +{ + public interface IDestroySystem : IEntitiesSystem { } + + public abstract class DestroySystem : IDestroySystem where T : Entity + { + public Type EntitiesType() => typeof(T); + protected abstract void Destroy(T self); + public void Invoke(Entity self) + { + Destroy((T) self); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs.meta new file mode 100644 index 00000000..c42232b3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IDestroySystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7140fd5ab56bd3b4ba04cfe40470ad23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs new file mode 100644 index 00000000..4b525a58 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs @@ -0,0 +1,10 @@ +using System; + +namespace TEngine +{ + public interface IEntitiesSystem + { + public Type EntitiesType(); + void Invoke(Entity entity); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs.meta new file mode 100644 index 00000000..2708bbf3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IEntitiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4e4fa4e598c849148a0dd63e785716f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs new file mode 100644 index 00000000..106f51b9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs @@ -0,0 +1,16 @@ +using System; + +namespace TEngine +{ + public interface IUpdateSystem : IEntitiesSystem { } + + public abstract class UpdateSystem : IUpdateSystem where T : Entity + { + public Type EntitiesType() => typeof(T); + protected abstract void Update(T self); + public void Invoke(Entity self) + { + Update((T) self); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs.meta new file mode 100644 index 00000000..5f932383 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Interface/System/IUpdateSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f39edd76db52bd84aa385cd02fa98db8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Scene.meta b/Assets/GameScripts/DotNet/Core/Entitas/Scene.meta new file mode 100644 index 00000000..1366ec8b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Scene.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d9f783d00ec372489401151473f8d7e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs b/Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs new file mode 100644 index 00000000..18e579db --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using TEngine.Core.Network; +using TEngine.Core; +#if TENGINE_NET +using TEngine.Core.DataBase; +#endif +#pragma warning disable CS8625 +#pragma warning disable CS8618 +namespace TEngine +{ + public sealed class Scene : Entity, INotSupportedPool + { + public string Name { get; private set; } + public uint RouteId { get; private set; } +#if TENGINE_UNITY + public Session Session { get; private set; } + public SceneConfigInfo SceneInfo { get; private set; } +#endif +#if TENGINE_NET + public World World { get; private set; } + public Server Server { get; private set; } + public uint SceneConfigId { get; private set; } + public SceneConfigInfo SceneInfo => ConfigTableManage.SceneConfig(SceneConfigId); +#endif + public static readonly List Scenes = new List(); + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + Name = null; + RouteId = 0; +#if TENGINE_NET + World = null; + Server = null; + SceneConfigId = 0; +#endif +#if TENGINE_UNITY + SceneInfo = null; + if (Session is { IsDisposed: false }) + { + Session.Dispose(); + Session = null; + } +#endif + Scenes.Remove(this); + base.Dispose(); + } + + public static void DisposeAllScene() + { + foreach (var scene in Scenes.ToArray()) + { + scene.Dispose(); + } + } +#if TENGINE_UNITY + public static Scene Create(string name) + { + var runTimeId = IdFactory.NextRunTimeId(); + var scene = CreateScene(runTimeId); + scene.Name = name; + Scenes.Add(scene); + return scene; + } + public void CreateSession(string remoteAddress, NetworkProtocolType networkProtocolType, Action onConnectComplete, Action onConnectFail, int connectTimeout = 5000) + { + var address = NetworkHelper.ToIPEndPoint(remoteAddress); + var clientNetworkComponent = AddComponent(); + clientNetworkComponent.Initialize(networkProtocolType, NetworkTarget.Outer); + clientNetworkComponent.Connect(address, onConnectComplete, onConnectFail, connectTimeout); + Session = clientNetworkComponent.Session; + } +#else + /// + /// 创建一个Scene + /// + /// + /// + /// + /// + /// + /// + public static async FTask Create(Server server, string outerBindIp, SceneConfigInfo sceneInfo, Action onSetNetworkComplete = null, bool runEvent = true) + { + var scene = CreateScene(sceneInfo.EntityId); + sceneInfo.Scene = scene; + scene.Name = sceneInfo.Name; + scene.RouteId = sceneInfo.RouteId; + scene.Server = server; + scene.SceneConfigId = sceneInfo.Id; + + if (sceneInfo.WorldId != 0) + { + // 有å¯èƒ½ä¸éœ€è¦æ•°æ®åº“ã€æ‰€ä»¥è¿™é‡Œé»˜è®¤0的情况下就ä¸åˆ›å»ºæ•°æ®åº“了 + scene.World = World.Create(sceneInfo.WorldId); + } + + if (!string.IsNullOrEmpty(sceneInfo.NetworkProtocol) && !string.IsNullOrEmpty(outerBindIp) && sceneInfo.OuterPort != 0) + { + // 设置Scene的网络ã€ç›®å‰åªæ”¯æŒKCPå’ŒTCP + var networkProtocolType = Enum.Parse(sceneInfo.NetworkProtocol); + var serverNetworkComponent = scene.AddComponent(); + var address = NetworkHelper.ToIPEndPoint($"{outerBindIp}:{sceneInfo.OuterPort}"); + serverNetworkComponent.Initialize(networkProtocolType, NetworkTarget.Outer, address); + } + + if (runEvent && sceneInfo.SceneType != null) + { + // 没有SceneTypeç›®å‰åªæœ‰ä»£ç åˆ›å»ºçš„Sceneæ‰ä¼šè¿™æ ·ã€ç›®å‰åªæœ‰Serverçš„Scene是这样 + await EventSystem.Instance.PublishAsync(new OnCreateScene(sceneInfo, onSetNetworkComplete)); + } + + Scenes.Add(scene); + return scene; + } + + /// + /// 一般用于创建临时Sceneã€å¦‚æžœä¸æ˜¯å¿…è¦ä¸å»ºè®®ä½¿ç”¨è¿™ä¸ªæŽ¥å£ + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async FTask Create(string name, Server server, long entityId, uint sceneConfigId = 0, string networkProtocol = null, string outerBindIp = null, int outerPort = 0, Action onSetNetworkComplete = null, bool runEvent = true) + { + var sceneInfo = new SceneConfigInfo() + { + Name = name, + EntityId = entityId, + Id = sceneConfigId, + NetworkProtocol = networkProtocol, + OuterPort = outerPort, + WorldId = ((EntityIdStruct)entityId).WordId + }; + + return await Create(server, outerBindIp, sceneInfo, onSetNetworkComplete, runEvent); + } + + public static List GetSceneInfoByRouteId(uint routeId) + { + var list = new List(); + var allSceneConfig = ConfigTableManage.AllSceneConfig(); + + foreach (var sceneConfigInfo in allSceneConfig) + { + if (sceneConfigInfo.RouteId != routeId) + { + continue; + } + + list.Add(sceneConfigInfo); + } + + return list; + } +#endif + } +} diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs.meta new file mode 100644 index 00000000..cddd73a4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Scene/Scene.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12c279f9aa38cab41b3bf04be0eea700 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs b/Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs new file mode 100644 index 00000000..03f27b3a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs @@ -0,0 +1,18 @@ +#if TENGINE_NET +using System; +using TEngine.Core.Network; +namespace TEngine +{ + public struct OnCreateScene + { + public readonly SceneConfigInfo SceneInfo; + public readonly Action OnSetNetworkComplete; + + public OnCreateScene(SceneConfigInfo sceneInfo, Action onSetNetworkComplete) + { + SceneInfo = sceneInfo; + OnSetNetworkComplete = onSetNetworkComplete; + } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs.meta new file mode 100644 index 00000000..e2e144d0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Scene/SceneEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e237f0cc7a65b51408d828a87535b4dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity.meta new file mode 100644 index 00000000..494449e2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7cdae026533d88e4684eca7b48850d12 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes.meta new file mode 100644 index 00000000..6c81ea1c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f3af894a35a86864fa02cf949eb89f03 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs new file mode 100644 index 00000000..2d152373 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs @@ -0,0 +1,11 @@ +#if TENGINE_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonDefaultValueAttribute : Attribute + { + public BsonDefaultValueAttribute(object defaultValue) { } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs.meta new file mode 100644 index 00000000..e23ead9e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonDefaultValueAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49dc7c79f6822c64e927ca4f5cad88f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs new file mode 100644 index 00000000..738258b8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs @@ -0,0 +1,13 @@ +#if TENGINE_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonElementAttribute : Attribute + { + public BsonElementAttribute() { } + + public BsonElementAttribute(string elementName) { } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs.meta new file mode 100644 index 00000000..58401921 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonElementAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9218b708df975ba428c2342e978cd473 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs new file mode 100644 index 00000000..a927a11e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs @@ -0,0 +1,13 @@ +#if TENGINE_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class BsonIdAttribute : Attribute + { + + } +} +#endif + + diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs.meta new file mode 100644 index 00000000..ce9cd2fb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIdAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e07e248c36907847adb96b09269689f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs new file mode 100644 index 00000000..cd496693 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs @@ -0,0 +1,11 @@ +#if TENGINE_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonIgnoreAttribute : Attribute + { + + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs.meta new file mode 100644 index 00000000..b7834c64 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a11815e9c0d9fe24f97da6a976538bfc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs new file mode 100644 index 00000000..db5ed11d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs @@ -0,0 +1,13 @@ +#if TENGINE_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonIgnoreIfDefaultAttribute : Attribute + { + public BsonIgnoreIfDefaultAttribute() { } + + public BsonIgnoreIfDefaultAttribute(bool value) { } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs.meta new file mode 100644 index 00000000..002cf812 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24366fa27fe479a439c306cae6a2cf98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs new file mode 100644 index 00000000..81bdf08f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs @@ -0,0 +1,11 @@ +#if TENGINE_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class BsonIgnoreIfNullAttribute : Attribute + { + + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs.meta b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs.meta new file mode 100644 index 00000000..cbd63cd0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Entitas/Unity/Attributes/BsonIgnoreIfNullAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb14d1148e275bf4f8220694eab51fc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/EventSystem.meta b/Assets/GameScripts/DotNet/Core/EventSystem.meta new file mode 100644 index 00000000..05cced74 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/EventSystem.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 29e4ce5c84d840a4c93fab95c1bbfd05 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs b/Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs new file mode 100644 index 00000000..8134e132 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs @@ -0,0 +1,177 @@ +using System; +using System.Threading.Tasks; +using TEngine.DataStructure; +using TEngine.Core; +using Type = System.Type; +#pragma warning disable CS8600 + +// ReSharper disable MethodOverloadWithOptionalParameter + +namespace TEngine +{ + internal sealed class EventInfo + { + public readonly Type Type; + public readonly object Obj; + + public EventInfo(Type type, object obj) + { + Type = type; + Obj = obj; + } + } + + public sealed class EventSystem : Singleton + { + private readonly OneToManyList _events = new(); + private readonly OneToManyList _asyncEvents = new(); + + private readonly OneToManyList _assemblyEvents = new(); + private readonly OneToManyList _assemblyAsyncEvents = new(); + + protected override void OnLoad(int assemblyName) + { + foreach (var type in AssemblyManager.ForEach(assemblyName, typeof(IEvent))) + { + var obj = (IEvent) Activator.CreateInstance(type); + + if (obj != null) + { + var eventType = obj.EventType(); + _events.Add(eventType, obj); + _assemblyEvents.Add(assemblyName, new EventInfo(eventType, obj)); + } + } + + foreach (var type in AssemblyManager.ForEach(assemblyName, typeof(IAsyncEvent))) + { + var obj = (IAsyncEvent) Activator.CreateInstance(type); + + if (obj != null) + { + var eventType = obj.EventType(); + _asyncEvents.Add(eventType, obj); + _assemblyAsyncEvents.Add(assemblyName, new EventInfo(eventType, obj)); + } + } + } + + protected override void OnUnLoad(int assemblyName) + { + if (_assemblyEvents.TryGetValue(assemblyName, out var events)) + { + foreach (var @event in events) + { + _events.RemoveValue(@event.Type, (IEvent)@event.Obj); + } + + _assemblyEvents.RemoveByKey(assemblyName); + } + + if (_assemblyAsyncEvents.TryGetValue(assemblyName, out var asyncEvents)) + { + foreach (var @event in asyncEvents) + { + _asyncEvents.RemoveValue(@event.Type, (IAsyncEvent)@event.Obj); + } + + _assemblyAsyncEvents.RemoveByKey(assemblyName); + } + } + + public void Publish(TEventData eventData) where TEventData : struct + { + if (!_events.TryGetValue(eventData.GetType(), out var list)) + { + return; + } + + foreach (var @event in list) + { + try + { + @event.Invoke(eventData); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public void Publish(TEventData eventData, bool isDisposed = true) where TEventData : Entity + { + if (!_events.TryGetValue(typeof(TEventData), out var list)) + { + return; + } + + foreach (var @event in list) + { + try + { + @event.Invoke(eventData); + } + catch (Exception e) + { + Log.Error(e); + } + } + + if (isDisposed) + { + eventData.Dispose(); + } + } + + public async FTask PublishAsync(TEventData eventData) where TEventData : struct + { + if (!_asyncEvents.TryGetValue(eventData.GetType(), out var list)) + { + return; + } + + using var tasks = ListPool.Create(); + + foreach (var @event in list) + { + tasks.Add(@event.InvokeAsync(eventData)); + } + + await FTask.WhenAll(tasks); + } + + public async FTask PublishAsync(TEventData eventData, bool isDisposed = true) where TEventData : Entity + { + if (!_asyncEvents.TryGetValue(eventData.GetType(), out var list)) + { + return; + } + + using var tasks = ListPool.Create(); + + foreach (var @event in list) + { + tasks.Add(@event.InvokeAsync(eventData)); + } + + await FTask.WhenAll(tasks); + + if (isDisposed) + { + eventData.Dispose(); + } + } + + public override void Dispose() + { + _events.Clear(); + _asyncEvents.Clear(); + _assemblyEvents.Clear(); + _assemblyAsyncEvents.Clear(); + AssemblyManager.OnLoadAssemblyEvent -= OnLoad; + AssemblyManager.OnUnLoadAssemblyEvent -= OnUnLoad; + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs.meta b/Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs.meta new file mode 100644 index 00000000..224b204a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/EventSystem/EventSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b56c6594aff226346866e463dbc20582 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/EventSystem/Interface.meta b/Assets/GameScripts/DotNet/Core/EventSystem/Interface.meta new file mode 100644 index 00000000..5422cf26 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/EventSystem/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 582e470763f128640a6c6a2ef0c34243 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs b/Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs new file mode 100644 index 00000000..18b7e8fb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs @@ -0,0 +1,64 @@ +using System; + +namespace TEngine +{ + public interface IEvent + { + Type EventType(); + void Invoke(object self); + } + + public interface IAsyncEvent + { + Type EventType(); + FTask InvokeAsync(object self); + } + + public abstract class EventSystem : IEvent + { + private readonly Type _selfType = typeof(T); + + public Type EventType() + { + return _selfType; + } + + public abstract void Handler(T self); + + public void Invoke(object self) + { + try + { + Handler((T) self); + } + catch (Exception e) + { + Log.Error($"{_selfType.Name} Error {e}"); + } + } + } + + public abstract class AsyncEventSystem : IAsyncEvent + { + private readonly Type _selfType = typeof(T); + + public Type EventType() + { + return _selfType; + } + + public abstract FTask Handler(T self); + + public async FTask InvokeAsync(object self) + { + try + { + await Handler((T) self); + } + catch (Exception e) + { + Log.Error($"{_selfType.Name} Error {e}"); + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs.meta b/Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs.meta new file mode 100644 index 00000000..cc069ad2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/EventSystem/Interface/IEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d36b90aef6077f941b9fe043ab4889b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter.meta b/Assets/GameScripts/DotNet/Core/Exporter.meta new file mode 100644 index 00000000..8664db9a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0e276cb6ff6d7414bacb29a4cd3a610a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel.meta new file mode 100644 index 00000000..7ecd1b1d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd2dcfcb8e4f2cd458f5b287c26f8dc4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base.meta new file mode 100644 index 00000000..2991af22 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 374826e14ee51d24b9386dcdba282238 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs new file mode 100644 index 00000000..565eb698 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs @@ -0,0 +1,144 @@ +#if TENGINE_NET +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ProtoBuf; + +#pragma warning disable CS8600 +#pragma warning disable CS8601 + +namespace TEngine.Core; + +public static class DynamicAssembly +{ + public static Assembly Load(string path) + { + var fileList = new List(); + + // 找到所有需è¦åŠ è½½çš„CS文件 + + foreach (string file in Directory.GetFiles(path)) + { + if (Path.GetExtension(file) != ".cs") + { + continue; + } + + fileList.Add(file); + } + + var syntaxTreeList = new List(); + + foreach (var file in fileList) + { + using var fileStream = new StreamReader(file); + var cSharp = CSharpSyntaxTree.ParseText(fileStream.ReadToEnd()); + syntaxTreeList.Add(cSharp); + } + + AssemblyMetadata assemblyMetadata; + MetadataReference metadataReference; + var currentDomain = AppDomain.CurrentDomain; + var assemblyName = Path.GetRandomFileName(); + var assemblyArray = currentDomain.GetAssemblies(); + var metadataReferenceList = new List(); + + // 注册引用 + + foreach (var domainAssembly in assemblyArray) + { + if (string.IsNullOrEmpty(domainAssembly.Location)) + { + continue; + } + + assemblyMetadata = AssemblyMetadata.CreateFromFile(domainAssembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + } + + // 添加ProtoEntityæ”¯æŒ + + assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(AProto).Assembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + + // 添加ProtoBuf.netæ”¯æŒ + + assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ProtoMemberAttribute).Assembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + + CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, syntaxTreeList, metadataReferenceList, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + using var ms = new MemoryStream(); + + var result = compilation.Emit(ms); + if (!result.Success) + { + foreach (var resultDiagnostic in result.Diagnostics) + { + Exporter.LogError(resultDiagnostic.GetMessage()); + } + + throw new Exception("failures"); + } + + ms.Seek(0, SeekOrigin.Begin); + return Assembly.Load(ms.ToArray()); + } + + public static DynamicConfigDataType GetDynamicInfo(Assembly dynamicAssembly, string tableName) + { + var dynamicConfigDataType = new DynamicConfigDataType + { + ConfigDataType = GetConfigType(dynamicAssembly, $"{tableName}Data"), + ConfigType = GetConfigType(dynamicAssembly, $"{tableName}") + }; + + dynamicConfigDataType.ConfigData = CreateInstance(dynamicConfigDataType.ConfigDataType); + var listPropertyType = dynamicConfigDataType.ConfigDataType.GetProperty("List"); + + if (listPropertyType == null) + { + throw new Exception("No Property named Add was found"); + } + + dynamicConfigDataType.Obj = listPropertyType.GetValue(dynamicConfigDataType.ConfigData); + dynamicConfigDataType.Method = listPropertyType.PropertyType.GetMethod("Add"); + + if (dynamicConfigDataType.Method == null) + { + throw new Exception("No method named Add was found"); + } + + return dynamicConfigDataType; + } + + private static Type GetConfigType(Assembly dynamicAssembly, string typeName) + { + var configType = dynamicAssembly.GetType($"TEngine.{typeName}"); + + if (configType == null) + { + throw new FileNotFoundException($"TEngine.{typeName} not found"); + } + + return configType; + // return dynamicAssembly.GetType($"TEngine.{typeName}"); + } + + public static AProto CreateInstance(Type configType) + { + var config = (AProto) Activator.CreateInstance(configType); + + if (config == null) + { + throw new Exception($"{configType.Name} is Activator.CreateInstance error"); + } + + return config; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs.meta new file mode 100644 index 00000000..9439d69d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicAssembly.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5ba993a045ee2d94da4827d5eff902af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs new file mode 100644 index 00000000..dd67c3ce --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs @@ -0,0 +1,16 @@ +#if TENGINE_NET +using System.Reflection; +using System.Text; + +namespace TEngine.Core; + +public class DynamicConfigDataType +{ + public AProto ConfigData; + public Type ConfigDataType; + public Type ConfigType; + public MethodInfo Method; + public object Obj; + public StringBuilder Json = new StringBuilder(); +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs.meta new file mode 100644 index 00000000..11e0d3fb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/DynamicConfigDataType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85d5d45679512064fa1c6dbbbc5ff29c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs new file mode 100644 index 00000000..9a293460 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs @@ -0,0 +1,75 @@ +#if TENGINE_NET +namespace TEngine.Core; + +public static class ExcelDefine +{ + /// + /// 项目跟目录路径 + /// + private const string ProjectPath = "../../.."; + /// + /// é…置文件根目录 + /// + public static string ProgramPath = $"{ProjectPath}/Config/Excel/"; + /// + /// 版本文件Excel + /// + public static string ExcelVersionFile = $"{ProgramPath}Version.txt"; + /// + /// æœåС噍代ç ç”Ÿæˆæ–‡ä»¶å¤¹ + /// + public static string ServerFileDirectory = $"{ProjectPath}/Server/TEngine.Model/Generate/ConfigTable/Entity/"; + /// + /// 客户端代ç ç”Ÿæˆæ–‡ä»¶å¤¹ + /// + public static string ClientFileDirectory = $"{ProjectPath}/Client/Unity/Assets/Scripts/TEngine/TEngine.Model/Generate/ConfigTable/Entity/"; + /// + /// æœåŠ¡å™¨äºŒè¿›åˆ¶æ•°æ®æ–‡ä»¶å¤¹ + /// + public static string ServerBinaryDirectory = $"{ProjectPath}/Config/Binary/"; + /// + /// å®¢æˆ·ç«¯äºŒè¿›åˆ¶æ•°æ®æ–‡ä»¶å¤¹ + /// + public static string ClientBinaryDirectory = $"{ProjectPath}/Client/Unity/Assets/Bundles/Config/"; + /// + /// æœåС噍Jsonæ•°æ®æ–‡ä»¶å¤¹ + /// + public static string ServerJsonDirectory = $"{ProjectPath}/Config/Json/Server/"; + /// + /// 客户端Jsonæ•°æ®æ–‡ä»¶å¤¹ + /// + public static string ClientJsonDirectory = $"{ProjectPath}/Config/Json/Client/"; + /// + /// æœåŠ¡å™¨è‡ªå®šä¹‰å¯¼å‡ºä»£ç  + /// + public static string ServerCustomExportDirectory = $"{ProjectPath}/Server/TEngine.Model/Generate/CustomExport/"; + /// + /// å®¢æˆ·ç«¯è‡ªå®šä¹‰å¯¼å‡ºä»£ç  + /// + public static string ClientCustomExportDirectory = $"{ProjectPath}/Client/Unity/Assets/Scripts/TEngine/TEngine.Model/Generate/CustomExport/"; + /// + /// 导表支æŒçš„类型 + /// + public static readonly HashSet ColTypeSet = new HashSet() + { + "", "0", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "string", "AttrConfig", + "short[]", "int[]", "long[]", "float[]", "string[]" + }; + /// + /// Excel生æˆä»£ç æ¨¡æ¿çš„ä½ç½® + /// + public static string ExcelTemplatePath = $"{ProjectPath}/Config/Template/ExcelTemplate.txt"; + /// + /// ä»£ç æ¨¡æ¿ + /// + public static string ExcelTemplate + { + get + { + return _template ??= File.ReadAllText(Path.Combine(Environment.CurrentDirectory, $"{ProjectPath}/Config/Template/ExcelTemplate.txt")); + } + } + private static string _template; +} + +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs.meta new file mode 100644 index 00000000..2f4333bb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelDefine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0dac52ee40f938641a63d624ff19dd3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs new file mode 100644 index 00000000..d649fc8e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs @@ -0,0 +1,15 @@ +#if TENGINE_NET +namespace TEngine.Core; + +public sealed class ExcelTable +{ + public readonly string Name; + public readonly SortedDictionary> ClientColInfos = new(); + public readonly SortedDictionary> ServerColInfos = new(); + + public ExcelTable(string name) + { + Name = name; + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs.meta new file mode 100644 index 00000000..311c2005 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExcelTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae216cf2a8852c54d9b1de65bc64fafa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs new file mode 100644 index 00000000..538e9a87 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs @@ -0,0 +1,9 @@ +#if TENGINE_NET +namespace TEngine.Core; + +public class ExportInfo +{ + public string Name; + public FileInfo FileInfo; +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs.meta new file mode 100644 index 00000000..2bd72782 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b19f3554768a6647b36f89e6a7c2116 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs new file mode 100644 index 00000000..6a9c6f83 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs @@ -0,0 +1,12 @@ +#if TENGINE_NET +namespace TEngine.Core; + +public enum ExportType +{ + None = 0, + ProtoBuf = 1, // 导出ProtoBuf + AllExcelIncrement = 2, // 所有-增é‡å¯¼å‡ºExcel + AllExcel = 3, // 所有-å…¨é‡å¯¼å‡ºExcel + Max, // è¿™ä¸ªä¸€å®šæ”¾æœ€åŽ +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs.meta new file mode 100644 index 00000000..fb983303 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/Base/ExportType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c7d1a9efd4b9a94bb7c4d99a7160068 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs new file mode 100644 index 00000000..59bd9162 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs @@ -0,0 +1,84 @@ +#if TENGINE_UNITY +using System; +using System.Collections.Generic; +using TEngine.Core; +using UnityEngine; + +namespace TEngine.Core +{ + public static class ConfigTableManage + { + private static readonly string ConfigBundle = "Config".ToLower(); + private static readonly Dictionary ConfigDic = new (); + + static ConfigTableManage() + { + + } + + public static T Load() where T : AProto + { + var dataConfig = typeof(T).Name; + + if (ConfigDic.TryGetValue(dataConfig, out var aProto)) + { + return (T)aProto; + } + + try + { + var bytes = GameModule.Resource.LoadAsset($"{ConfigBundle}-{dataConfig}").bytes; + var data = (AProto) ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length); + data.AfterDeserialization(); + ConfigDic[dataConfig] = data; + return (T)data; + } + catch (Exception ex) + { + throw new Exception($"ConfigTableManage:{typeof(T).Name} æ•°æ®è¡¨åŠ è½½ä¹‹åŽååºåˆ—化时出错:{ex}"); + } + } + + private static AProto Load(string dataConfig, int assemblyName) + { + if (ConfigDic.TryGetValue(dataConfig, out var aProto)) + { + return aProto; + } + + var fullName = $"TEngine.{dataConfig}"; + var assembly = AssemblyManager.GetAssembly(assemblyName); + var type = assembly.GetType(fullName); + + if (type == null) + { + Log.Error($"not find {fullName} in assembly"); + return null; + } + + try + { + var bytes = GameModule.Resource.LoadAsset($"{ConfigBundle}-{dataConfig}").bytes; + var data = (AProto) ProtoBufHelper.FromBytes(type, bytes, 0, bytes.Length); + data.AfterDeserialization(); + ConfigDic[dataConfig] = data; + return data; + } + catch (Exception ex) + { + throw new Exception($"ConfigTableManage:{type.Name} æ•°æ®è¡¨åŠ è½½ä¹‹åŽååºåˆ—化时出错:{ex}"); + } + } + + private static void Reload() + { + foreach (var (_, aProto) in ConfigDic) + { + ((IDisposable) aProto).Dispose(); + } + + ConfigDic.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs.meta new file mode 100644 index 00000000..600aea99 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ClientConfigTableManage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30f0a5e8602ad3d4f83d9e275d1edd15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs new file mode 100644 index 00000000..4ce11d04 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs @@ -0,0 +1,788 @@ +#if TENGINE_NET +using System.Collections.Concurrent; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Text.RegularExpressions; +using TEngine.DataStructure; +using TEngine.Core; +using Newtonsoft.Json; +using OfficeOpenXml; +using static System.String; +#pragma warning disable CS8625 +#pragma warning disable CS8604 +#pragma warning disable CS8602 +#pragma warning disable CS8601 +#pragma warning disable CS8600 +#pragma warning disable CS8618 + +namespace TEngine.Core; + +using TableDictionary = SortedDictionary>; + +public sealed class ExcelExporter +{ + private Dictionary _versionDic; + private readonly Regex _regexName = new Regex("^[a-zA-Z][a-zA-Z0-9_]*$"); + private readonly HashSet _loadFiles = new HashSet {".xlsx", ".xlsm", ".csv"}; + private readonly OneToManyList _tables = new OneToManyList(); + private readonly ConcurrentDictionary _excelTables = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _worksheets = new ConcurrentDictionary(); + + public ExcelExporter(ExportType exportType) + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + var versionFilePath = ExcelDefine.ExcelVersionFile; + + switch (exportType) + { + case ExportType.AllExcelIncrement: + { + break; + } + case ExportType.AllExcel: + { + if (File.Exists(versionFilePath)) + { + File.Delete(versionFilePath); + } + + FileHelper.ClearDirectoryFile(ExcelDefine.ServerFileDirectory); + FileHelper.ClearDirectoryFile(ExcelDefine.ClientFileDirectory); + break; + } + } + + Find(); + Parsing(); + ExportToBinary(); + File.WriteAllText(versionFilePath, JsonConvert.SerializeObject(_versionDic)); + CustomExport(); + } + + private static void CustomExport() + { + // 清除文件夹 + FileHelper.ClearDirectoryFile(ExcelDefine.ServerCustomExportDirectory); + FileHelper.ClearDirectoryFile(ExcelDefine.ClientCustomExportDirectory); + // 找到程åºé›† + var assemblyLoadContext = new AssemblyLoadContext("ExporterDll", true); + var dllBytes = File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, "TEngine.Model.dll")); + var pdbBytes = File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, "TEngine.Model.pdb")); + var assembly = assemblyLoadContext.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); + // 加载程åºé›† + AssemblyManager.LoadAssembly(int.MaxValue, assembly); + + // 执行自定义导出 + + var task = new List(); + + foreach (var type in AssemblyManager.ForEach(typeof(ICustomExport))) + { + var customExport = (ICustomExport)Activator.CreateInstance(type); + + if (customExport != null) + { + task.Add(Task.Run(customExport.Run)); + } + } + + Task.WaitAll(task.ToArray()); + } + + /// + /// 查找é…置文件 + /// + private void Find() + { + var versionFilePath = ExcelDefine.ExcelVersionFile; + + if(File.Exists(versionFilePath)) + { + var versionJson = File.ReadAllText(versionFilePath); + _versionDic = JsonConvert.DeserializeObject>(versionJson); + } + else + { + _versionDic = new Dictionary(); + } + + var dir = new DirectoryInfo(ExcelDefine.ProgramPath); + var excelFiles = dir.GetFiles("*", SearchOption.AllDirectories); + + if (excelFiles.Length <= 0) + { + return; + } + + foreach (var excelFile in excelFiles) + { + // è¿‡æ»¤æŽ‰éžæŒ‡å®šåŽç¼€çš„æ–‡ä»¶ + + if (!_loadFiles.Contains(excelFile.Extension)) + { + continue; + } + + var lastIndexOf = excelFile.Name.LastIndexOf(".", StringComparison.Ordinal); + + if (lastIndexOf < 0) + { + continue; + } + + var fullName = excelFile.FullName; + var excelName = excelFile.Name.Substring(0, lastIndexOf); + var path = fullName.Substring(0, fullName.Length - excelFile.Name.Length); + + // 过滤#å’Œ~开头文件和文件夹带有#的所有文件 + + if (excelName.StartsWith("#", StringComparison.Ordinal) || + excelName.StartsWith("~", StringComparison.Ordinal) || + path.Contains("#", StringComparison.Ordinal)) + { + continue; + } + + if (!_regexName.IsMatch(excelName)) + { + Exporter.LogError($"{excelName} é…置文件åéžæ³•"); + continue; + } + + _tables.Add(excelName.Split('_')[0], new ExportInfo() + { + Name = excelName, FileInfo = excelFile + }); + } + + var removeTables = new List(); + + foreach (var (tableName, tableList) in _tables) + { + var isNeedExport = false; + + foreach (var exportInfo in tableList) + { + var timer = TimeHelper.Transition(exportInfo.FileInfo.LastWriteTime); + + if (!isNeedExport) + { + if (_versionDic.TryGetValue(exportInfo.Name, out var lastWriteTime)) + { + isNeedExport = lastWriteTime != timer; + } + else + { + isNeedExport = true; + } + } + + _versionDic[exportInfo.Name] = timer; + } + + if (!isNeedExport) + { + removeTables.Add(tableName); + } + } + + foreach (var removeTable in removeTables) + { + _tables.Remove(removeTable); + } + + foreach (var (_, exportInfo) in _tables) + { + exportInfo.Sort((x, y) => Compare(x.Name, y.Name, StringComparison.Ordinal)); + } + } + + /// + /// 生æˆé…置文件 + /// + private void Parsing() + { + var generateTasks = new List(); + + foreach (var (tableName, tableList) in _tables) + { + var task = Task.Run(() => + { + var writeToClassTask = new List(); + var excelTable = new ExcelTable(tableName); + + // 筛选需è¦å¯¼å‡ºçš„列 + + foreach (var exportInfo in tableList) + { + try + { + var serverColInfoList = new List(); + var clientColInfoList = new List(); + var worksheet = LoadExcel(exportInfo.FileInfo.FullName, true); + + for (var col = 3; col <= worksheet.Columns.EndColumn; col++) + { + // 列å字第一个字符是#ä¸å‚与导出 + + var colName = GetCellValue(worksheet, 5, col); + if (colName.StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + // 数值列ä¸å‚与导出 + + var numericalCol = GetCellValue(worksheet, 3, col); + if (numericalCol != "" && numericalCol != "0") + { + continue; + } + + var serverType = GetCellValue(worksheet, 1, col); + var clientType = GetCellValue(worksheet, 2, col); + var isExportServer = !IsNullOrEmpty(serverType) && serverType != "0"; + var isExportClient = !IsNullOrEmpty(clientType) && clientType != "0"; + + if (!isExportServer && !isExportClient) + { + continue; + } + + if (isExportServer && isExportClient & serverType != clientType) + { + Exporter.LogError($"é…置表 {exportInfo.Name} {col} 列 [{colName}] 客户端类型 {clientType} å’Œ æœåŠ¡ç«¯ç±»åž‹ {serverType} ä¸ä¸€è‡´"); + continue; + } + + if (!ExcelDefine.ColTypeSet.Contains(serverType) || + !ExcelDefine.ColTypeSet.Contains(clientType)) + { + Exporter.LogError($"é…置表 {exportInfo.Name} {col} 列 [{colName}] 客户端类型 {clientType}, æœåŠ¡ç«¯ç±»åž‹ {serverType} ä¸åˆæ³•"); + continue; + } + + if (!_regexName.IsMatch(colName)) + { + Exporter.LogError($"é…置表 {exportInfo.Name} {col} 列 [{colName}] 列åéžæ³•"); + continue; + } + + serverColInfoList.Add(col); + + if (isExportClient) + { + clientColInfoList.Add(col); + } + } + + if (clientColInfoList.Count > 0) + { + excelTable.ClientColInfos.Add(exportInfo.FileInfo.FullName, clientColInfoList); + } + + if (serverColInfoList.Count > 0) + { + excelTable.ServerColInfos.Add(exportInfo.FileInfo.FullName, serverColInfoList); + } + } + catch (Exception e) + { + Exporter.LogError($"Config : {tableName}, Name : {exportInfo.Name}, Error : {e}"); + } + } + + // 生æˆcs文件 + + writeToClassTask.Add(Task.Run(() => + { + WriteToClass(excelTable.ServerColInfos, ExcelDefine.ServerFileDirectory, true); + })); + + writeToClassTask.Add(Task.Run(() => + { + WriteToClass(excelTable.ClientColInfos, ExcelDefine.ClientFileDirectory, false); + })); + + Task.WaitAll(writeToClassTask.ToArray()); + _excelTables.TryAdd(tableName, excelTable); + }); + + generateTasks.Add(task); + } + + Task.WaitAll(generateTasks.ToArray()); + } + + /// + /// 写入到cs + /// + /// + /// + /// + private void WriteToClass(TableDictionary colInfos, string exportPath, bool isServer) + { + if (colInfos.Count <= 0) + { + return; + } + + var index = 0; + var fileBuilder = new StringBuilder(); + var colNameSet = new HashSet(); + + if (colInfos.Count == 0) + { + return; + } + + var csName = Path.GetFileNameWithoutExtension(colInfos.First().Key)?.Split('_')[0]; + + foreach (var (tableName, cols) in colInfos) + { + if (cols == null || cols.Count == 0) + { + continue; + } + + var excelWorksheet = LoadExcel(tableName, false); + + foreach (var colIndex in cols) + { + var colName = GetCellValue(excelWorksheet, 5, colIndex); + + if (colNameSet.Contains(colName)) + { + continue; + } + + colNameSet.Add(colName); + + string colType; + + if (isServer) + { + colType = GetCellValue(excelWorksheet, 1, colIndex); + + if (IsNullOrEmpty(colType) || colType == "0") + { + colType = GetCellValue(excelWorksheet, 2, colIndex); + } + } + else + { + colType = GetCellValue(excelWorksheet, 2, colIndex); + } + + var remarks = GetCellValue(excelWorksheet, 4, colIndex); + + fileBuilder.Append($"\n\t\t[ProtoMember({++index}, IsRequired = true)]\n"); + fileBuilder.Append( + IsArray(colType,out var t) + ? $"\t\tpublic {colType} {colName} {{ get; set; }} = Array.Empty<{t}>(); // {remarks}" + : $"\t\tpublic {colType} {colName} {{ get; set; }} // {remarks}"); + } + } + + var template = ExcelDefine.ExcelTemplate; + + if (fileBuilder.Length > 0) + { + if (!Directory.Exists(exportPath)) + { + Directory.CreateDirectory(exportPath); + } + + var content = template.Replace("(namespace)", "TEngine") + .Replace("(ConfigName)", csName) + .Replace("(Fields)", fileBuilder.ToString()); + File.WriteAllText(Path.Combine(exportPath, $"{csName}.cs"), content); + } + } + + /// + /// 把数æ®å’Œå®žä½“类转æ¢äºŒè¿›åˆ¶å¯¼å‡ºåˆ°æ–‡ä»¶ä¸­ + /// + private void ExportToBinary() + { + var exportToBinaryTasks = new List(); + var dynamicServerAssembly = DynamicAssembly.Load(ExcelDefine.ServerFileDirectory); + var dynamicClientAssembly = DynamicAssembly.Load(ExcelDefine.ClientFileDirectory); + + foreach (var (tableName, tableList) in _tables) + { + var task = Task.Run(() => + { + var idCheck = new HashSet(); + var excelTable = _excelTables[tableName]; + var csName = Path.GetFileNameWithoutExtension(tableName); + var serverColInfoCount = excelTable.ServerColInfos.Sum(d=>d.Value.Count); + var serverDynamicInfo = serverColInfoCount == 0 ? null : DynamicAssembly.GetDynamicInfo(dynamicServerAssembly, csName); + var clientColInfoCount = excelTable.ClientColInfos.Sum(d=>d.Value.Count); + var clientDynamicInfo = clientColInfoCount == 0 ? null : DynamicAssembly.GetDynamicInfo(dynamicClientAssembly, csName); + + for (var i = 0; i < tableList.Count; i++) + { + var tableListName = tableList[i]; + + try + { + var fileInfoFullName = tableListName.FileInfo.FullName; + var excelWorksheet = LoadExcel(fileInfoFullName, false); + var rows = excelWorksheet.Dimension.Rows; + excelTable.ServerColInfos.TryGetValue(fileInfoFullName, out var serverCols); + excelTable.ClientColInfos.TryGetValue(fileInfoFullName, out var clientCols); + + for (var row = 7; row <= rows; row++) + { + if (GetCellValue(excelWorksheet, row, 1).StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + var id = GetCellValue(excelWorksheet, row, 3); + + if (idCheck.Contains(id)) + { + Exporter.LogError($"{tableListName.Name} 存在é‡å¤Id {id} è¡Œå· {row}"); + continue; + } + + idCheck.Add(id); + var isLast = row == rows && (i == tableList.Count - 1); + GenerateBinary(fileInfoFullName, excelWorksheet, serverDynamicInfo, serverCols, id, row, isLast, true); + GenerateBinary(fileInfoFullName, excelWorksheet, clientDynamicInfo, clientCols, id, row, isLast, false); + } + } + catch (Exception e) + { + Exporter.LogError($"Table:{tableListName} error! \n{e}"); + throw; + } + } + + if (serverDynamicInfo?.ConfigData != null) + { + var bytes = ProtoBufHelper.ToBytes(serverDynamicInfo.ConfigData); + var serverBinaryDirectory = ExcelDefine.ServerBinaryDirectory; + + if (!Directory.Exists(serverBinaryDirectory)) + { + Directory.CreateDirectory(serverBinaryDirectory); + } + + File.WriteAllBytes(Path.Combine(serverBinaryDirectory, $"{csName}Data.bytes"), bytes); + + if (serverDynamicInfo.Json.Length > 0) + { + var serverJsonDirectory = ExcelDefine.ServerJsonDirectory; + using var sw = new StreamWriter(Path.Combine(serverJsonDirectory, $"{csName}Data.Json")); + sw.WriteLine("{\"List\":["); + sw.Write(serverDynamicInfo.Json.ToString()); + sw.WriteLine("]}"); + } + } + + if (clientDynamicInfo?.ConfigData != null) + { + var bytes = ProtoBufHelper.ToBytes(clientDynamicInfo.ConfigData); + var clientBinaryDirectory = ExcelDefine.ClientBinaryDirectory; + + if (!Directory.Exists(clientBinaryDirectory)) + { + Directory.CreateDirectory(clientBinaryDirectory); + } + + File.WriteAllBytes(Path.Combine(clientBinaryDirectory, $"{csName}Data.bytes"), bytes); + + if (clientDynamicInfo.Json.Length > 0) + { + var clientJsonDirectory = ExcelDefine.ClientJsonDirectory; + using var sw = new StreamWriter(Path.Combine(clientJsonDirectory, $"{csName}Data.Json")); + sw.WriteLine("{\"List\":["); + sw.Write(clientDynamicInfo.Json.ToString()); + sw.WriteLine("]}"); + } + } + }); + exportToBinaryTasks.Add(task); + } + + Task.WaitAll(exportToBinaryTasks.ToArray()); + } + + private void GenerateBinary(string fileInfoFullName, ExcelWorksheet excelWorksheet, DynamicConfigDataType dynamicInfo, List cols, string id, int row, bool isLast, bool isServer) + { + if (cols == null || IsNullOrEmpty(id) || cols.Count <= 0 || dynamicInfo?.ConfigType == null) + { + return; + } + + var config = DynamicAssembly.CreateInstance(dynamicInfo.ConfigType); + + for (var i = 0; i < cols.Count; i++) + { + string colType; + var colIndex = cols[i]; + var colName = GetCellValue(excelWorksheet, 5, colIndex); + var value = GetCellValue(excelWorksheet, row, colIndex); + + if (isServer) + { + colType = GetCellValue(excelWorksheet, 1, colIndex); + + if (IsNullOrEmpty(colType) || colType == "0") + { + colType = GetCellValue(excelWorksheet, 2, colIndex); + } + } + else + { + colType = GetCellValue(excelWorksheet, 2, colIndex); + } + + try + { + SetNewValue(dynamicInfo.ConfigType.GetProperty(colName), config, colType, value); + } + catch (Exception e) + { + Exporter.LogError($"Error Table {fileInfoFullName} Col:{colName} colType:{colType} Row:{row} value:{value} {e}"); + throw; + } + } + + dynamicInfo.Method.Invoke(dynamicInfo.Obj, new object[] {config}); + + var json = JsonConvert.SerializeObject(config); + + if (isLast) + { + dynamicInfo.Json.AppendLine(json); + } + else + { + dynamicInfo.Json.AppendLine($"{json},"); + } + } + + public ExcelWorksheet LoadExcel(string name, bool isAddToDic) + { + if (_worksheets.TryGetValue(name, out var worksheet)) + { + return worksheet; + } + + worksheet = new ExcelPackage(name).Workbook.Worksheets[0]; + + if (isAddToDic) + { + _worksheets.TryAdd(name, worksheet); + } + + Exporter.LogInfo(name); + return worksheet; + } + + private string GetCellValue(ExcelWorksheet sheet, int row, int column) + { + var cell = sheet.Cells[row, column]; + + try + { + if (cell.Value == null) + { + return ""; + } + + var s = cell.GetValue(); + return s.Trim(); + } + catch (Exception e) + { + throw new Exception($"Rows {row} Columns {column} Content {cell.Text} {e}"); + } + } + + private void SetNewValue(PropertyInfo propertyInfo, AProto config, string type, string value) + { + if (IsNullOrWhiteSpace(value)) + { + return; + } + + switch (type) + { + case "short": + { + propertyInfo.SetValue(config, Convert.ToInt16(value)); + return; + } + case "ushort": + { + propertyInfo.SetValue(config, Convert.ToUInt16(value)); + return; + } + case "uint": + { + propertyInfo.SetValue(config, Convert.ToUInt32(value)); + return; + } + case "int": + { + propertyInfo.SetValue(config, Convert.ToInt32(value)); + return; + } + case "decimal": + { + propertyInfo.SetValue(config, Convert.ToDecimal(value)); + return; + } + case "string": + { + try + { + propertyInfo.SetValue(config, value); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + return; + } + case "bool": + { + // 空字符串 + + value = value.ToLower(); + + if (IsNullOrEmpty(value)) + { + propertyInfo.SetValue(config, false); + } + else if (bool.TryParse(value, out bool b)) + { + propertyInfo.SetValue(config, b); + } + else if (int.TryParse(value, out int v)) + { + propertyInfo.SetValue(config, v != 0); + } + else + { + propertyInfo.SetValue(config, false); + } + + return; + } + case "ulong": + { + propertyInfo.SetValue(config, Convert.ToUInt64(value)); + return; + } + case "long": + { + propertyInfo.SetValue(config, Convert.ToInt64(value)); + return; + } + case "double": + { + propertyInfo.SetValue(config, Convert.ToDouble(value)); + return; + } + case "float": + { + propertyInfo.SetValue(config, Convert.ToSingle(value)); + return; + } + case "int32[]": + case "int[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToInt32(d)).ToArray()); + } + + return; + } + case "long[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToInt64(d)).ToArray()); + } + + return; + } + case "double[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToDouble(d)).ToArray()); + } + + return; + } + case "string[]": + { + if (value == "0") + { + return; + } + + var list = value.Split(",").ToArray(); + + for (var i = 0; i < list.Length; i++) + { + list[i] = list[i].Replace("\"", ""); + } + + propertyInfo.SetValue(config, value.Split(",").ToArray()); + + return; + } + case "float[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToSingle(d)).ToArray()); + } + + return; + } + // case "AttrConfig": + // { + // if (value.Trim() == "" || value.Trim() == "{}") + // { + // propertyInfo.SetValue(config, null); + // return; + // } + // + // var attr = new AttrConfig {KV = JsonConvert.DeserializeObject>(value)}; + // + // propertyInfo.SetValue(config, attr); + // + // return; + // } + default: + throw new NotSupportedException($"䏿”¯æŒæ­¤ç±»åž‹: {type}"); + } + } + + private bool IsArray(string type, out string t) + { + t = null; + var index = type.IndexOf("[]", StringComparison.Ordinal); + + if (index >= 0) + { + t = type.Remove(index, 2); + } + + return index >= 0; + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs.meta new file mode 100644 index 00000000..9f9c09f4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ExcelExporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11f644344212d644dbfd6cae6ff1f133 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs new file mode 100644 index 00000000..5d020cc9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs @@ -0,0 +1,102 @@ +#if TENGINE_NET +using TEngine.Core.DataBase; +using TEngine.Core; +#pragma warning disable CS8603 +#pragma warning disable CS8618 + +namespace TEngine.Core +{ + public static class ConfigTableManage + { + public static Func ServerConfig; + public static Func MachineConfig; + public static Func SceneConfig; + public static Func WorldConfigInfo; + + public static Func> AllServerConfig; + public static Func> AllMachineConfig; + public static Func> AllSceneConfig; + + private const string BinaryDirectory = "../../../Config/Binary/"; + private static readonly Dictionary ConfigDic = new (); + + public static T Load() where T : AProto + { + var dataConfig = typeof(T).Name; + + if (ConfigDic.TryGetValue(dataConfig, out var aProto)) + { + return (T)aProto; + } + + try + { + var configFile = GetConfigPath(dataConfig); + var bytes = File.ReadAllBytes(configFile); + var data = (AProto) ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length); + data.AfterDeserialization(); + ConfigDic[dataConfig] = data; + return (T)data; + } + catch (Exception ex) + { + throw new Exception($"ConfigTableManage:{typeof(T).Name} æ•°æ®è¡¨åŠ è½½ä¹‹åŽååºåˆ—化时出错:{ex}"); + } + } + + private static AProto Load(string dataConfig, int assemblyName) + { + if (ConfigDic.TryGetValue(dataConfig, out var aProto)) + { + return aProto; + } + + var fullName = $"TEngine.{dataConfig}"; + var assembly = AssemblyManager.GetAssembly(assemblyName); + var type = assembly.GetType(fullName); + + if (type == null) + { + Log.Error($"not find {fullName} in assembly"); + return null; + } + + try + { + var configFile = GetConfigPath(type.Name); + var bytes = File.ReadAllBytes(configFile); + var data = (AProto) ProtoBufHelper.FromBytes(type, bytes, 0, bytes.Length); + data.AfterDeserialization(); + ConfigDic[dataConfig] = data; + return data; + } + catch (Exception ex) + { + throw new Exception($"ConfigTableManage:{type.Name} æ•°æ®è¡¨åŠ è½½ä¹‹åŽååºåˆ—化时出错:{ex}"); + } + } + + private static string GetConfigPath(string name) + { + var configFile = Path.Combine(BinaryDirectory, $"{name}.bytes"); + + if (File.Exists(configFile)) + { + return configFile; + } + + throw new FileNotFoundException($"{name}.byte not found: {configFile}"); + } + + private static void Reload() + { + foreach (var (_, aProto) in ConfigDic) + { + ((IDisposable) aProto).Dispose(); + } + + ConfigDic.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs.meta new file mode 100644 index 00000000..7096eea2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04d66af120535af4a9dbbbf4e73931a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs b/Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs new file mode 100644 index 00000000..e6a28bf4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs @@ -0,0 +1,130 @@ +#if TENGINE_NET +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using TEngine.Core; +using Microsoft.Extensions.Configuration; +#pragma warning disable CS8601 + +#pragma warning disable CS8618 + +namespace TEngine.Core; + +public sealed class Exporter +{ + public void Start() + { + Console.OutputEncoding = Encoding.UTF8; + var exportType = Define.Options.ExportType; + + if (exportType != ExportType.None) + { + return; + } + + LogInfo("请输入你想è¦åšçš„æ“ä½œ:"); + LogInfo("1:导出网络å议(ProtoBuf)"); + LogInfo("2:增é‡å¯¼å‡ºExcel(包å«å¸¸é‡æžšä¸¾ï¼‰"); + LogInfo("3:å…¨é‡å¯¼å‡ºExcel(包å«å¸¸é‡æžšä¸¾ï¼‰"); + + var keyChar = Console.ReadKey().KeyChar; + + if (!int.TryParse(keyChar.ToString(), out var key) || key is < 1 or >= (int) ExportType.Max) + { + Console.WriteLine(""); + LogInfo("无法识别的导出类型请,输入正确的æ“作类型。"); + return; + } + + LogInfo(""); + exportType = (ExportType) key; + LoadConfig(); + + switch (exportType) + { + case ExportType.ProtoBuf: + { + _ = new ProtoBufExporter(); + break; + } + case ExportType.AllExcel: + case ExportType.AllExcelIncrement: + { + _ = new ExcelExporter(exportType); + break; + } + } + + LogInfo("æ“作完æˆ,按任æ„键关闭程åº"); + Console.ReadKey(); + Environment.Exit(0); + } + + private void LoadConfig() + { + const string settingsName = "TEngineSettings.json"; + var currentDirectory = Directory.GetCurrentDirectory(); + + if (!File.Exists(Path.Combine(currentDirectory, settingsName))) + { + throw new FileNotFoundException($"not found {settingsName} in OutputDirectory"); + } + + var configurationRoot = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(settingsName) + .Build(); + // ProtoBuf文件所在的ä½ç½®æ–‡ä»¶å¤¹ä½ç½® + ProtoBufDefine.ProtoBufDirectory = configurationRoot["Export:ProtoBufDirectory:Value"]; + // ProtoBuf生æˆåˆ°æœåŠ¡ç«¯çš„æ–‡ä»¶å¤¹ä½ç½® + ProtoBufDefine.ServerDirectory = configurationRoot["Export:ProtoBufServerDirectory:Value"]; + // ProtoBuf生æˆåˆ°å®¢æˆ·ç«¯çš„æ–‡ä»¶å¤¹ä½ç½® + ProtoBufDefine.ClientDirectory = configurationRoot["Export:ProtoBufClientDirectory:Value"]; + // ProtoBuf生æˆä»£ç æ¨¡æ¿çš„ä½ç½® + ProtoBufDefine.ProtoBufTemplatePath = configurationRoot["Export:ProtoBufTemplatePath:Value"]; + // Excelé…置文件根目录 + ExcelDefine.ProgramPath = configurationRoot["Export:ExcelProgramPath:Value"]; + // Excel版本文件的ä½ç½® + ExcelDefine.ExcelVersionFile = configurationRoot["Export:ExcelVersionFile:Value"]; + // Excelç”ŸæˆæœåС噍代ç çš„æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ServerFileDirectory = configurationRoot["Export:ExcelServerFileDirectory:Value"]; + // Excel生æˆå®¢æˆ·ç«¯ä»£ç æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ClientFileDirectory = configurationRoot["Export:ExcelClientFileDirectory:Value"]; + // Excelç”ŸæˆæœåŠ¡å™¨äºŒè¿›åˆ¶æ•°æ®æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ServerBinaryDirectory = configurationRoot["Export:ExcelServerBinaryDirectory:Value"]; + // Excel生æˆå®¢æˆ·ç«¯äºŒè¿›åˆ¶æ•°æ®æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ClientBinaryDirectory = configurationRoot["Export:ExcelClientBinaryDirectory:Value"]; + // Excelç”ŸæˆæœåС噍Jsonæ•°æ®æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ServerJsonDirectory = configurationRoot["Export:ExcelServerJsonDirectory:Value"]; + // Excel生æˆå®¢æˆ·ç«¯Jsonæ•°æ®æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ClientJsonDirectory = configurationRoot["Export:ExcelClientJsonDirectory:Value"]; + // Excel生æˆä»£ç æ¨¡æ¿çš„ä½ç½® + ExcelDefine.ExcelTemplatePath = configurationRoot["Export:ExcelTemplatePath:Value"]; + // æœåŠ¡å™¨è‡ªå®šä¹‰å¯¼å‡ºä»£ç æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ServerCustomExportDirectory = configurationRoot["Export:ServerCustomExportDirectory:Value"]; + // å®¢æˆ·ç«¯è‡ªå®šä¹‰å¯¼å‡ºä»£ç æ–‡ä»¶å¤¹ä½ç½® + ExcelDefine.ClientCustomExportDirectory = configurationRoot["Export:ClientCustomExportDirectory:Value"]; + } + + public static void LogInfo(string msg) + { + Console.WriteLine(msg); + } + + public static void LogError(string msg) + { + ConsoleColor color = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"{msg}\n{new StackTrace(1, true)}"); + Console.ForegroundColor = color; + } + + public static void LogError(Exception e) + { + ConsoleColor color = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e.Data.Contains("StackTrace") ? $"{e.Data["StackTrace"]}\n{e}" : e.ToString()); + Console.ForegroundColor = color; + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs.meta new file mode 100644 index 00000000..00a8a5d3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Exporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b2afd6fb4280f446aff438db7861e64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Interface.meta b/Assets/GameScripts/DotNet/Core/Exporter/Interface.meta new file mode 100644 index 00000000..719849d5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3fa83b6f387771f48be26dc88c05e808 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs b/Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs new file mode 100644 index 00000000..bf807ea2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs @@ -0,0 +1,10 @@ +namespace TEngine.Core +{ + /// + /// 表示是一个é…置文件 + /// + public interface IConfigTable + { + + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs.meta new file mode 100644 index 00000000..bf9c3b1c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Interface/IConfigTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0c846012d7e36e4fa17c615f15da685 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs b/Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs new file mode 100644 index 00000000..0b171cf2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs @@ -0,0 +1,44 @@ +#if TENGINE_NET +namespace TEngine.Core; + +public interface ICustomExport +{ + void Run(); +} +public abstract class ACustomExport : ICustomExport +{ + protected enum CustomExportType + { + Client,Server + } + + public abstract void Run(); + + protected void Write(string fileName, string fileContent, CustomExportType customExportType) + { + switch (customExportType) + { + case CustomExportType.Client: + { + if (!Directory.Exists(ExcelDefine.ClientCustomExportDirectory)) + { + Directory.CreateDirectory(ExcelDefine.ClientCustomExportDirectory); + } + + File.WriteAllText($"{ExcelDefine.ClientCustomExportDirectory}/{fileName}", fileContent); + return; + } + case CustomExportType.Server: + { + if (!Directory.Exists(ExcelDefine.ServerCustomExportDirectory)) + { + Directory.CreateDirectory(ExcelDefine.ServerCustomExportDirectory); + } + + File.WriteAllText($"{ExcelDefine.ServerCustomExportDirectory}/{fileName}", fileContent); + return; + } + } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs.meta new file mode 100644 index 00000000..0fec1e0a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/Interface/ICustomExport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d55723aa5c671d544a461eb8fb524769 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf.meta b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf.meta new file mode 100644 index 00000000..854f2c6a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c6c545584f5eb24892e7de1da9daffd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs new file mode 100644 index 00000000..20a7a31b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs @@ -0,0 +1,28 @@ +#if TENGINE_NET +namespace TEngine.Core; + +public static class ProtoBufDefine +{ + public static readonly char[] SplitChars = {' ', '\t'}; + /// + /// 项目跟目录路径 + /// + private const string ProjectPath = "../../.."; + /// + /// ProtoBuf文件夹 + /// + public static string ProtoBufDirectory = $"{ProjectPath}/Config/ProtoBuf/"; + /// + /// æœåŠ¡ç«¯ç”Ÿæˆæ–‡ä»¶å¤¹ + /// + public static string ServerDirectory = $"{ProjectPath}/Server/TEngine.Model/Generate/NetworkProtocol/"; + /// + /// å®¢æˆ·ç«¯ç”Ÿæˆæ–‡ä»¶å¤¹ + /// + public static string ClientDirectory = $"{ProjectPath}/Client/Unity/Assets/Scripts/TEngine/TEngine.Model/Generate/NetworkProtocol/"; + /// + /// ä»£ç æ¨¡æ¿è·¯å¾„ + /// + public static string ProtoBufTemplatePath = $"{ProjectPath}/Config/Template/ProtoTemplate.txt"; +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs.meta new file mode 100644 index 00000000..19f76f5e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufDefine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d418e7f4b6646504c8a6ca4b381992b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs new file mode 100644 index 00000000..60ce17f4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs @@ -0,0 +1,505 @@ +#if TENGINE_NET +using System.Text; +using TEngine.Core.Network; +#pragma warning disable CS8604 +#pragma warning disable CS8602 +#pragma warning disable CS8600 +#pragma warning disable CS8618 + +namespace TEngine.Core; + +public enum ProtoBufOpCodeType +{ + None = 0, + Outer = 1, + Inner = 2, + InnerBson = 3, +} + +public sealed class OpcodeInfo +{ + public uint Code; + public string Name; +} + +public sealed class ProtoBufExporter +{ + private uint _aMessage; + private uint _aRequest; + private uint _aResponse; + private uint _aRouteMessage; + private uint _aRouteRequest; + private uint _aRouteResponse; + private string _serverTemplate; + private string _clientTemplate; + + private readonly List _opcodes = new(); + + public ProtoBufExporter() + { + Console.OutputEncoding = Encoding.UTF8; + + if (!Directory.Exists(ProtoBufDefine.ServerDirectory)) + { + Directory.CreateDirectory(ProtoBufDefine.ServerDirectory); + } + + if (!Directory.Exists(ProtoBufDefine.ClientDirectory)) + { + Directory.CreateDirectory(ProtoBufDefine.ClientDirectory); + } + + var tasks = new Task[2]; + tasks[0] = Task.Run(RouteType); + tasks[1] = Task.Run(async () => + { + LoadTemplate(); + await Start(ProtoBufOpCodeType.Outer); + await Start(ProtoBufOpCodeType.Inner); + await Start(ProtoBufOpCodeType.InnerBson); + }); + Task.WaitAll(tasks); + } + + private async Task Start(ProtoBufOpCodeType opCodeType) + { + var protoFile = ""; + var opCodeName = ""; + var parameter = ""; + var className = ""; + var isMsgHead = false; + OpcodeInfo opcodeInfo = null; + string responseTypeStr = null; + string customRouteType = null; + _opcodes.Clear(); + var file = new StringBuilder(); + var saveDirectory = new Dictionary(); + + switch (opCodeType) + { + case ProtoBufOpCodeType.Outer: + { + _aMessage = Opcode.OuterMessage; + _aRequest = Opcode.OuterRequest; + _aResponse = Opcode.OuterResponse; + _aRouteMessage = Opcode.OuterRouteMessage; + _aRouteRequest = Opcode.OuterRouteRequest; + _aRouteResponse = Opcode.OuterRouteResponse; + opCodeName = "OuterOpcode"; + protoFile = $"{ProtoBufDefine.ProtoBufDirectory}OuterMessage.proto"; + saveDirectory.Add(ProtoBufDefine.ServerDirectory, _serverTemplate); + saveDirectory.Add(ProtoBufDefine.ClientDirectory, _clientTemplate); + break; + } + case ProtoBufOpCodeType.Inner: + { + // 预留1000个åè®®å·ç»™æ¡†æž¶å†…部å议用 + _aMessage = Opcode.InnerMessage + 1000; + _aRequest = Opcode.InnerRequest + 1000; + _aResponse = Opcode.InnerResponse + 1000; + _aRouteMessage = Opcode.InnerRouteMessage + 1000; + _aRouteRequest = Opcode.InnerRouteRequest + 1000; + _aRouteResponse = Opcode.InnerRouteResponse + 1000; + opCodeName = "InnerOpcode"; + protoFile = $"{ProtoBufDefine.ProtoBufDirectory}InnerMessage.proto"; + saveDirectory.Add(ProtoBufDefine.ServerDirectory, _serverTemplate); + break; + } + case ProtoBufOpCodeType.InnerBson: + { + // 预留1000个åè®®å·ç»™æ¡†æž¶å†…部å议用 + _aMessage = Opcode.InnerBsonMessage + 1000; + _aRequest = Opcode.InnerBsonRequest + 1000; + _aResponse = Opcode.InnerBsonResponse + 1000; + _aRouteMessage = Opcode.InnerBsonRouteMessage + 1000; + _aRouteRequest = Opcode.InnerBsonRouteRequest + 1000; + _aRouteResponse = Opcode.InnerBsonRouteResponse + 1000; + opCodeName = "InnerBsonOpcode"; + protoFile = $"{ProtoBufDefine.ProtoBufDirectory}InnerBsonMessage.proto"; + saveDirectory.Add(ProtoBufDefine.ServerDirectory, _serverTemplate); + break; + } + } + + var protoFileText = await File.ReadAllTextAsync(protoFile); + + foreach (var line in protoFileText.Split('\n')) + { + var currentLine = line.Trim(); + + if (string.IsNullOrWhiteSpace(currentLine)) + { + continue; + } + + if (currentLine.StartsWith("///")) + { + file.AppendFormat(" /// \r\n" + " /// {0}\r\n" + " /// \r\n", currentLine.TrimStart(new[] {'/', '/', '/'})); + continue; + } + + if (currentLine.StartsWith("message")) + { + isMsgHead = true; + opcodeInfo = new OpcodeInfo(); + file.AppendLine("\t[ProtoContract]"); + className = currentLine.Split(ProtoBufDefine.SplitChars, StringSplitOptions.RemoveEmptyEntries)[1]; + var splits = currentLine.Split(new[] {"//"}, StringSplitOptions.RemoveEmptyEntries); + + if (splits.Length > 1) + { + var parameterArray = currentLine.Split(new[] {"//"}, StringSplitOptions.RemoveEmptyEntries)[1].Trim().Split(','); + parameter = parameterArray[0].Trim(); + + switch (parameterArray.Length) + { + case 2: + responseTypeStr = parameterArray[1].Trim(); + break; + case 3: + { + customRouteType = parameterArray[1].Trim(); + + if (parameterArray.Length == 3) + { + responseTypeStr = parameterArray[2].Trim(); + } + + break; + } + } + } + else + { + parameter = ""; + } + + file.Append(string.IsNullOrWhiteSpace(parameter) + ? $"\tpublic partial class {className} : AProto" + : $"\tpublic partial class {className} : AProto, {parameter}"); + opcodeInfo.Name = className; + continue; + } + + if (!isMsgHead) + { + continue; + } + + switch (currentLine) + { + case "{": + { + file.AppendLine("\n\t{"); + + if (string.IsNullOrWhiteSpace(parameter) || parameter == "IMessage") + { + opcodeInfo.Code += ++_aMessage; + file.AppendLine($"\t\tpublic uint OpCode() {{ return {opCodeName}.{className}; }}"); + } + else + { + if (responseTypeStr != null) + { + file.AppendLine("\t\t[ProtoIgnore]"); + file.AppendLine($"\t\tpublic {responseTypeStr} ResponseType {{ get; set; }}"); + responseTypeStr = null; + } + else + { + if (parameter.Contains("RouteRequest")) + { + Exporter.LogError($"{opcodeInfo.Name} 没指定ResponseType"); + } + } + + file.AppendLine($"\t\tpublic uint OpCode() {{ return {opCodeName}.{className}; }}"); + + if (customRouteType != null) + { + file.AppendLine($"\t\tpublic long RouteTypeOpCode() {{ return (long)RouteType.{customRouteType}; }}"); + customRouteType = null; + } + else if (parameter is "IAddressableRouteRequest" or "IAddressableRouteMessage") + { + file.AppendLine($"\t\tpublic long RouteTypeOpCode() {{ return CoreRouteType.Addressable; }}"); + } + else if (parameter.EndsWith("BsonRouteMessage") || parameter.EndsWith("BsonRouteRequest")) + { + file.AppendLine($"\t\tpublic long RouteTypeOpCode() {{ return CoreRouteType.BsonRoute; }}"); + } + else if (parameter is "IRouteMessage" or "IRouteRequest") + { + file.AppendLine($"\t\tpublic long RouteTypeOpCode() {{ return CoreRouteType.Route; }}"); + } + + switch (parameter) + { + case "IRequest": + case "IBsonRequest": + { + opcodeInfo.Code += ++_aRequest; + break; + } + case "IResponse": + case "IBsonResponse": + { + opcodeInfo.Code += ++_aResponse; + file.AppendLine("\t\t[ProtoMember(91, IsRequired = true)]"); + file.AppendLine("\t\tpublic int ErrorCode { get; set; }"); + break; + } + default: + { + if (parameter.EndsWith("RouteMessage") || parameter == "IRouteMessage") + { + opcodeInfo.Code += ++_aRouteMessage; + } + else if (parameter.EndsWith("RouteRequest") || parameter == "IRouteRequest") + { + opcodeInfo.Code += ++_aRouteRequest; + } + else if (parameter.EndsWith("RouteResponse") || parameter == "IRouteResponse") + { + opcodeInfo.Code += ++_aRouteResponse; + file.AppendLine("\t\t[ProtoMember(91, IsRequired = true)]"); + file.AppendLine("\t\tpublic int ErrorCode { get; set; }"); + } + + break; + } + } + } + + _opcodes.Add(opcodeInfo); + continue; + } + case "}": + { + isMsgHead = false; + file.AppendLine("\t}"); + continue; + } + case "": + { + continue; + } + } + + if (currentLine.StartsWith("//")) + { + file.AppendFormat("\t\t///\r\n" + "\t\t/// {0}\r\n" + "\t\t///\r\n", currentLine.TrimStart('/', '/')); + continue; + } + + if (currentLine.StartsWith("repeated")) + { + Repeated(file, currentLine); + } + else + { + Members(file, currentLine); + } + } + + var csName = $"{Path.GetFileNameWithoutExtension(protoFile)}.cs"; + + foreach (var (directory, template) in saveDirectory) + { + var csFile = Path.Combine(directory, csName); + var content = template.Replace("(Content)", file.ToString()); + await File.WriteAllTextAsync(csFile, content); + } + + file.Clear(); + + file.AppendLine("namespace TEngine"); + file.AppendLine("{"); + file.AppendLine($"\tpublic static partial class {opCodeName}"); + file.AppendLine("\t{"); + + foreach (var opcode in _opcodes) + { + file.AppendLine($"\t\t public const int {opcode.Name} = {opcode.Code};"); + } + + _opcodes.Clear(); + + file.AppendLine("\t}"); + file.AppendLine("}"); + + foreach (var (directory, _) in saveDirectory) + { + var csFile = Path.Combine(directory, $"{opCodeName}.cs"); + await File.WriteAllTextAsync(csFile, file.ToString()); + } + } + + private async Task RouteType() + { + var routeTypeFile = $"{ProtoBufDefine.ProtoBufDirectory}RouteType.Config"; + var protoFileText = await File.ReadAllTextAsync(routeTypeFile); + var routeTypeFileSb = new StringBuilder(); + routeTypeFileSb.AppendLine("namespace TEngine.Core.Network\n{"); + routeTypeFileSb.AppendLine("\t// Routeå议定义(需è¦å®šä¹‰1000以上ã€å› ä¸º1000以内的框架预留)\t"); + routeTypeFileSb.AppendLine("\tpublic enum RouteType : long\n\t{"); + + foreach (var line in protoFileText.Split('\n')) + { + var currentLine = line.Trim(); + + if (currentLine.StartsWith("//")) + { + continue; + } + + var splits = currentLine.Split(new[] {"//"}, StringSplitOptions.RemoveEmptyEntries); + var routeTypeStr = splits[0].Split("=", StringSplitOptions.RemoveEmptyEntries); + routeTypeFileSb.Append($"\t\t{routeTypeStr[0].Trim()} = {routeTypeStr[1].Trim()},"); + + if (splits.Length > 1) + { + routeTypeFileSb.Append($" // {splits[1].Trim()}\n"); + } + else + { + routeTypeFileSb.Append('\n'); + } + } + + routeTypeFileSb.AppendLine("\t}\n}"); + var file = routeTypeFileSb.ToString(); + await File.WriteAllTextAsync($"{ProtoBufDefine.ServerDirectory}RouteType.cs", file); + await File.WriteAllTextAsync($"{ProtoBufDefine.ClientDirectory}RouteType.cs", file); + } + + private void Repeated(StringBuilder file, string newline) + { + try + { + var index = newline.IndexOf(";", StringComparison.Ordinal); + newline = newline.Remove(index); + var property = newline.Split(ProtoBufDefine.SplitChars, StringSplitOptions.RemoveEmptyEntries); + var type = property[1]; + var name = property[2]; + var memberIndex = int.Parse(property[4]); + type = ConvertType(type); + + file.AppendLine($"\t\t[ProtoMember({memberIndex})]"); + file.AppendLine($"\t\tpublic List<{type}> {name} = new List<{type}>();"); + } + catch (Exception e) + { + Exporter.LogError($"{newline}\n {e}"); + } + } + + private void Members(StringBuilder file, string currentLine) + { + try + { + var index = currentLine.IndexOf(";", StringComparison.Ordinal); + currentLine = currentLine.Remove(index); + var property = currentLine.Split(ProtoBufDefine.SplitChars, StringSplitOptions.RemoveEmptyEntries); + var type = property[0]; + var name = property[1]; + var memberIndex = int.Parse(property[3]); + var typeCs = ConvertType(type); + string defaultValue = GetDefault(typeCs); + + file.AppendLine($"\t\t[ProtoMember({memberIndex})]"); + file.AppendLine($"\t\tpublic {typeCs} {name} {{ get; set; }}"); + } + catch (Exception e) + { + Exporter.LogError($"{currentLine}\n {e}"); + } + } + + private string ConvertType(string type) + { + return type switch + { + "int[]" => "int[] { }", + "int32[]" => "int[] { }", + "int64[]" => "long[] { }", + "int32" => "int", + "int64" => "long", + _ => type + }; + } + + private string GetDefault(string type) + { + type = type.Trim(); + + switch (type) + { + case "byte": + case "short": + case "int": + case "long": + case "float": + case "double": + return "0"; + case "bool": + return "false"; + default: + return "null"; + } + } + + private void LoadTemplate() + { + string[] lines = File.ReadAllLines(ProtoBufDefine.ProtoBufTemplatePath, Encoding.UTF8); + + StringBuilder serverSb = new StringBuilder(); + StringBuilder clientSb = new StringBuilder(); + + int flag = 0; + foreach (string line in lines) + { + string trim = line.Trim(); + + if (trim.StartsWith("#if") && trim.Contains("SERVER")) + { + flag = 1; + continue; + } + else if(trim.StartsWith("#else")) + { + flag = 2; + continue; + } + else if(trim.StartsWith($"#endif")) + { + flag = 0; + continue; + } + + switch (flag) + { + case 1: // æœåŠ¡ç«¯ + { + serverSb.AppendLine(line); + break; + } + case 2: // 客户端 + { + clientSb.AppendLine(line); + break; + } + default: // åŒç«¯ + { + serverSb.AppendLine(line); + clientSb.AppendLine(line); + break; + } + } + } + + _serverTemplate = serverSb.ToString(); + _clientTemplate = clientSb.ToString(); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs.meta b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs.meta new file mode 100644 index 00000000..d36eb770 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Exporter/ProtoBuf/ProtoBufExporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2876caa2819095b4e94fa1ca697e3fba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper.meta b/Assets/GameScripts/DotNet/Core/Helper.meta new file mode 100644 index 00000000..c8cdf2d9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af4c2e7b63f02432a9d2658299efa111 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs new file mode 100644 index 00000000..31f002e2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs @@ -0,0 +1,432 @@ +#if TENGINE_UNITY +using System; +using System.IO; +using System.Text; +using UnityEngine; + +namespace TEngine.Core +{ + public static class AudioHelper + { + // Force save as 16-bit .wav + const int BlockSize_16Bit = 2; + + /// + /// Load PCM format *.wav audio file (using Unity's Application data path) and convert to AudioClip. + /// + /// The AudioClip. + /// Local file path to .wav file + public static AudioClip ToAudioClip(string filePath) + { + if (!filePath.StartsWith(UnityEngine.Application.persistentDataPath) && + !filePath.StartsWith(UnityEngine.Application.dataPath)) + { + Debug.LogWarning( + "This only supports files that are stored using Unity's Application data path. \nTo load bundled resources use 'Resources.Load(\"filename\") typeof(AudioClip)' method. \nhttps://docs.unity3d.com/ScriptReference/Resources.Load.html"); + return null; + } + + var fileBytes = File.ReadAllBytes(filePath); + return ToAudioClip(fileBytes, 0); + } + + public static AudioClip ToAudioClip(byte[] fileBytes, int offsetSamples = 0, string name = "wav") + { + //string riff = Encoding.ASCII.GetString (fileBytes, 0, 4); + //string wave = Encoding.ASCII.GetString (fileBytes, 8, 4); + int subchunk1 = BitConverter.ToInt32(fileBytes, 16); + UInt16 audioFormat = BitConverter.ToUInt16(fileBytes, 20); + + // NB: Only uncompressed PCM wav files are supported. + string formatCode = FormatCode(audioFormat); + Debug.AssertFormat(audioFormat == 1 || audioFormat == 65534, + "Detected format code '{0}' {1}, but only PCM and WaveFormatExtensable uncompressed formats are currently supported.", + audioFormat, formatCode); + + UInt16 channels = BitConverter.ToUInt16(fileBytes, 22); + int sampleRate = BitConverter.ToInt32(fileBytes, 24); + //int byteRate = BitConverter.ToInt32 (fileBytes, 28); + //UInt16 blockAlign = BitConverter.ToUInt16 (fileBytes, 32); + UInt16 bitDepth = BitConverter.ToUInt16(fileBytes, 34); + + int headerOffset = 16 + 4 + subchunk1 + 4; + int subchunk2 = BitConverter.ToInt32(fileBytes, headerOffset); + //Debug.LogFormat ("riff={0} wave={1} subchunk1={2} format={3} channels={4} sampleRate={5} byteRate={6} blockAlign={7} bitDepth={8} headerOffset={9} subchunk2={10} filesize={11}", riff, wave, subchunk1, formatCode, channels, sampleRate, byteRate, blockAlign, bitDepth, headerOffset, subchunk2, fileBytes.Length); + + float[] data; + switch (bitDepth) + { + case 8: + data = Convert8BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2); + break; + case 16: + data = Convert16BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2); + break; + case 24: + data = Convert24BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2); + break; + case 32: + data = Convert32BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2); + break; + default: + throw new Exception(bitDepth + " bit depth is not supported."); + } + + AudioClip audioClip = AudioClip.Create(name, data.Length, (int)channels, sampleRate, false); + audioClip.SetData(data, 0); + return audioClip; + } + + #region wav file bytes to Unity AudioClip conversion methods + + private static float[] Convert8BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) + { + int wavSize = BitConverter.ToInt32(source, headerOffset); + headerOffset += sizeof(int); + Debug.AssertFormat(wavSize > 0 && wavSize == dataSize, + "Failed to get valid 8-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, + headerOffset); + + float[] data = new float[wavSize]; + + sbyte maxValue = sbyte.MaxValue; + + int i = 0; + while (i < wavSize) + { + data[i] = (float)source[i] / maxValue; + ++i; + } + + return data; + } + + private static float[] Convert16BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) + { + int wavSize = BitConverter.ToInt32(source, headerOffset); + headerOffset += sizeof(int); + Debug.AssertFormat(wavSize > 0 && wavSize == dataSize, + "Failed to get valid 16-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, + headerOffset); + + int x = sizeof(Int16); // block size = 2 + int convertedSize = wavSize / x; + + float[] data = new float[convertedSize]; + + Int16 maxValue = Int16.MaxValue; + + int offset = 0; + int i = 0; + while (i < convertedSize) + { + offset = i * x + headerOffset; + data[i] = (float)BitConverter.ToInt16(source, offset) / maxValue; + ++i; + } + + Debug.AssertFormat(data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", + data.Length, convertedSize); + + return data; + } + + private static float[] Convert24BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) + { + int wavSize = BitConverter.ToInt32(source, headerOffset); + headerOffset += sizeof(int); + Debug.AssertFormat(wavSize > 0 && wavSize == dataSize, + "Failed to get valid 24-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, + headerOffset); + + int x = 3; // block size = 3 + int convertedSize = wavSize / x; + + int maxValue = Int32.MaxValue; + + float[] data = new float[convertedSize]; + + byte[] + block = new byte[sizeof(int)]; // using a 4 byte block for copying 3 bytes, then copy bytes with 1 offset + + int offset = 0; + int i = 0; + while (i < convertedSize) + { + offset = i * x + headerOffset; + Buffer.BlockCopy(source, offset, block, 1, x); + data[i] = (float)BitConverter.ToInt32(block, 0) / maxValue; + ++i; + } + + Debug.AssertFormat(data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", + data.Length, convertedSize); + + return data; + } + + private static float[] Convert32BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) + { + int wavSize = BitConverter.ToInt32(source, headerOffset); + headerOffset += sizeof(int); + Debug.AssertFormat(wavSize > 0 && wavSize == dataSize, + "Failed to get valid 32-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, + headerOffset); + + int x = sizeof(float); // block size = 4 + int convertedSize = wavSize / x; + + Int32 maxValue = Int32.MaxValue; + + float[] data = new float[convertedSize]; + + int offset = 0; + int i = 0; + while (i < convertedSize) + { + offset = i * x + headerOffset; + data[i] = (float)BitConverter.ToInt32(source, offset) / maxValue; + ++i; + } + + Debug.AssertFormat(data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", + data.Length, convertedSize); + + return data; + } + + #endregion + + public static byte[] FromAudioClip(AudioClip audioClip) + { + string file; + return FromAudioClip(audioClip, out file, false); + } + + public static byte[] FromAudioClip(AudioClip audioClip, out string filepath, bool saveAsFile = true, + string dirname = "recordings") + { + MemoryStream stream = new MemoryStream(); + + const int headerSize = 44; + + // get bit depth + UInt16 bitDepth = 16; //BitDepth (audioClip); + + // NB: Only supports 16 bit + //Debug.AssertFormat (bitDepth == 16, "Only converting 16 bit is currently supported. The audio clip data is {0} bit.", bitDepth); + + // total file size = 44 bytes for header format and audioClip.samples * factor due to float to Int16 / sbyte conversion + int fileSize = audioClip.samples * BlockSize_16Bit + headerSize; // BlockSize (bitDepth) + + // chunk descriptor (riff) + WriteFileHeader(ref stream, fileSize); + // file header (fmt) + WriteFileFormat(ref stream, audioClip.channels, audioClip.frequency, bitDepth); + // data chunks (data) + WriteFileData(ref stream, audioClip, bitDepth); + + byte[] bytes = stream.ToArray(); + + // Validate total bytes + Debug.AssertFormat(bytes.Length == fileSize, "Unexpected AudioClip to wav format byte count: {0} == {1}", + bytes.Length, fileSize); + + // Save file to persistant storage location + if (saveAsFile) + { + filepath = string.Format("{0}/{1}/{2}.{3}", UnityEngine.Application.persistentDataPath, dirname, + DateTime.UtcNow.ToString("yyMMdd-HHmmss-fff"), "wav"); + Directory.CreateDirectory(Path.GetDirectoryName(filepath)); + File.WriteAllBytes(filepath, bytes); + //Debug.Log ("Auto-saved .wav file: " + filepath); + } + else + { + filepath = null; + } + + stream.Dispose(); + + return bytes; + } + + #region write .wav file functions + + private static int WriteFileHeader(ref MemoryStream stream, int fileSize) + { + int count = 0; + int total = 12; + + // riff chunk id + byte[] riff = Encoding.ASCII.GetBytes("RIFF"); + count += WriteBytesToMemoryStream(ref stream, riff, "ID"); + + // riff chunk size + int chunkSize = fileSize - 8; // total size - 8 for the other two fields in the header + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(chunkSize), "CHUNK_SIZE"); + + byte[] wave = Encoding.ASCII.GetBytes("WAVE"); + count += WriteBytesToMemoryStream(ref stream, wave, "FORMAT"); + + // Validate header + Debug.AssertFormat(count == total, "Unexpected wav descriptor byte count: {0} == {1}", count, total); + + return count; + } + + private static int WriteFileFormat(ref MemoryStream stream, int channels, int sampleRate, UInt16 bitDepth) + { + int count = 0; + int total = 24; + + byte[] id = Encoding.ASCII.GetBytes("fmt "); + count += WriteBytesToMemoryStream(ref stream, id, "FMT_ID"); + + int subchunk1Size = 16; // 24 - 8 + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(subchunk1Size), "SUBCHUNK_SIZE"); + + UInt16 audioFormat = 1; + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(audioFormat), "AUDIO_FORMAT"); + + UInt16 numChannels = Convert.ToUInt16(channels); + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(numChannels), "CHANNELS"); + + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(sampleRate), "SAMPLE_RATE"); + + int byteRate = sampleRate * channels * BytesPerSample(bitDepth); + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(byteRate), "BYTE_RATE"); + + UInt16 blockAlign = Convert.ToUInt16(channels * BytesPerSample(bitDepth)); + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(blockAlign), "BLOCK_ALIGN"); + + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(bitDepth), "BITS_PER_SAMPLE"); + + // Validate format + Debug.AssertFormat(count == total, "Unexpected wav fmt byte count: {0} == {1}", count, total); + + return count; + } + + private static int WriteFileData(ref MemoryStream stream, AudioClip audioClip, UInt16 bitDepth) + { + int count = 0; + int total = 8; + + // Copy float[] data from AudioClip + float[] data = new float[audioClip.samples * audioClip.channels]; + audioClip.GetData(data, 0); + + byte[] bytes = ConvertAudioClipDataToInt16ByteArray(data); + + byte[] id = Encoding.ASCII.GetBytes("data"); + count += WriteBytesToMemoryStream(ref stream, id, "DATA_ID"); + + int subchunk2Size = Convert.ToInt32(audioClip.samples * BlockSize_16Bit); // BlockSize (bitDepth) + count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(subchunk2Size), "SAMPLES"); + + // Validate header + Debug.AssertFormat(count == total, "Unexpected wav data id byte count: {0} == {1}", count, total); + + // Write bytes to stream + count += WriteBytesToMemoryStream(ref stream, bytes, "DATA"); + + // Validate audio data + Debug.AssertFormat(bytes.Length == subchunk2Size, "Unexpected AudioClip to wav subchunk2 size: {0} == {1}", + bytes.Length, subchunk2Size); + + return count; + } + + private static byte[] ConvertAudioClipDataToInt16ByteArray(float[] data) + { + MemoryStream dataStream = new MemoryStream(); + + int x = sizeof(Int16); + + Int16 maxValue = Int16.MaxValue; + + int i = 0; + while (i < data.Length) + { + dataStream.Write(BitConverter.GetBytes(Convert.ToInt16(data[i] * maxValue)), 0, x); + ++i; + } + + byte[] bytes = dataStream.ToArray(); + + // Validate converted bytes + Debug.AssertFormat(data.Length * x == bytes.Length, + "Unexpected float[] to Int16 to byte[] size: {0} == {1}", data.Length * x, bytes.Length); + + dataStream.Dispose(); + + return bytes; + } + + private static int WriteBytesToMemoryStream(ref MemoryStream stream, byte[] bytes, string tag = "") + { + int count = bytes.Length; + stream.Write(bytes, 0, count); + //Debug.LogFormat ("WAV:{0} wrote {1} bytes.", tag, count); + return count; + } + + #endregion + + /// + /// Calculates the bit depth of an AudioClip + /// + /// The bit depth. Should be 8 or 16 or 32 bit. + /// Audio clip. + public static UInt16 BitDepth(AudioClip audioClip) + { + UInt16 bitDepth = + Convert.ToUInt16(audioClip.samples * audioClip.channels * audioClip.length / audioClip.frequency); + Debug.AssertFormat(bitDepth == 8 || bitDepth == 16 || bitDepth == 32, + "Unexpected AudioClip bit depth: {0}. Expected 8 or 16 or 32 bit.", bitDepth); + return bitDepth; + } + + private static int BytesPerSample(UInt16 bitDepth) + { + return bitDepth / 8; + } + + private static int BlockSize(UInt16 bitDepth) + { + switch (bitDepth) + { + case 32: + return sizeof(Int32); // 32-bit -> 4 bytes (Int32) + case 16: + return sizeof(Int16); // 16-bit -> 2 bytes (Int16) + case 8: + return sizeof(sbyte); // 8-bit -> 1 byte (sbyte) + default: + throw new Exception(bitDepth + " bit depth is not supported."); + } + } + + private static string FormatCode(UInt16 code) + { + switch (code) + { + case 1: + return "PCM"; + case 2: + return "ADPCM"; + case 3: + return "IEEE"; + case 7: + return "μ-law"; + case 65534: + return "WaveFormatExtensable"; + default: + Debug.LogWarning("Unknown wav code format:" + code); + return ""; + } + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs.meta new file mode 100644 index 00000000..96c9c2ec --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/AudioHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84a1c6c8542d1814da8b5fba76c64e74 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs new file mode 100644 index 00000000..86bf7231 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; +using System.Text; + +namespace TEngine.Core +{ + public static class ByteHelper + { + private static readonly string[] Suffix = { "Byte", "KB", "MB", "GB", "TB" }; + + public static long ReadInt64(FileStream stream) + { + var buffer = new byte[8]; + stream.Read(buffer, 0, 8); + return BitConverter.ToInt64(buffer, 0); + } + + public static int ReadInt32(FileStream stream) + { + var buffer = new byte[4]; + stream.Read(buffer, 0, 4); + return BitConverter.ToInt32(buffer, 0); + } + + public static long ReadInt64(MemoryStream stream) + { + var buffer = new byte[8]; + stream.Read(buffer, 0, 8); + return BitConverter.ToInt64(buffer, 0); + } + + public static int ReadInt32(MemoryStream stream) + { + var buffer = new byte[4]; + stream.Read(buffer, 0, 4); + return BitConverter.ToInt32(buffer, 0); + } + + public static string ToHex(this byte b) + { + return b.ToString("X2"); + } + + public static string ToHex(this byte[] bytes) + { + var stringBuilder = new StringBuilder(); + foreach (var b in bytes) + { + stringBuilder.Append(b.ToString("X2")); + } + + return stringBuilder.ToString(); + } + + public static string ToHex(this byte[] bytes, string format) + { + var stringBuilder = new StringBuilder(); + foreach (var b in bytes) + { + stringBuilder.Append(b.ToString(format)); + } + + return stringBuilder.ToString(); + } + + public static string ToHex(this byte[] bytes, int offset, int count) + { + var stringBuilder = new StringBuilder(); + for (var i = offset; i < offset + count; ++i) + { + stringBuilder.Append(bytes[i].ToString("X2")); + } + + return stringBuilder.ToString(); + } + + public static string ToStr(this byte[] bytes) + { + return Encoding.Default.GetString(bytes); + } + + public static string ToStr(this byte[] bytes, int index, int count) + { + return Encoding.Default.GetString(bytes, index, count); + } + + public static string Utf8ToStr(this byte[] bytes) + { + return Encoding.UTF8.GetString(bytes); + } + + public static string Utf8ToStr(this byte[] bytes, int index, int count) + { + return Encoding.UTF8.GetString(bytes, index, count); + } + + public static void WriteTo(this byte[] bytes, int offset, uint num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + bytes[offset + 2] = (byte)((num & 0xff0000) >> 16); + bytes[offset + 3] = (byte)((num & 0xff000000) >> 24); + } + + public static void WriteTo(this byte[] bytes, int offset, int num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + bytes[offset + 2] = (byte)((num & 0xff0000) >> 16); + bytes[offset + 3] = (byte)((num & 0xff000000) >> 24); + } + + public static void WriteTo(this byte[] bytes, int offset, byte num) + { + bytes[offset] = num; + } + + public static void WriteTo(this byte[] bytes, int offset, short num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + } + + public static void WriteTo(this byte[] bytes, int offset, ushort num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + } + + public static string ToReadableSpeed(this long byteCount) + { + var i = 0; + double dblSByte = byteCount; + if (byteCount <= 1024) + { + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + for (i = 0; byteCount / 1024 > 0; i++, byteCount /= 1024) + { + dblSByte = byteCount / 1024.0; + } + + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + public static string ToReadableSpeed(this ulong byteCount) + { + var i = 0; + double dblSByte = byteCount; + + if (byteCount <= 1024) + { + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + for (i = 0; byteCount / 1024 > 0; i++, byteCount /= 1024) + { + dblSByte = byteCount / 1024.0; + } + + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + /// + /// åˆå¹¶ä¸€ä¸ªæ•°ç»„ + /// + /// + /// + /// + public static byte[] MergeBytes(byte[] bytes, byte[] otherBytes) + { + var result = new byte[bytes.Length + otherBytes.Length]; + bytes.CopyTo(result, 0); + otherBytes.CopyTo(result, bytes.Length); + return result; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs.meta new file mode 100644 index 00000000..3df7e77f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/ByteHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33f44962744fcd64d98b9bcd0fc41bf9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs new file mode 100644 index 00000000..9f634e90 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace TEngine.Core +{ + public static class CryptHelper + { + // 对应的字符串SYUwQN360OPY1gaL + // åœ¨è¿™ä¸ªç½‘ç«™éšæœºç”Ÿæˆçš„ http://tool.c7sky.com/password/ + private static readonly byte[] Key = + { + 0x53, 0x59, 0x55, 0x77, 0x51, 0x4e, 0x33, 0x36, + 0x30, 0x4f, 0x50, 0x59, 0x31, 0x67, 0x61, 0x4c + }; + + /// + /// 创建一个新的KEY + /// + /// 一个长度为16的字符串ã€å¦‚果超过åªä¼šæˆªå–å‰16ä½ + /// 返回的是一个åå…­è¿›åˆ¶çš„å­—ç¬¦ä¸²ã€æ¯ä¸ªç”¨,åˆ†å‰²çš„ã€æ¯ä¸ªéƒ½æ˜¯ä¸€ä¸ªbyte + public static string CreateKey(string keyStr) + { + if (keyStr.Length > 16) + { + keyStr = keyStr.Substring(0, 15); + } + + var bytes = Encoding.UTF8.GetBytes(keyStr); + return $"0x{BitConverter.ToString(bytes, 0).Replace("-", ", 0x").ToLower()}"; + } + + /// + /// AES 加密 + /// + /// + /// + public static byte[] AesEncrypt(byte[] toEncryptArray) + { + var rm = Aes.Create(); + rm.Key = Key; + rm.Mode = CipherMode.ECB; + rm.Padding = PaddingMode.PKCS7; + var cTransform = rm.CreateEncryptor(); + return cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); + } + + /// + /// AES 解密 + /// + /// + /// + public static MemoryStream AesDecryptReturnStream(byte[] toEncryptArray) + { + var bytes = AesDecrypt(toEncryptArray); + + return new MemoryStream(bytes); + } + + /// + /// AES 解密 + /// + /// + /// + public static byte[] AesDecrypt(byte[] toEncryptArray) + { + var rm = Aes.Create(); + rm.Key = Key; + rm.Mode = CipherMode.ECB; + rm.Padding = PaddingMode.PKCS7; + + var cTransform = rm.CreateDecryptor(); + return cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs.meta new file mode 100644 index 00000000..33b3f96e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/CryptHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 060c741ce3ae4d54498be5f7a286801b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs new file mode 100644 index 00000000..5cf647a3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.IO; + +namespace TEngine.Core +{ + public static class FileHelper + { + /// + /// æ‹·è´æ–‡ä»¶åˆ°ç›®æ ‡è·¯å¾„ã€å¦‚果目标目录ä¸å­˜åœ¨ä¼šè‡ªåŠ¨åˆ›å»ºç›®å½• + /// + /// + /// + /// + public static void Copy(string sourceFile, string destinationFile, bool overwrite) + { + var directoriesByFilePath = GetDirectoriesByFilePath(destinationFile); + + foreach (var dir in directoriesByFilePath) + { + if (Directory.Exists(dir)) + { + continue; + } + + Directory.CreateDirectory(dir); + } + + File.Copy(sourceFile, destinationFile, overwrite); + } + + /// + /// èŽ·å–æ–‡ä»¶è·¯å¾„内的所有文件夹路径 + /// + /// + /// + public static List GetDirectoriesByFilePath(string filePath) + { + var dir = ""; + var directories = new List(); + var fileDirectories = filePath.Split('/'); + + for (var i = 0; i < fileDirectories.Length - 1; i++) + { + dir = $"{dir}{fileDirectories[i]}/"; + directories.Add(dir); + } + + return directories; + } + + /// + /// 把文件夹里所有内容拷è´çš„目标ä½ç½® + /// + /// + /// + /// + public static void CopyDirectory(string sourceDirectory, string destinationDirectory, bool overwrite) + { + // 创建目标文件夹 + + if (!Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + + // 获å–当剿–‡ä»¶å¤¹ä¸­çš„æ‰€æœ‰æ–‡ä»¶ + + var files = Directory.GetFiles(sourceDirectory); + + // æ‹·è´æ–‡ä»¶åˆ°ç›®æ ‡æ–‡ä»¶å¤¹ + + foreach (var file in files) + { + var fileName = Path.GetFileName(file); + var destinationPath = Path.Combine(destinationDirectory, fileName); + File.Copy(file, destinationPath, overwrite); + } + + // èŽ·å–æºæ–‡ä»¶å¤¹ä¸­çš„æ‰€æœ‰å­æ–‡ä»¶å¤¹ + + var directories = Directory.GetDirectories(sourceDirectory); + + // é€’å½’æ–¹å¼æ‹·è´æ–‡ä»¶å¤¹ + + foreach (var directory in directories) + { + var directoryName = Path.GetFileName(directory); + var destinationPath = Path.Combine(destinationDirectory, directoryName); + CopyDirectory(directory, destinationPath, overwrite); + } + } + + /// + /// 清除文件夹里的所有文件 + /// + /// + public static void ClearDirectoryFile(string folderPath) + { + if (!Directory.Exists(folderPath)) + { + return; + } + + var files = Directory.GetFiles(folderPath); + + foreach (var file in files) + { + File.Delete(file); + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs.meta new file mode 100644 index 00000000..3733eb75 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/FileHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74cc512af7309484fa066b16175c6331 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs new file mode 100644 index 00000000..5fce1f1b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; +#pragma warning disable CS8603 + +namespace TEngine.Core +{ + public static class JsonHelper + { + public static string ToJson(this T t) + { + return JsonConvert.SerializeObject(t); + } + + public static object Deserialize(this string json, Type type, bool reflection = true) + { + return JsonConvert.DeserializeObject(json, type); + } + + public static T Deserialize(this string json) + { + return JsonConvert.DeserializeObject(json); + } + + public static T Clone(T t) + { + return t.ToJson().Deserialize(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs.meta new file mode 100644 index 00000000..4a742e48 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/JsonHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44fd4e119f9d29342a2e312d58f6a1fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs b/Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs new file mode 100644 index 00000000..29058f25 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Security.Cryptography; + +namespace TEngine.Core +{ + public static class MD5Helper + { + public static string FileMD5(string filePath) + { + using var file = new FileStream(filePath, FileMode.Open); + return FileMD5(file); + } + + public static string FileMD5(FileStream fileStream) + { + var md5 = MD5.Create(); + return md5.ComputeHash(fileStream).ToHex("x2"); + } + + public static string BytesMD5(byte[] bytes) + { + var md5 = MD5.Create(); + bytes = md5.ComputeHash(bytes); + return bytes.ToHex("x2"); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs.meta new file mode 100644 index 00000000..f6f0b9c2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/MD5Helper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c37f13dde60869459e624143ae45a3c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs new file mode 100644 index 00000000..015be7c7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs @@ -0,0 +1,15 @@ +using System.IO; +using TEngine.IO; + +namespace TEngine.Core +{ + public static class MemoryStreamHelper + { + private static readonly RecyclableMemoryStreamManager Manager = new RecyclableMemoryStreamManager(); + + public static MemoryStream GetRecyclableMemoryStream() + { + return Manager.GetStream(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs.meta new file mode 100644 index 00000000..2f613e70 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/MemoryStreamHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90f0f0ce0808b61458dbb17cdc1f3766 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/Mongo.meta b/Assets/GameScripts/DotNet/Core/Helper/Mongo.meta new file mode 100644 index 00000000..ee3b3348 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Mongo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90685e6f420bcae4f851975873f430c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs new file mode 100644 index 00000000..2e36222a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs @@ -0,0 +1,118 @@ +#if TENGINE_NET +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Conventions; +using Unity.Mathematics; + +namespace TEngine.Core; + +public sealed class MongoHelper : Singleton +{ + private readonly HashSet _registerCount = new HashSet(3); + + static MongoHelper() + { + // 自动注册IgnoreExtraElements + var conventionPack = new ConventionPack {new IgnoreExtraElementsConvention(true)}; + ConventionRegistry.Register("IgnoreExtraElements", conventionPack, type => true); + BsonSerializer.TryRegisterSerializer(typeof(float2), new StructBsonSerialize()); + BsonSerializer.TryRegisterSerializer(typeof(float3), new StructBsonSerialize()); + BsonSerializer.TryRegisterSerializer(typeof(float4), new StructBsonSerialize()); + BsonSerializer.TryRegisterSerializer(typeof(quaternion), new StructBsonSerialize()); + } + + protected override void OnLoad(int assemblyName) + { + if (_registerCount.Count == 3) + { + return; + } + + _registerCount.Add(assemblyName); + + Task.Run(() => + { + foreach (var type in AssemblyManager.ForEach(assemblyName)) + { + if (type.IsInterface || type.IsAbstract || !typeof(Entity).IsAssignableFrom(type)) + { + continue; + } + + BsonClassMap.LookupClassMap(type); + } + }); + } + + public T Deserialize(byte[] bytes) + { + return BsonSerializer.Deserialize(bytes); + } + + public object Deserialize(byte[] bytes, Type type) + { + return BsonSerializer.Deserialize(bytes, type); + } + + public object Deserialize(byte[] bytes, string type) + { + return BsonSerializer.Deserialize(bytes, Type.GetType(type)); + } + + public T Deserialize(Stream stream) + { + return BsonSerializer.Deserialize(stream); + } + + public object Deserialize(Stream stream, Type type) + { + return BsonSerializer.Deserialize(stream, type); + } + + public object DeserializeFrom(Type type, MemoryStream stream) + { + return Deserialize(stream, type); + } + + public T DeserializeFrom(MemoryStream stream) + { + return Deserialize(stream); + } + + public T DeserializeFrom(byte[] bytes, int index, int count) + { + return BsonSerializer.Deserialize(bytes.AsMemory(index, count).ToArray()); + } + + public byte[] SerializeTo(T t) + { + return t.ToBson(); + } + + public void SerializeTo(T t, MemoryStream stream) + { + var bytes = t.ToBson(); + + stream.Write(bytes, 0, bytes.Length); + } + + public T Clone(T t) + { + return Deserialize(t.ToBson()); + } +} +#endif +#if TENGINE_UNITY +using System; +using System.IO; +namespace TEngine.Core +{ + public sealed class MongoHelper : Singleton + { + public object DeserializeFrom(Type type, MemoryStream stream) + { + return null; + } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs.meta new file mode 100644 index 00000000..b6662b10 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8305336cdf88b72498d9c3365424ed49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs b/Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs new file mode 100644 index 00000000..f1db420f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs @@ -0,0 +1,57 @@ +#if TENGINE_NET +using System.Reflection; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; + +namespace TEngine.Core; + +public class StructBsonSerialize : StructSerializerBase where TValue : struct +{ + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value) + { + var nominalType = args.NominalType; + + var bsonWriter = context.Writer; + + bsonWriter.WriteStartDocument(); + + var fields = nominalType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + foreach (var field in fields) + { + bsonWriter.WriteName(field.Name); + BsonSerializer.Serialize(bsonWriter, field.FieldType, field.GetValue(value)); + } + + bsonWriter.WriteEndDocument(); + } + + public override TValue Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + //boxing is required for SetValue to work + object obj = new TValue(); + var actualType = args.NominalType; + var bsonReader = context.Reader; + + bsonReader.ReadStartDocument(); + + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + var name = bsonReader.ReadName(Utf8NameDecoder.Instance); + + var field = actualType.GetField(name, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (field != null) + { + var value = BsonSerializer.Deserialize(bsonReader, field.FieldType); + field.SetValue(obj, value); + } + } + + bsonReader.ReadEndDocument(); + + return (TValue) obj; + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs.meta new file mode 100644 index 00000000..3939bf4e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Mongo/StructBsonSerialize.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ac9e1e3f21a01f40a9bda7f7cc4e667 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/Proto.meta b/Assets/GameScripts/DotNet/Core/Helper/Proto.meta new file mode 100644 index 00000000..18ae8854 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Proto.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9557f6157233b304fb1f1669311f5fc9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs b/Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs new file mode 100644 index 00000000..0940414d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs @@ -0,0 +1,11 @@ +using ProtoBuf; + +namespace TEngine +{ + [ProtoContract] + public abstract class AProto + { + public virtual void AfterDeserialization() => EndInit(); + protected virtual void EndInit() { } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs.meta new file mode 100644 index 00000000..9e9fc670 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Proto/AProto.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3172691aec101574282e3e66d998d4dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs new file mode 100644 index 00000000..0941302c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using ProtoBuf; +#pragma warning disable CS8604 + +namespace TEngine.Core +{ + public static class ProtoBufHelper + { + public static object FromBytes(Type type, byte[] bytes, int index, int count) + { + using var stream = new MemoryStream(bytes, index, count); + return Serializer.Deserialize(type, stream); + } + + public static T FromBytes(byte[] bytes) + { + using var stream = new MemoryStream(bytes, 0, bytes.Length); + return Serializer.Deserialize(stream); + // return FromBytes(bytes, 0, bytes.Length); + } + + public static T FromBytes(byte[] bytes, int index, int count) + { + using var stream = new MemoryStream(bytes, index, count); + return Serializer.Deserialize(stream); + } + + public static byte[] ToBytes(object message) + { + using var stream = new MemoryStream(); + Serializer.Serialize(stream, message); + return stream.ToArray(); + } + + public static void ToStream(object message, MemoryStream stream) + { + Serializer.Serialize(stream, message); + } + + public static object FromStream(Type type, MemoryStream stream) + { + return Serializer.Deserialize(type, stream); + } + + public static T FromStream(MemoryStream stream) + { + return (T) Serializer.Deserialize(typeof(T), stream); + } + + public static T Clone(T t) + { + var bytes = ToBytes(t); + using var stream = new MemoryStream(bytes, 0, bytes.Length); + return Serializer.Deserialize(stream); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs.meta new file mode 100644 index 00000000..0287ecfc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 27e0977e977a7834695b073736221239 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs new file mode 100644 index 00000000..16f73e73 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using TEngine.DataStructure; + +namespace TEngine.Core +{ + public static class RandomHelper + { + private static readonly Random Random = new Random(); + + private static readonly byte[] Byte8 = new byte[8]; + private static readonly byte[] Byte2 = new byte[2]; + + public static ulong RandUInt64() + { + Random.NextBytes(Byte8); + return BitConverter.ToUInt64(Byte8, 0); + } + + public static long RandInt64() + { + Random.NextBytes(Byte8); + return BitConverter.ToInt64(Byte8, 0); + } + + public static uint RandUInt32() + { + return (uint) Random.Next(); + } + + public static ushort RandUInt16() + { + Random.NextBytes(Byte2); + return BitConverter.ToUInt16(Byte2, 0); + } + + /// + /// 获å–lower与Upperä¹‹é—´çš„éšæœºæ•°,包å«ä¸‹é™ï¼Œä¸åŒ…å«ä¸Šé™ + /// + /// + /// + /// + public static int RandomNumber(int lower, int upper) + { + return Random.Next(lower, upper); + } + + public static bool RandomBool() + { + return Random.Next(2) == 0; + } + + public static T RandomArray(this T[] array) + { + return array[RandomNumber(0, array.Count() - 1)]; + } + + public static T RandomArray(this List array) + { + return array[RandomNumber(0, array.Count() - 1)]; + } + + /// + /// 打乱数组 + /// + /// + /// è¦æ‰“乱的数组 + public static void BreakRank(List arr) + { + if (arr == null || arr.Count < 2) + { + return; + } + + for (var i = 0; i < arr.Count / 2; i++) + { + var index = Random.Next(0, arr.Count); + (arr[index], arr[arr.Count - index - 1]) = (arr[arr.Count - index - 1], arr[index]); + } + } + + public static float RandFloat01() + { + var value = Random.NextDouble(); + return (float) value; + } + + private static int Rand(int n) + { + var rd = new Random(); + // 注æ„,返回值是左闭å³å¼€ï¼Œæ‰€ä»¥maxValueè¦åŠ 1 + return rd.Next(1, n + 1); + } + + /// + /// 通过æƒé‡éšæœº + /// + /// + /// + public static int RandomByWeight(int[] weights) + { + var sum = weights.Sum(); + var numberRand = Rand(sum); + var sumTemp = 0; + for (var i = 0; i < weights.Length; i++) + { + sumTemp += weights[i]; + if (numberRand <= sumTemp) + { + return i; + } + } + + return -1; + } + + /// + /// å›ºå®šæ¦‚çŽ‡çš„éšæœºã€å°±æ˜¯æŸæ•°å€¼ä¸Šé™é‡Œæ¯”出多少次 + /// + /// + /// + public static int RandomByFixedProbability(int[] args) + { + var argCount = args.Length; + var sum = args.Sum(); + var random = Random.NextDouble() * sum; + while (sum > random) + { + sum -= args[argCount - 1]; + argCount--; + } + + return argCount; + } + + /// + /// è¿”å›žéšæœºæ•°ã€‚ + /// + /// 是å¦åŒ…å«è´Ÿæ•°ã€‚ + /// è¿”å›žä¸€ä¸ªéšæœºçš„å•精度浮点数。 + public static float NextFloat(bool containNegative = false) + { + float f; + var buffer = new byte[4]; + if (containNegative) + { + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= float.MinValue && f < float.MaxValue) == false); + + return f; + } + + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= 0 && f < float.MaxValue) == false); + + return f; + } + + /// + /// 返回一个å°äºŽæ‰€æŒ‡å®šæœ€å¤§å€¼çš„éžè´Ÿéšæœºæ•°ã€‚ + /// + /// è¦ç”Ÿæˆçš„éšæœºæ•°çš„上é™ï¼ˆéšæœºæ•°ä¸èƒ½å–该上é™å€¼ï¼‰ã€‚ maxValue 必须大于或等于零。 + /// 大于等于零且å°äºŽ maxValue çš„å•精度浮点数,å³ï¼šè¿”回值的范围通常包括零但ä¸åŒ…括 maxValue。 ä¸è¿‡ï¼Œå¦‚æžœ maxValue 等于零,则返回 maxValue。 + public static float NextFloat(float maxValue) + { + if (maxValue.Equals(0)) + { + return maxValue; + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException("“maxValueâ€å¿…须大于 0。", "maxValue"); + } + + float f; + var buffer = new byte[4]; + + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= 0 && f < maxValue) == false); + + return f; + } + + /// + /// è¿”å›žä¸€ä¸ªæŒ‡å®šèŒƒå›´å†…çš„éšæœºæ•°ã€‚ + /// + /// è¿”å›žçš„éšæœºæ•°çš„ä¸‹ç•Œï¼ˆéšæœºæ•°å¯å–该下界值)。 + /// è¿”å›žçš„éšæœºæ•°çš„ä¸Šç•Œï¼ˆéšæœºæ•°ä¸èƒ½å–该上界值)。 maxValue 必须大于或等于 minValue。 + /// 一个大于等于 minValue 且å°äºŽ maxValue çš„å•精度浮点数,å³ï¼šè¿”回的值范围包括 minValue 但ä¸åŒ…括 maxValue。 如果 minValue 等于 maxValue,则返回 minValue。 + public static float NextFloat(float minValue, float maxValue) + { + if (minValue.Equals(maxValue)) + { + return minValue; + } + + if (minValue > maxValue) + { + throw new ArgumentOutOfRangeException("“minValueâ€ä¸èƒ½å¤§äºŽ maxValue。", "minValue"); + } + + float f; + var buffer = new byte[4]; + + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= minValue && f < maxValue) == false); + + return f; + } + + /// + /// 在4个Vector2ç‚¹èŒƒå›´å†…éšæœºå‡ºä¸€ä¸ªä½ç½® + /// + /// X轴最å°å€¼ + /// X轴最大值 + /// Y轴最å°å€¼ + /// Y轴最大值 + /// + public static Vector2 NextVector2(float minX, float maxX, float minY, float maxY) + { + return new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY)); + } + + public static string RandomNumberCode(int len = 6) + { + int num = 0; + for (int i = 0; i < len; i++) + { + int number = RandomNumber(0, 10); + num = num * 10 + number; + } + + return num.ToString(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs.meta new file mode 100644 index 00000000..831a7e9d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/RandomHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e66c86ada7ae8d4680728f4d6562245 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs new file mode 100644 index 00000000..9937de21 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs @@ -0,0 +1,36 @@ +using System; + +namespace TEngine.Core +{ + public static class TimeHelper + { + public const long Hour = 3600000; // å°æ—¶æ¯«ç§’值 60 * 60 * 1000 + public const long Minute = 60000; // 分钟毫秒值 60 * 1000 + public const long OneDay = 86400000; // 天毫秒值 24 * 60 * 60 * 1000 + private const long Epoch = 621355968000000000L; // 1970å¹´1月1日的Ticks + + private static readonly DateTime Dt1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static long Now => (DateTime.UtcNow.Ticks - Epoch) / 10000; + +#if TENGINE_UNITY + public static long TimeDiff; + public static long ServerNow => Now + TimeDiff; +#endif + + public static long Transition(DateTime d) + { + return (d.Ticks - Epoch) / 10000; + } + + public static DateTime Transition(long timeStamp) + { + return Dt1970.AddTicks(timeStamp); + } + + public static DateTime TransitionLocal(long timeStamp) + { + return Dt1970.AddTicks(timeStamp).ToLocalTime(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs.meta new file mode 100644 index 00000000..252995fb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Helper/TimeHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4061d25ace5fcdf4f866a85e45e1a8d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/IdFactory.meta b/Assets/GameScripts/DotNet/Core/IdFactory.meta new file mode 100644 index 00000000..ee21c89c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18509e45bb5163e4cb79fa240210f7cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs b/Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs new file mode 100644 index 00000000..976f20d0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs @@ -0,0 +1,55 @@ +using System.Runtime.InteropServices; + +namespace TEngine.Core +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EntityIdStruct + { + // +-------------------+----------------------+-------------------------+----------------------------------+ + // | time(30) 最大34å¹´ | AppId(8) 最多255个进程 | WordId(10) 最多1023个世界 | sequence(16) æ¯æ¯«ç§’æ¯ä¸ªè¿›ç¨‹èƒ½ç”Ÿäº§65535个 + // +-------------------+----------------------+-------------------------+----------------------------------+ + // 这样算下æ¥ã€æ¯ä¸ªä¸–界都有255个进程ã€å°±ç®—WOW的游æˆä¸€ä¸ªåŒºä¹Ÿç”¨ä¸åˆ°255个进程 + // 计算下æ¯ä¸ªä¸–界255个进程也就是1023 * 255 = 260865个进程ã€å®Œå…¨å¤Ÿç”¨äº† + // ä½†æœ‰ä¸€ä¸ªé—®é¢˜æ˜¯æ¸¸æˆæœ€å¤§åªèƒ½æœ‰1023个区了ã€ä½†å¯ä»¥é€šè¿‡æˆ˜åŒºæ‰‹æ®µæ¥è§£å†³è¿™ä¸ªé—®é¢˜ + + public uint Time{ get; private set; } + public uint Sequence{ get; private set; } + public uint RouteId { get; private set; } + + public ushort AppId => (ushort)(RouteId >> 10 & RouteIdStruct.MaskAppId); + public ushort WordId=> (ushort)(RouteId & RouteIdStruct.MaskWordId); + + public const int MaskRouteId = 0x3FFFF; + public const int MaskSequence = 0xFFFF; + + public EntityIdStruct(uint routeId, uint time, uint sequence) + { + Time = time; + Sequence = sequence; + RouteId = routeId; + } + + public static implicit operator long(EntityIdStruct entityIdStruct) + { + ulong result = 0; + result |= entityIdStruct.Sequence; + result |= (ulong)entityIdStruct.RouteId << 16; + result |= (ulong)entityIdStruct.Time << 34; + return (long)result; + } + + public static implicit operator EntityIdStruct(long id) + { + var result = (ulong) id; + var idStruct = new EntityIdStruct() + { + Sequence = (uint) (result & MaskSequence) + }; + result >>= 16; + idStruct.RouteId = (uint) (result & 0x3FFFF); + result >>= 18; + idStruct.Time = (uint) result; + return idStruct; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs.meta b/Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs.meta new file mode 100644 index 00000000..c3828128 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/EntityIdStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca0fd54b2e368d246a01a2e34420a11a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs b/Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs new file mode 100644 index 00000000..504f19bc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs @@ -0,0 +1,69 @@ +using System; + +namespace TEngine.Core +{ + public static class IdFactory + { + private static readonly long Epoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000; + private static readonly long Epoch2023 = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000 - Epoch1970; + private static readonly long EpochThisYear = new DateTime(DateTime.Now.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000 - Epoch1970; + + private static uint _lastRunTimeIdTime; + private static uint _lastRunTimeIdSequence; + + private static uint _lastEntityIdTime; + private static uint _lastEntityIdSequence; + + public static long NextRunTimeId() + { + var time = (uint) ((TimeHelper.Now - EpochThisYear) / 1000); + + if (time > _lastRunTimeIdTime) + { + _lastRunTimeIdTime = time; + _lastRunTimeIdSequence = 0; + } + else if (++_lastRunTimeIdSequence > uint.MaxValue - 1) + { + ++_lastRunTimeIdTime; + _lastRunTimeIdSequence = 0; + } + + return new RuntimeIdStruct(_lastRunTimeIdTime, _lastRunTimeIdSequence); + } + + public static long NextEntityId(uint routeId) + { + var time = (uint)((TimeHelper.Now - Epoch2023) / 1000); + + if (time > _lastEntityIdTime) + { + _lastEntityIdTime = time; + _lastEntityIdSequence = 0; + } + else if (++_lastEntityIdSequence > EntityIdStruct.MaskSequence - 1) + { + ++_lastEntityIdTime; + _lastEntityIdSequence = 0; + } + + return new EntityIdStruct(routeId, _lastEntityIdTime, _lastEntityIdSequence); + } + + public static uint GetRouteId(long entityId) + { + return (ushort)(entityId >> 16 & EntityIdStruct.MaskRouteId); + } + + public static ushort GetAppId(long entityId) + { + return (ushort)(entityId >> 26 & RouteIdStruct.MaskAppId); + } + + public static int GetWordId(long entityId) + { + return (ushort)(entityId >> 16 & RouteIdStruct.MaskWordId); + } + } +} + diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs.meta b/Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs.meta new file mode 100644 index 00000000..a1da3265 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/IdFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bcd505cdcef612546b9501632e9f56e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs b/Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs new file mode 100644 index 00000000..8ce2b162 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; + +namespace TEngine.Core +{ + // +----------------------+---------------------------+ + // | AppId(8) 最多255个进程 | WordId(10) 最多1023个世界 + // +----------------------+---------------------------+ + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RouteIdStruct + { + public ushort AppId; + public ushort WordId; + + public const int MaskAppId = 0xFF; + public const int MaskWordId = 0x3FF; + + public RouteIdStruct(ushort appId, ushort wordId) + { + AppId = appId; + WordId = wordId; + } + + public static implicit operator uint(RouteIdStruct routeId) + { + uint result = 0; + result |= routeId.WordId; + result |= (uint)routeId.AppId << 10; + return result; + } + + public static implicit operator RouteIdStruct(uint routeId) + { + var idStruct = new RouteIdStruct() + { + WordId = (ushort)(routeId & MaskWordId) + }; + + routeId >>= 10; + idStruct.AppId = (ushort)(routeId & MaskAppId); + return idStruct; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs.meta b/Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs.meta new file mode 100644 index 00000000..70b944ae --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/RouteIdStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e4db9bed6b0052e409bb43d36c146fd0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs b/Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs new file mode 100644 index 00000000..24676fa0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace TEngine.Core +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RuntimeIdStruct + { + // +----------+------------+ + // | time(32) | sequence(32) + // +----------+------------+ + + private uint Time; + private uint Sequence; + + public RuntimeIdStruct(uint time, uint sequence) + { + Time = time; + Sequence = sequence; + } + + public static implicit operator long(RuntimeIdStruct runtimeId) + { + ulong result = 0; + result |= runtimeId.Sequence; + result |= (ulong) runtimeId.Time << 32; + return (long) result; + } + + public static implicit operator RuntimeIdStruct(long id) + { + var result = (ulong) id; + var idStruct = new RuntimeIdStruct() + { + Time = (uint) (result >> 32), + Sequence = (uint) (result & 0xFFFFFFFF) + }; + return idStruct; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs.meta b/Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs.meta new file mode 100644 index 00000000..df152fe4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/IdFactory/RuntimeIdStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e67b19c4b87042949acec08063708f54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Log.meta b/Assets/GameScripts/DotNet/Core/Log.meta new file mode 100644 index 00000000..f8bb2ac6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4cf6835f4f028434f8728b338abf2316 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Log/ILog.cs b/Assets/GameScripts/DotNet/Core/Log/ILog.cs new file mode 100644 index 00000000..defd7022 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/ILog.cs @@ -0,0 +1,17 @@ +namespace TEngine +{ + public interface ILog + { + void Trace(string message); + void Warning(string message); + void Info(string message); + void Debug(string message); + void Error(string message); + void Fatal(string message); + void Trace(string message, params object[] args); + void Warning(string message, params object[] args); + void Info(string message, params object[] args); + void Debug(string message, params object[] args); + void Error(string message, params object[] args); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Log/ILog.cs.meta b/Assets/GameScripts/DotNet/Core/Log/ILog.cs.meta new file mode 100644 index 00000000..2b6f95d8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/ILog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b90975fb800e1a745a323955fa71f3a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Log/Log.cs b/Assets/GameScripts/DotNet/Core/Log/Log.cs new file mode 100644 index 00000000..c8246aa1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/Log.cs @@ -0,0 +1,141 @@ +#if TENGINE_NET +namespace TEngine +{ + public static class Log + { + static Log() + { + LogCore.Instance.ILog = new NLog("Server"); + } + + /// + /// 打å°è¿½æœ”级别日志,用于记录追朔类日志信æ¯ã€‚ + /// + /// 日志内容。 + public static void Trace(string msg) + { + LogCore.Instance.Trace(msg); + } + + /// + /// 打å°è°ƒè¯•级别日志,用于记录调试类日志信æ¯ã€‚ + /// + /// 日志内容。 + public static void Debug(string msg) + { + LogCore.Instance.Debug(msg); + } + + /// + /// 打å°ä¿¡æ¯çº§åˆ«æ—¥å¿—,用于记录信æ¯ç±»æ—¥å¿—ä¿¡æ¯ã€‚ + /// + /// 日志内容。 + public static void Info(string msg) + { + LogCore.Instance.Info(msg); + } + + /// + /// 打å°è¿½æœ”级别日志,用于记录追朔类日志信æ¯ã€‚ + /// + /// 日志内容。 + public static void TraceInfo(string msg) + { + LogCore.Instance.Trace(msg); + } + + /// + /// 打å°è¿½è­¦å‘Šæ—¥å¿—,用于记录警告类日志信æ¯ã€‚ + /// + /// 日志内容。 + public static void Warning(string msg) + { + LogCore.Instance.Warning(msg); + } + + /// + /// 打å°é”™è¯¯çº§åˆ«æ—¥å¿—,建议在å‘生功能逻辑错误,但尚ä¸ä¼šå¯¼è‡´æ¸¸æˆå´©æºƒæˆ–异常时使用。 + /// + /// 日志内容。 + public static void Error(string msg) + { + LogCore.Instance.Error(msg); + } + + /// + /// 打å°é”™è¯¯çº§åˆ«æ—¥å¿—,建议在å‘生功能逻辑错误,但尚ä¸ä¼šå¯¼è‡´æ¸¸æˆå´©æºƒæˆ–异常时使用。 + /// + /// 异常内容。 + public static void Error(Exception exception) + { + LogCore.Instance.Error(exception); + } + + /// + /// 打å°ä¸¥é‡é”™è¯¯çº§åˆ«æ—¥å¿—,建议在å‘生严é‡é”™è¯¯ï¼Œå¯èƒ½å¯¼è‡´æ¸¸æˆå´©æºƒæˆ–异常时使用,此时应å°è¯•é‡å¯è¿›ç¨‹æˆ–é‡å»ºæœåŠ¡æ¡†æž¶ã€‚ + /// + /// 日志内容。 + public static void Fatal(string msg) + { + LogCore.Instance.Fatal(msg); + } + + /// + /// 打å°ä¸¥é‡é”™è¯¯çº§åˆ«æ—¥å¿—,建议在å‘生严é‡é”™è¯¯ï¼Œå¯èƒ½å¯¼è‡´æ¸¸æˆå´©æºƒæˆ–异常时使用,此时应å°è¯•é‡å¯è¿›ç¨‹æˆ–é‡å»ºæœåŠ¡æ¡†æž¶ã€‚ + /// + /// 常内容。 + public static void Fatal(Exception exception) + { + LogCore.Instance.Fatal(exception.Message); + } + + /// + /// 断言严é‡é”™è¯¯çº§åˆ«æ—¥å¿—,建议在å‘生严é‡é”™è¯¯ã€‚ + /// + /// 断言æ¡ä»¶ + /// + public static void Assert(bool condition, string msg = "") + { + if (!condition) + { + if (string.IsNullOrEmpty(msg)) + { + msg = AssertError; + } + + LogCore.Instance.Error(msg); + } + } + + /// + /// 断言默认日志。 + /// + public const string AssertError = "ASSERT FAILD"; + + public static void Trace(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message) + { + LogCore.Instance.Trace(message.ToStringAndClear()); + } + + public static void Warning(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message) + { + LogCore.Instance.Warning(message.ToStringAndClear()); + } + + public static void Info(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message) + { + LogCore.Instance.Info(message.ToStringAndClear()); + } + + public static void Debug(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message) + { + LogCore.Instance.Debug(message.ToStringAndClear()); + } + + public static void Error(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message) + { + LogCore.Instance.Error(message.ToStringAndClear()); + } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Log/Log.cs.meta b/Assets/GameScripts/DotNet/Core/Log/Log.cs.meta new file mode 100644 index 00000000..0c497aea --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/Log.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7794bf5b314b88d409d6113935a0d1f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Log/LogCore.cs b/Assets/GameScripts/DotNet/Core/Log/LogCore.cs new file mode 100644 index 00000000..cfa9cd8e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/LogCore.cs @@ -0,0 +1,267 @@ +#if TENGINE_NET +using System.Diagnostics; +using System.Text; +using TEngine.Core; + +namespace TEngine +{ + public class LogCore : Singleton + { + private ILog iLog; + + public ILog ILog + { + set => iLog = value; + } + + public enum LogLevel + { + INFO, + DEBUG, + ASSERT, + WARNING, + ERROR, + EXCEPTION, + } + + private LogLevel _filterLevel = LogLevel.INFO; + private StringBuilder _stringBuilder = new StringBuilder(); + + private const int TraceLevel = 1; + private const int DebugLevel = 2; + private const int InfoLevel = 3; + private const int WarningLevel = 4; + + private bool CheckLogLevel(int level) + { + if (Define.Options == null) + { + return true; + } + + return Define.Options.LogLevel <= level; + } + + public void Trace(string msg) + { + if (!CheckLogLevel(DebugLevel)) + { + return; + } + + StackTrace st = new StackTrace(2, true); + string log = $"{msg}\n{st}"; + this.Log(LogLevel.INFO, log); + this.iLog.Trace(log); + } + + public void Debug(string msg) + { + if (!CheckLogLevel(DebugLevel)) + { + return; + } + this.Log(LogLevel.DEBUG, msg); + this.iLog.Debug(msg); + } + + public void Info(string msg) + { + if (!CheckLogLevel(InfoLevel)) + { + return; + } + this.Log(LogLevel.INFO, msg); + this.iLog.Info(msg); + } + + public void TraceInfo(string msg) + { + if (!CheckLogLevel(InfoLevel)) + { + return; + } + + StackTrace st = new StackTrace(2, true); + string log = $"{msg}\n{st}"; + this.Log(LogLevel.INFO, msg); + this.iLog.Trace(log); + } + + public void Warning(string msg) + { + if (!CheckLogLevel(WarningLevel)) + { + return; + } + this.Log(LogLevel.WARNING, msg); + this.iLog.Warning(msg); + } + + public void Error(string msg) + { + StackTrace st = new StackTrace(2, true); + string log = $"{msg}\n{st}"; + this.Log(LogLevel.ERROR, log); + this.iLog.Error(log); + } + + public void Error(Exception e) + { + if (e.Data.Contains("StackTrace")) + { + this.iLog.Error($"{e.Data["StackTrace"]}\n{e}"); + return; + } + + string str = e.ToString(); + this.Log(LogLevel.ERROR, str); + this.iLog.Error(str); + } + + public void Fatal(string msg) + { + StackTrace st = new StackTrace(2, true); + this.iLog.Fatal($"{msg}\n{st}"); + } + + public void Trace(string message, params object[] args) + { + if (!CheckLogLevel(TraceLevel)) + { + return; + } + + StackTrace st = new StackTrace(2, true); + this.iLog.Trace($"{string.Format(message, args)}\n{st}"); + } + + public void Warning(string message, params object[] args) + { + if (!CheckLogLevel(WarningLevel)) + { + return; + } + + this.iLog.Warning(string.Format(message, args)); + } + + public void Info(string message, params object[] args) + { + if (!CheckLogLevel(InfoLevel)) + { + return; + } + + this.iLog.Info(string.Format(message, args)); + } + + public void Debug(string message, params object[] args) + { + if (!CheckLogLevel(DebugLevel)) + { + return; + } + + this.iLog.Debug(string.Format(message, args)); + } + + public void Error(string message, params object[] args) + { + StackTrace st = new StackTrace(2, true); + string s = string.Format(message, args) + '\n' + st; + this.iLog.Error(s); + } + + private StringBuilder GetFormatString(LogLevel logLevel, string logString) + { + _stringBuilder.Clear(); + switch (logLevel) + { + case LogLevel.DEBUG: + _stringBuilder.AppendFormat( + "[TEngine][DEBUG][{0:yyyy-MM-dd HH:mm:ss fff}] - {1}", DateTime.Now, logString); + break; + case LogLevel.INFO: + _stringBuilder.AppendFormat( + "[TEngine][INFO][{0:yyyy-MM-dd HH:mm:ss fff}] - {1}", DateTime.Now, logString); + break; + case LogLevel.ASSERT: + _stringBuilder.AppendFormat( + "[TEngine][ASSERT][{0:yyyy-MM-dd HH:mm:ss fff}] - {1}", DateTime.Now, logString); + break; + case LogLevel.WARNING: + _stringBuilder.AppendFormat( + "[TEngine][WARNING][{0:yyyy-MM-dd HH:mm:ss fff}] - {1}", DateTime.Now, + logString); + break; + case LogLevel.ERROR: + _stringBuilder.AppendFormat( + "[TEngine][ERROR][{0:yyyy-MM-dd HH:mm:ss fff}] - {1}", DateTime.Now, logString); + break; + case LogLevel.EXCEPTION: + _stringBuilder.AppendFormat( + "[TEngine][EXCEPTION][{0:yyyy-MM-dd HH:mm:ss fff}] - {1}", DateTime.Now, logString); + break; + } + + return _stringBuilder; + } + + private void Log(LogLevel type, string logString) + { + if (type < _filterLevel) + { + return; + } + + StringBuilder infoBuilder = GetFormatString(type, logString); + + if (type == LogLevel.ERROR || type == LogLevel.WARNING || type == LogLevel.EXCEPTION) + { + StackFrame[] sf = new StackTrace().GetFrames(); + for (int i = 0; i < sf.Length; i++) + { + StackFrame frame = sf[i]; + string declaringTypeName = frame.GetMethod()?.DeclaringType.FullName; + string methodName = frame.GetMethod()?.Name; + infoBuilder.AppendFormat("[{0}::{1}\n", declaringTypeName, methodName); + } + } + + string logStr = infoBuilder.ToString(); + + if (type == LogLevel.INFO) + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(logStr); + } + else if (type == LogLevel.DEBUG) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(logStr); + } + else if (type == LogLevel.WARNING) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine(logStr); + } + else if (type == LogLevel.ASSERT) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(logStr); + } + else if (type == LogLevel.ERROR) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(logStr); + } + else if (type == LogLevel.EXCEPTION) + { + Console.ForegroundColor = ConsoleColor.Magenta; + Console.WriteLine(logStr); + } + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Log/LogCore.cs.meta b/Assets/GameScripts/DotNet/Core/Log/LogCore.cs.meta new file mode 100644 index 00000000..cb792b44 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/LogCore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a0e8dec854eecb448be2f1bf80fc3d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Log/NLog.cs b/Assets/GameScripts/DotNet/Core/Log/NLog.cs new file mode 100644 index 00000000..8ad3b94e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/NLog.cs @@ -0,0 +1,75 @@ +#if TENGINE_NET +using NLog; + +namespace TEngine; + +public class NLog : ILog +{ + private readonly Logger _logger; + + public NLog(string name) + { + _logger = LogManager.GetLogger(name); + } + + public void Trace(string message) + { + _logger.Trace(message); + } + + public void Warning(string message) + { + _logger.Warn(message); + } + + public void Info(string message) + { + _logger.Info(message); + } + + public void Debug(string message) + { + _logger.Debug(message); + } + + public void Error(string message) + { + _logger.Error(message); + } + + public void Fatal(string message) + { + _logger.Fatal(message); + } + + public void Trace(string message, params object[] args) + { + _logger.Trace(message, args); + } + + public void Warning(string message, params object[] args) + { + _logger.Warn(message, args); + } + + public void Info(string message, params object[] args) + { + _logger.Info(message, args); + } + + public void Debug(string message, params object[] args) + { + _logger.Debug(message, args); + } + + public void Error(string message, params object[] args) + { + _logger.Error(message, args); + } + + public void Fatal(string message, params object[] args) + { + _logger.Fatal(message, args); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Log/NLog.cs.meta b/Assets/GameScripts/DotNet/Core/Log/NLog.cs.meta new file mode 100644 index 00000000..3961e58f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Log/NLog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9dee8b48fb3d80418d31ed532a904f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network.meta b/Assets/GameScripts/DotNet/Core/Network.meta new file mode 100644 index 00000000..80423a65 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c06c0635bd8bd84b90c02b96187e575 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base.meta b/Assets/GameScripts/DotNet/Core/Network/Base.meta new file mode 100644 index 00000000..bcb31ffc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1d6d37d54409f584d95bcfea9b9345b3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Enum.meta new file mode 100644 index 00000000..a3cbeba7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 457e84e8ec81f254da550c11a78d4353 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs new file mode 100644 index 00000000..34bbd946 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs @@ -0,0 +1,9 @@ +namespace TEngine.Core.Network +{ + public enum NetworkProtocolType + { + None = 0, + KCP = 1, + TCP = 2 + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs.meta new file mode 100644 index 00000000..ef04b761 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkProtocolType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74351b44f2b6d4a41969a8159e2808ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs new file mode 100644 index 00000000..32320ccb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs @@ -0,0 +1,9 @@ +namespace TEngine.Core.Network +{ + public enum NetworkTarget + { + None = 0, + Outer = 1, + Inner = 2 + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs.meta new file mode 100644 index 00000000..e7c5b332 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkTarget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf647911052fccc40b1d4601ac423b5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs new file mode 100644 index 00000000..7a995089 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs @@ -0,0 +1,9 @@ +namespace TEngine.Core.Network +{ + public enum NetworkType + { + None = 0, + Client = 1, + Server = 2 + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs.meta new file mode 100644 index 00000000..efcdf6f5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Enum/NetworkType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8841f1e5b0a30eb4cad8a4e0a099adc8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Helper.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Helper.meta new file mode 100644 index 00000000..5978eab1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Helper.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a19a0ba64b936934ba63f099234b941a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs new file mode 100644 index 00000000..83f70ef6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +// ReSharper disable InconsistentNaming + +namespace TEngine.Core +{ + public static class NetworkHelper + { + public static string[] GetAddressIPs() + { + var list = new List(); + foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + if (networkInterface.NetworkInterfaceType != NetworkInterfaceType.Ethernet) + { + continue; + } + + foreach (var add in networkInterface.GetIPProperties().UnicastAddresses) + { + list.Add(add.Address.ToString()); + } + } + + return list.ToArray(); + } + + public static IPEndPoint ToIPEndPoint(string host, int port) + { + return new IPEndPoint(IPAddress.Parse(host), port); + } + + public static IPEndPoint ToIPEndPoint(string address) + { + var index = address.LastIndexOf(':'); + var host = address.Substring(0, index); + var p = address.Substring(index + 1); + var port = int.Parse(p); + return ToIPEndPoint(host, port); + } + + public static string IPEndPointToStr(this IPEndPoint self) + { + return $"{self.Address}:{self.Port}"; + } + + public static void SetSioUdpConnReset(Socket socket) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + /* + ç›®å‰è¿™ä¸ªé—®é¢˜åªæœ‰Windows下æ‰ä¼šå‡ºçŽ°ã€‚ + æœåŠ¡å™¨ç«¯åœ¨å‘逿•°æ®æ—¶æ•获到了一个异常, + 这个异常导致原因应该是远程客户端的UDP监å¬å·²åœæ­¢å¯¼è‡´æ•°æ®å‘é€å‡ºé”™ã€‚ + 按ç†è¯´UDP是无连接的,报这个异常是ä¸åˆç†çš„ + 这个异常让整UDPçš„æœåŠ¡ç›‘å¬ä¹Ÿåœæ­¢äº†ã€‚ + 这样就因为一个客户端的数æ®å‘逿— æ³•到达而导致了æœåŠ¡æŒ‚äº†ï¼Œæ‰€æœ‰å®¢æˆ·ç«¯éƒ½æ— æ³•ä¸ŽæœåŠ¡å™¨é€šä¿¡äº† + 想详细了解看下https://blog.csdn.net/sunzhen6251/article/details/124168805*/ + const uint IOC_IN = 0x80000000; + const uint IOC_VENDOR = 0x18000000; + const int SIO_UDP_CONNRESET = unchecked((int) (IOC_IN | IOC_VENDOR | 12)); + + socket.IOControl(SIO_UDP_CONNRESET, new[] {Convert.ToByte(false)}, null); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs.meta new file mode 100644 index 00000000..4157749d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Helper/NetworkHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: adfef17cb18d2624c8b425b5004f9e75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Interface.meta new file mode 100644 index 00000000..0a615b64 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 93c4a9920a37e1b4c9df005d5f1dfa54 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs new file mode 100644 index 00000000..9b941dfa --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Net; + +namespace TEngine.Core.Network +{ + public abstract class AClientNetwork : ANetwork + { + public uint ChannelId { get; protected set; } + public abstract event Action OnDispose; + public abstract event Action OnChangeChannelId; + public abstract event Action OnReceiveMemoryStream; + + protected AClientNetwork(Scene scene, NetworkType networkType, NetworkProtocolType networkProtocolType, NetworkTarget networkTarget) : base(scene, networkType, networkProtocolType, networkTarget) { } + + public abstract uint Connect(IPEndPoint remoteEndPoint, Action onConnectComplete, Action onConnectFail, int connectTimeout = 5000); + + public override void Dispose() + { + ChannelId = 0; + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs.meta new file mode 100644 index 00000000..fc9a51fe --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/AClientNetwork.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 513303d3661a378438e54a292c1c6d15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs new file mode 100644 index 00000000..88351875 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs @@ -0,0 +1,139 @@ +using System; +using System.IO; +using TEngine.Core; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public struct MessageCacheInfo + { + public uint RpcId; + public long RouteId; + public long RouteTypeOpCode; + public object Message; + public MemoryStream MemoryStream; + } + + public abstract class ANetwork : IDisposable + { + public long Id { get; protected set; } + public Scene Scene { get; protected set; } + public bool IsDisposed { get; protected set; } + public NetworkType NetworkType { get; private set; } + public NetworkTarget NetworkTarget { get; private set; } + public NetworkProtocolType NetworkProtocolType { get; private set; } + public ANetworkMessageScheduler NetworkMessageScheduler { get; protected set; } + + protected readonly Func Pack; + private readonly LastMessageInfo _lastMessageInfo = new LastMessageInfo(); + + protected ANetwork(Scene scene, NetworkType networkType, NetworkProtocolType networkProtocolType, NetworkTarget networkTarget) + { + Scene = scene; + NetworkType = networkType; + NetworkTarget = networkTarget; + NetworkProtocolType = networkProtocolType; + Id = IdFactory.NextRunTimeId(); +#if TENGINE_NET + if (networkTarget == NetworkTarget.Inner) + { + Pack = InnerPack; + NetworkMessageScheduler = new InnerMessageScheduler(); + return; + } +#endif + Pack = OuterPack; + + switch (networkType) + { + case NetworkType.Client: + { + NetworkMessageScheduler = new ClientMessageScheduler(); + return; + } + case NetworkType.Server: + { + NetworkMessageScheduler = new OuterMessageScheduler(); + return; + } + } + } +#if TENGINE_NET + private MemoryStream InnerPack(uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream, object message) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return null; + } +#endif + if (memoryStream != null) + { + return InnerPacketParser.Pack(rpcId, routeId, memoryStream); + } + + // åªé’ˆå¯¹æœåС噍åšç¼“存消æ¯ä¼˜åŒ–ï¼ˆä¾‹å¦‚ç¾¤å‘æ¶ˆæ¯ç­‰ï¼‰ã€é¿å…多次åºåˆ—化 + if (ReferenceEquals(_lastMessageInfo.Message, message)) + { + _lastMessageInfo.MemoryStream.Seek(0, SeekOrigin.Begin); + return _lastMessageInfo.MemoryStream; + } + + memoryStream = InnerPacketParser.Pack(rpcId, routeId, message); + _lastMessageInfo.MemoryStream = memoryStream; + _lastMessageInfo.Message = message; + return memoryStream; + } +#endif + private MemoryStream OuterPack(uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream, object message) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return null; + } +#endif + if (memoryStream != null) + { + return OuterPacketParser.Pack(rpcId, routeTypeOpCode, memoryStream); + } + + // åªé’ˆå¯¹æœåС噍åšç¼“存消æ¯ä¼˜åŒ–ï¼ˆä¾‹å¦‚ç¾¤å‘æ¶ˆæ¯ç­‰ï¼‰ã€é¿å…多次åºåˆ—化 + // å®¢æˆ·ç«¯æ²¡æœ‰ç¾¤å‘æ¶ˆæ¯çš„功能ã€ä¸€èˆ¬å®¢æˆ·ç«¯éƒ½æ˜¯è‡ªå·±ç¼“存消æ¯ã€å¦‚果这里åšäº†ç¼“å­˜å而ä¸å¥½äº† +#if TENGINE_NET + if (ReferenceEquals(_lastMessageInfo.Message, message)) + { + _lastMessageInfo.MemoryStream.Seek(0, SeekOrigin.Begin); + return _lastMessageInfo.MemoryStream; + } +#endif + memoryStream = OuterPacketParser.Pack(rpcId, routeTypeOpCode, message); +#if TENGINE_NET + _lastMessageInfo.MemoryStream = memoryStream; + _lastMessageInfo.Message = message; +#endif + return memoryStream; + } + + public abstract void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, object message); + public abstract void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream); + public abstract void RemoveChannel(uint channelId); + + public virtual void Dispose() + { + NetworkThread.Instance?.RemoveNetwork(Id); + + Id = 0; + Scene = null; + IsDisposed = true; + NetworkType = NetworkType.None; + NetworkTarget = NetworkTarget.None; + NetworkProtocolType = NetworkProtocolType.None; + + _lastMessageInfo.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs.meta new file mode 100644 index 00000000..6a1e7411 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8e1b157f4cd9134b84dd17cbb6cb498 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs new file mode 100644 index 00000000..e4fc312b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Net; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public abstract class ANetworkChannel + { + public uint Id { get; private set; } + public Scene Scene { get; protected set; } + public long NetworkId { get; private set; } + public bool IsDisposed { get; protected set; } + public EndPoint RemoteEndPoint { get; protected set; } + public APacketParser PacketParser { get; protected set; } + + public uint LocalConn + { + get + { + return (uint)this.Id; + } + private set + { + this.Id = value; + } + } + + public abstract event Action OnDispose; + public abstract event Action OnReceiveMemoryStream; + + protected ANetworkChannel(Scene scene, uint id, long networkId) + { + Id = id; + Scene = scene; + NetworkId = networkId; + } + + public virtual void Dispose() + { + NetworkThread.Instance.RemoveChannel(NetworkId, Id); + + Id = 0; + Scene = null; + NetworkId = 0; + IsDisposed = true; + RemoteEndPoint = null; + + if (PacketParser != null) + { + PacketParser.Dispose(); + PacketParser = null; + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs.meta new file mode 100644 index 00000000..97ed3761 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetworkChannel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cef25b06be672c04993353b330cef248 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs new file mode 100644 index 00000000..cfdd9daf --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs @@ -0,0 +1,7 @@ +namespace TEngine.Core.Network +{ + public interface INetworkUpdate + { + void Update(); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs.meta new file mode 100644 index 00000000..57531212 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/INetworkUpdate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7eb7bbbac8bc38f429bad42158a0eb39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs b/Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs new file mode 100644 index 00000000..2b6e745f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public class LastMessageInfo : IDisposable + { + public object Message; + public MemoryStream MemoryStream; + + public void Dispose() + { + Message = null; + MemoryStream = null; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs.meta new file mode 100644 index 00000000..22f68fc9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/LastMessageInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9135b10424d287419b2b9b46caa803d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Server.meta new file mode 100644 index 00000000..6182bd39 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b9629d3993af22245b83309fcfee436e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs new file mode 100644 index 00000000..8546e8cc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs @@ -0,0 +1,12 @@ +#if TENGINE_NET +namespace TEngine; + +public class MachineConfigInfo +{ + public uint Id; + public string OuterIP; + public string OuterBindIP; + public string InnerBindIP; + public int ManagementPort; +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs.meta new file mode 100644 index 00000000..aa83d518 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/MachineConfigInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1e5851b16c4a4d49b253c124b953fb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs new file mode 100644 index 00000000..80fbed2d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs @@ -0,0 +1,19 @@ +namespace TEngine +{ + public class SceneConfigInfo + { + public Scene Scene; + public long EntityId; + + public uint Id; + public string SceneType; + public string Name; + public string NetworkProtocol; + public uint RouteId; + public uint WorldId; + public int OuterPort; + } +} + + + diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs.meta new file mode 100644 index 00000000..2f5ad2f0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/SceneConfigInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4fb765098d2e8f34894ec2696a260f48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs new file mode 100644 index 00000000..3ebc02ba --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs @@ -0,0 +1,173 @@ +#if TENGINE_NET +using TEngine.Core; +using TEngine.Core.Network; +#pragma warning disable CS8603 +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine +{ + public sealed class Server + { + public uint Id { get; private set; } + public Scene Scene { get; private set; } + private readonly Dictionary _sessions = new Dictionary(); + + private sealed class ConnectInfo : IDisposable + { + public Session Session; + public Entity NetworkComponent; + + public ConnectInfo(Session session, Entity networkComponent) + { + Session = session; + NetworkComponent = networkComponent; + } + + public void Dispose() + { + if (Session != null) + { + Session.Dispose(); + Session = null; + } + + if (NetworkComponent != null) + { + NetworkComponent.Dispose(); + NetworkComponent = null; + } + } + } + + public Session GetSession(uint targetServerId) + { + if (_sessions.TryGetValue(targetServerId, out var connectInfo)) + { + if (!connectInfo.Session.IsDisposed) + { + return connectInfo.Session; + } + + _sessions.Remove(targetServerId); + } + + // åŒä¸€ä¸ªServer下ã€åªéœ€è¦å†…éƒ¨èµ°ä¸‹æ¶ˆæ¯æ´¾å‘å°±å¯ä»¥ + + if (Id == targetServerId) + { + var serverNetworkComponent = Scene.GetComponent(); + var session = Session.Create(serverNetworkComponent.Network); + _sessions.Add(targetServerId, new ConnectInfo(session, null)); + return session; + } + + // ä¸åŒçš„Server下需è¦å»ºç«‹ç½‘络连接 + + var serverConfigInfo = ConfigTableManage.ServerConfig(targetServerId); + + if (serverConfigInfo == null) + { + throw new Exception($"The server with ServerId {targetServerId} was not found in the configuration file"); + } + + var machineConfigInfo = ConfigTableManage.MachineConfig(serverConfigInfo.MachineId); + + if (machineConfigInfo == null) + { + throw new Exception($"Server.cs The specified MachineId was not found: {serverConfigInfo.MachineId}"); + } + + var ipEndPoint = NetworkHelper.ToIPEndPoint($"{machineConfigInfo.InnerBindIP}:{serverConfigInfo.InnerPort}"); + var clientNetworkComponent = Entity.Create(Scene); + clientNetworkComponent.Initialize(NetworkProtocolType.KCP, NetworkTarget.Inner); + clientNetworkComponent.Connect(ipEndPoint,null, () => + { + Log.Error($"Unable to connect to the target server sourceServerId:{Id} targetServerId:{targetServerId}"); + }); + _sessions.Add(targetServerId, new ConnectInfo(clientNetworkComponent.Session, clientNetworkComponent)); + return clientNetworkComponent.Session; + } + + public void RemoveSession(uint targetServerId) + { + if (!_sessions.Remove(targetServerId, out var connectInfo)) + { + return; + } + + connectInfo.Dispose(); + } + + #region Static + + private static readonly Dictionary Servers = new Dictionary(); + + public static async FTask Create(uint routeId) + { + var serverConfigInfo = ConfigTableManage.ServerConfig(routeId); + + if (serverConfigInfo == null) + { + Log.Error($"not found server by Id:{routeId}"); + return; + } + + var machineConfigInfo = ConfigTableManage.MachineConfig(serverConfigInfo.MachineId); + + if (machineConfigInfo == null) + { + Log.Error($"not found machine by Id:{serverConfigInfo.MachineId}"); + return; + } + + var sceneInfos = Scene.GetSceneInfoByRouteId(routeId); + await Create(routeId, machineConfigInfo.InnerBindIP, serverConfigInfo.InnerPort, machineConfigInfo.OuterBindIP, sceneInfos); + // Log.Info($"ServerId:{routeId} is start complete"); + } + + public static async FTask Create(uint routeId, string innerBindIp, int innerPort, string outerBindIp, List sceneInfos) + { + if (Servers.TryGetValue(routeId, out var server)) + { + return server; + } + + // 创建一个新的Serverã€åˆ›å»ºä¸€ä¸ªä¸´æ—¶Sceneç»™Server当åšScene使用 + + server = new Server + { + Id = routeId + }; + + server.Scene = await Scene.Create($"ServerScene{routeId}", server, new EntityIdStruct(routeId, 0, 0)); + + // 创建网络ã€Server下的网络åªèƒ½æ˜¯å†…部网络ã€å¤–部网络是在Scene中定义 + + if (!string.IsNullOrEmpty(innerBindIp) && innerPort != 0) + { + var address = NetworkHelper.ToIPEndPoint(innerBindIp, innerPort); + var serverNetworkComponent = server.Scene.AddComponent(); + serverNetworkComponent.Initialize(NetworkProtocolType.KCP, NetworkTarget.Inner, address); + } + + // 创建Server拥有所有的Scene + + foreach (var sceneConfig in sceneInfos) + { + await Scene.Create(server, outerBindIp, sceneConfig); + } + + Servers.Add(routeId, server); + return server; + } + + public static Server Get(uint routeId) + { + return Servers.TryGetValue(routeId, out var server) ? server : null; + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs.meta new file mode 100644 index 00000000..40072da4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/Server.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c108c5f2d1cfef844b1842be027fda0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs new file mode 100644 index 00000000..23f18cb2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs @@ -0,0 +1,10 @@ +#if TENGINE_NET +namespace TEngine; + +public class ServerConfigInfo +{ + public uint Id; + public uint MachineId; + public int InnerPort; +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs.meta new file mode 100644 index 00000000..6fcfb6b0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Server/ServerConfigInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d2ecc96f78db214fa45582d7b146b93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs b/Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs new file mode 100644 index 00000000..a4b01440 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs @@ -0,0 +1,51 @@ +using System.Net.Sockets; + +namespace TEngine.Core.Network +{ + public static class SocketExtensions + { + public static void SetSocketBufferToOsLimit(this Socket socket) + { + socket.SetReceiveBufferToOSLimit(); + socket.SetSendBufferToOSLimit(); + } + + // 100k attempts of 1 KB increases = default + 100 MB max + public static void SetReceiveBufferToOSLimit(this Socket socket, int stepSize = 1024, int attempts = 100_000) + { + // setting a too large size throws a socket exception. + // so let's keep increasing until we encounter it. + for (int i = 0; i < attempts; ++i) + { + // increase in 1 KB steps + try + { + socket.ReceiveBufferSize += stepSize; + } + catch (SocketException) + { + break; + } + } + } + + // 100k attempts of 1 KB increases = default + 100 MB max + public static void SetSendBufferToOSLimit(this Socket socket, int stepSize = 1024, int attempts = 100_000) + { + // setting a too large size throws a socket exception. + // so let's keep increasing until we encounter it. + for (var i = 0; i < attempts; ++i) + { + // increase in 1 KB steps + try + { + socket.SendBufferSize += stepSize; + } + catch (SocketException) + { + break; + } + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs.meta new file mode 100644 index 00000000..735f50a0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Base/SocketExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4638b4ab890780c419b527a606f0b184 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity.meta b/Assets/GameScripts/DotNet/Core/Network/Entity.meta new file mode 100644 index 00000000..62572f80 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f9ea43e4953ac874aa8ec1c44059c396 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs b/Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs new file mode 100644 index 00000000..6f5adb13 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs @@ -0,0 +1,57 @@ +using System; +using System.Net; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class ClientNetworkComponent : Entity + { + private AClientNetwork Network { get; set; } + public Session Session { get; private set; } + + public void Initialize(NetworkProtocolType networkProtocolType, NetworkTarget networkTarget) + { + switch (networkProtocolType) + { + case NetworkProtocolType.KCP: + { + Network = new KCPClientNetwork(Scene, networkTarget); + return; + } + case NetworkProtocolType.TCP: + { + Network = new TCPClientNetwork(Scene, networkTarget); + return; + } + default: + { + throw new NotSupportedException($"Unsupported NetworkProtocolType:{networkProtocolType}"); + } + } + } + + public void Connect(IPEndPoint remoteEndPoint, Action onConnectComplete, Action onConnectFail, int connectTimeout = 5000) + { + if (Network == null || Network.IsDisposed) + { + throw new NotSupportedException("Network is null or isDisposed"); + } + + Network.Connect(remoteEndPoint, onConnectComplete, onConnectFail, connectTimeout); + Session = Session.Create(Network); + } + + public override void Dispose() + { + if (Network != null) + { + Network.Dispose(); + Network = null; + } + + Session = null; + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs.meta new file mode 100644 index 00000000..66c4648f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/ClientNetworkComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8de6a8ca9e15721489a435305757da53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs b/Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs new file mode 100644 index 00000000..7fd9b5bd --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs @@ -0,0 +1,46 @@ +using System; +using System.Net; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class ServerNetworkComponent : Entity, INotSupportedPool + { + public ANetwork Network { get; private set; } + + public void Initialize(NetworkProtocolType networkProtocolType, NetworkTarget networkTarget, IPEndPoint address) + { + switch (networkProtocolType) + { + case NetworkProtocolType.KCP: + { + Network = new KCPServerNetwork(Scene, networkTarget, address); + // Log.Info($"NetworkProtocol:KCP IPEndPoint:{address}"); + return; + } + case NetworkProtocolType.TCP: + { + Network = new TCPServerNetwork(Scene, networkTarget, address); + // Log.Info($"NetworkProtocol:TCP IPEndPoint:{address}"); + return; + } + default: + { + throw new NotSupportedException($"Unsupported NetworkProtocolType:{networkProtocolType}"); + } + } + } + + public override void Dispose() + { + if (Network != null) + { + Network.Dispose(); + Network = null; + } + + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs.meta new file mode 100644 index 00000000..cc8c9782 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/ServerNetworkComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08a60481ef8b51540b31a673aa1837e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/Session.meta b/Assets/GameScripts/DotNet/Core/Network/Entity/Session.meta new file mode 100644 index 00000000..f9034a86 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/Session.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 313e3a0020362b4408b2b68e276004e6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs new file mode 100644 index 00000000..7d3d52f4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs @@ -0,0 +1,49 @@ +using TEngine.Core; + +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class ServerInnerSession : Session +{ + public override void Send(object message, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + // åºåˆ—化消æ¯åˆ°æµä¸­ + var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + InnerPacketParser.Serialize(message, memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + // åˆ†å‘æ¶ˆæ¯ + var packInfo = InnerPackInfo.Create(rpcId, routeId, ((IMessage)message).OpCode(), 0, memoryStream); + NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine(); + } + + public override void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + // åºåˆ—化消æ¯åˆ°æµä¸­ + var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + InnerPacketParser.Serialize(routeMessage, memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + // åˆ†å‘æ¶ˆæ¯ + var packInfo = InnerPackInfo.Create(rpcId, routeId, routeMessage.OpCode(), routeMessage.RouteTypeOpCode(), memoryStream); + NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine(); + } + + public override void Send(MemoryStream memoryStream, uint rpcId = 0, long routeTypeOpCode = 0, long routeId = 0) + { + throw new Exception("The use of this method is not supported"); + } + + public override FTask Call(IRequest request, long routeId = 0) + { + throw new Exception("The use of this method is not supported"); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs.meta new file mode 100644 index 00000000..e3a12b20 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 787fbc8b90a182644abe477d98ed8939 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs new file mode 100644 index 00000000..f7fde407 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using TEngine.Core; +#pragma warning disable CS8603 +#pragma warning disable CS8601 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public class Session : Entity, INotSupportedPool, ISupportedMultiEntity + { + private uint _rpcId; + public long NetworkId { get; private set; } + public uint ChannelId { get; private set; } + public long LastReceiveTime { get; private set; } + public ANetworkMessageScheduler NetworkMessageScheduler { get; private set;} + private static readonly Dictionary Sessions = new (); + public readonly Dictionary> RequestCallback = new(); + + public static void Create(ANetworkMessageScheduler networkMessageScheduler, ANetworkChannel channel) + { +#if TENGINE_DEVELOP + if (ThreadSynchronizationContext.Main.ThreadId != Thread.CurrentThread.ManagedThreadId) + { + throw new NotSupportedException("Session Create not in MainThread"); + } +#endif + var session = Entity.Create(channel.Scene); + session.ChannelId = channel.Id; + session.NetworkId = channel.NetworkId; + session.NetworkMessageScheduler = networkMessageScheduler; + channel.OnDispose += session.Dispose; + channel.OnReceiveMemoryStream += session.OnReceive; + Sessions.Add(session.Id, session); + } + + public static Session Create(AClientNetwork network) + { +#if TENGINE_DEVELOP + if (ThreadSynchronizationContext.Main.ThreadId != Thread.CurrentThread.ManagedThreadId) + { + throw new NotSupportedException("Session Create not in MainThread"); + } +#endif + var session = Entity.Create(network.Scene); + session.ChannelId = network.ChannelId; + session.NetworkId = network.Id; + session.NetworkMessageScheduler = network.NetworkMessageScheduler; + network.OnDispose += session.Dispose; + network.OnChangeChannelId += session.OnChangeChannelId; + network.OnReceiveMemoryStream += session.OnReceive; + Sessions.Add(session.Id, session); + return session; + } +#if TENGINE_NET + public static ServerInnerSession Create(ANetwork network) + { +#if TENGINE_DEVELOP + if (ThreadSynchronizationContext.Main.ThreadId != Thread.CurrentThread.ManagedThreadId) + { + throw new NotSupportedException("Session Create not in MainThread"); + } +#endif + var session = Entity.Create(network.Scene); + session.NetworkMessageScheduler = network.NetworkMessageScheduler; + Sessions.Add(session.Id, session); + return session; + } + + public static ServerInnerSession CreateServerInner(Scene scene) + { +#if TENGINE_DEVELOP + if (ThreadSynchronizationContext.Main.ThreadId != Thread.CurrentThread.ManagedThreadId) + { + throw new NotSupportedException("Session Create not in MainThread"); + } +#endif + var session = Entity.Create(scene, false); + Sessions.Add(session.Id, session); + return session; + } +#endif + + public static bool TryGet(long id, out Session session) + { + return Sessions.TryGetValue(id, out session); + } + + public override void Dispose() + { +#if TENGINE_DEVELOP + if (ThreadSynchronizationContext.Main.ThreadId != Thread.CurrentThread.ManagedThreadId) + { + throw new NotSupportedException("Session Create not in MainThread"); + } +#endif + if (IsDisposed) + { + return; + } + + var id = Id; + var networkId = NetworkId; + var channelId = ChannelId; + + foreach (var requestCallback in RequestCallback.Values.ToArray()) + { + requestCallback.SetException(new Exception($"session is dispose: {Id}")); + } + + if (networkId != 0 && channelId != 0) + { + NetworkThread.Instance.SynchronizationContext.Post(() => + { + NetworkThread.Instance?.RemoveChannel(networkId, channelId); + }); + } + + Sessions.Remove(id); +#if NETDEBUG + Log.Debug($"Sessions Dispose Count:{Sessions.Count}"); +#endif + base.Dispose(); + } + + public virtual void Send(object message, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + NetworkThread.Instance.Send(NetworkId, ChannelId, rpcId, 0, routeId, message); + } + + public virtual void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + NetworkThread.Instance.Send(NetworkId, ChannelId, rpcId, routeMessage.RouteTypeOpCode(), routeId, routeMessage); + } + + public virtual void Send(MemoryStream memoryStream, uint rpcId = 0, long routeTypeOpCode = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + NetworkThread.Instance.SendStream(NetworkId, ChannelId, rpcId, routeTypeOpCode, routeId, memoryStream); + } + + public virtual FTask Call(IRequest request, long routeId = 0) + { + if (IsDisposed) + { + return null; + } + + var requestCallback = FTask.Create(); + + unchecked + { + var rpcId = ++_rpcId; + RequestCallback.Add(rpcId, requestCallback); + + if (request is IRouteMessage iRouteMessage) + { + Send(iRouteMessage, rpcId, routeId); + } + else + { + Send(request, rpcId, routeId); + } + } + + return requestCallback; + } + + private void OnReceive(APackInfo packInfo) + { + if (IsDisposed) + { + return; + } + + LastReceiveTime = TimeHelper.Now; + + try + { + NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine(); + } + catch (Exception e) + { + // 如果解æžå¤±è´¥ï¼Œåªæœ‰ä¸€ç§å¯èƒ½ï¼Œé‚£å°±æ˜¯æœ‰äººæ¶æ„å‘包。 + // 所以这里强制关闭了当å‰è¿žæŽ¥ã€‚ä¸è®©å¯¹æ–¹ä¸€ç›´å‘包。 + Dispose(); + Log.Error(e); + } + } + + private void OnChangeChannelId(uint channelId) + { + ChannelId = channelId; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs.meta new file mode 100644 index 00000000..a3109a5e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/Session.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a7d60d383d04c84888fd48783813ed3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Exception.meta b/Assets/GameScripts/DotNet/Core/Network/Exception.meta new file mode 100644 index 00000000..3f58f1e8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Exception.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b62e9f6a86604cf469ab9d9f47bddc5a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs b/Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs new file mode 100644 index 00000000..9a358126 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs @@ -0,0 +1,11 @@ +using System; + +namespace TEngine.Core.Network +{ + public class ScanException : Exception + { + public ScanException() { } + + public ScanException(string msg) : base(msg) { } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs.meta new file mode 100644 index 00000000..505355ca --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Exception/ScanException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8258e475fd71ff040a40766c65bd34a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message.meta b/Assets/GameScripts/DotNet/Core/Network/Message.meta new file mode 100644 index 00000000..d449c5f3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4192fecfffd49194086253fef2b074f7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable.meta new file mode 100644 index 00000000..1ece76c2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ab17a7b38d59bc84aafe2d54a2a6c834 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs new file mode 100644 index 00000000..0a68a031 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs @@ -0,0 +1,104 @@ +#if TENGINE_NET + +namespace TEngine.Core.Network +{ + public static class AddressableHelper + { + private static readonly List AddressableScenes = new List(); + + static AddressableHelper() + { + var sceneConfigInfos = ConfigTableManage.AllSceneConfig(); + + foreach (var sceneConfigInfo in sceneConfigInfos) + { + if (sceneConfigInfo.SceneType == "Addressable") + { + AddressableScenes.Add(sceneConfigInfo); + } + } + } + + public static async FTask AddAddressable(Scene scene, long addressableId, long routeId) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await MessageHelper.CallInnerRoute(scene, addressableScene.EntityId, + new I_AddressableAdd_Request + { + AddressableId = addressableId, RouteId = routeId + }); + + if (response.ErrorCode != 0) + { + Log.Error($"AddAddressable error is {response.ErrorCode}"); + } + } + + public static async FTask GetAddressableRouteId(Scene scene, long addressableId) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + + var response = (I_AddressableGet_Response) await MessageHelper.CallInnerRoute(scene, addressableScene.EntityId, + new I_AddressableGet_Request + { + AddressableId = addressableId + }); + + if (response.ErrorCode == 0) + { + return response.RouteId; + } + + Log.Error($"GetAddressable error is {response.ErrorCode} addressableId:{addressableId}"); + return 0; + } + + public static async FTask RemoveAddressable(Scene scene, long addressableId) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await MessageHelper.CallInnerRoute(scene, addressableScene.EntityId, + new I_AddressableRemove_Request + { + AddressableId = addressableId + }); + + if (response.ErrorCode != 0) + { + Log.Error($"RemoveAddressable error is {response.ErrorCode}"); + } + } + + public static async FTask LockAddressable(Scene scene, long addressableId) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await MessageHelper.CallInnerRoute(scene, addressableScene.EntityId, + new I_AddressableLock_Request + { + AddressableId = addressableId + }); + + if (response.ErrorCode != 0) + { + Log.Error($"LockAddressable error is {response.ErrorCode}"); + } + } + + public static async FTask UnLockAddressable(Scene scene, long addressableId, long routeId, string source) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await MessageHelper.CallInnerRoute(scene, addressableScene.EntityId, + new I_AddressableUnLock_Request + { + AddressableId = addressableId, + RouteId = routeId, + Source = source + }); + + if (response.ErrorCode != 0) + { + Log.Error($"UnLockAddressable error is {response.ErrorCode}"); + } + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs.meta new file mode 100644 index 00000000..9795ecb7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e8589a3e288b52f4e92a04053ba0c6bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs new file mode 100644 index 00000000..24df1693 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace TEngine.Core.Network +{ + public sealed class AddressableManageComponent : Entity + { + private readonly Dictionary _addressable = new(); + private readonly Dictionary _locks = new(); + private readonly CoroutineLockQueueType _addressableLock = new CoroutineLockQueueType("AddressableLock"); + + public async FTask Add(long addressableId, long routeId) + { + using (await _addressableLock.Lock(addressableId)) + { + _addressable[addressableId] = routeId; + Log.Debug($"AddressableManageComponent Add addressableId:{addressableId} routeId:{routeId}"); + } + } + + public async FTask Get(long addressableId) + { + using (await _addressableLock.Lock(addressableId)) + { + _addressable.TryGetValue(addressableId, out var routeId); + return routeId; + } + } + + public async FTask Remove(long addressableId) + { + using (await _addressableLock.Lock(addressableId)) + { + _addressable.Remove(addressableId); + } + } + + public async FTask Lock(long addressableId) + { + var waitCoroutineLock = await _addressableLock.Lock(addressableId); + _locks.Add(addressableId, waitCoroutineLock); + } + + public void UnLock(long addressableId, long routeId, string source) + { + if (!_locks.Remove(addressableId, out var coroutineLock)) + { + Log.Error($"Addressable unlock not found addressableId: {addressableId} Source:{source}"); + return; + } + + _addressable.TryGetValue(addressableId, out var oldAddressableId); + + if (routeId != 0) + { + _addressable[addressableId] = routeId; + } + + coroutineLock.Dispose(); + Log.Debug($"Addressable UnLock key: {addressableId} oldAddressableId : {oldAddressableId} routeId: {routeId} Source:{source}"); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs.meta new file mode 100644 index 00000000..db9a6e28 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableManageComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e946a97e041db3b4f89af34ac9914098 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs new file mode 100644 index 00000000..0cb4bb85 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs @@ -0,0 +1,59 @@ +#if TENGINE_NET +namespace TEngine.Core.Network +{ + /// + /// å¯å¯»å€æ¶ˆæ¯ç»„ä»¶ã€æŒ‚载了这个组件å¯ä»¥æŽ¥æ”¶Addressableæ¶ˆæ¯ + /// + public sealed class AddressableMessageComponent : Entity + { + public long AddressableId; + + public override void Dispose() + { + if (AddressableId != 0) + { + AddressableHelper.RemoveAddressable(Scene, AddressableId).Coroutine(); + AddressableId = 0; + } + + base.Dispose(); + } + + public FTask Register() + { + if (Parent == null) + { + throw new Exception("AddressableRouteComponent must be mounted under a component"); + } + + AddressableId = Parent.Id; + + if (AddressableId == 0) + { + throw new Exception("AddressableRouteComponent.Parent.Id is null"); + } + +#if TENGINE_DEVELOP + Log.Debug($"AddressableMessageComponent Register addressableId:{AddressableId} RouteId:{Parent.RuntimeId}"); +#endif + return AddressableHelper.AddAddressable(Scene, AddressableId, Parent.RuntimeId); + } + + public FTask Lock() + { +#if TENGINE_DEVELOP + Log.Debug($"AddressableMessageComponent Lock {Parent.Id}"); +#endif + return AddressableHelper.LockAddressable(Scene, Parent.Id); + } + + public FTask UnLock(string source) + { +#if TENGINE_DEVELOP + Log.Debug($"AddressableMessageComponent UnLock {Parent.Id} {Parent.RuntimeId}"); +#endif + return AddressableHelper.UnLockAddressable(Scene, Parent.Id, Parent.RuntimeId, source); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs.meta new file mode 100644 index 00000000..6d62194a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableMessageComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be31fb4b0e977f347837b7b6b6b840d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs new file mode 100644 index 00000000..abc0351d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs @@ -0,0 +1,191 @@ +#pragma warning disable CS8603 +#pragma warning disable CS8600 +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class AddressableRouteComponentAwakeSystem : AwakeSystem +{ + protected override void Awake(AddressableRouteComponent self) + { + self.Awake(); + } +} + +/// +/// å¯å¯»å€æ¶ˆæ¯ç»„ä»¶ã€æŒ‚载了这个组件å¯ä»¥æŽ¥æ”¶å’Œå‘é€Addressableæ¶ˆæ¯ +/// +public sealed class AddressableRouteComponent : Entity +{ + private long _parentId; + private long _addressableRouteId; + public static readonly CoroutineLockQueueType AddressableRouteMessageLock = new CoroutineLockQueueType("AddressableRouteMessageLock"); + + public override void Dispose() + { + _parentId = 0; + _addressableRouteId = 0; + base.Dispose(); + } + + public void Awake() + { + if (Parent == null) + { + throw new Exception("AddressableRouteComponent must be mounted under a component"); + } + + if (Parent.RuntimeId == 0) + { + throw new Exception("AddressableRouteComponent.Parent.RuntimeId is null"); + } + + _parentId = Parent.Id; + } + + public void SetAddressableRouteId(long addressableRouteId) + { + _addressableRouteId = addressableRouteId; + } + + public void Send(IAddressableRouteMessage message) + { + Call(message).Coroutine(); + } + + public void Send(long routeTypeOpCode, Type requestType, MemoryStream message) + { + Call(routeTypeOpCode, requestType, message).Coroutine(); + } + + public async FTask Call(long routeTypeOpCode, Type requestType, MemoryStream request) + { + if (IsDisposed) + { + return MessageDispatcherSystem.Instance.CreateResponse(requestType, CoreErrorCode.ErrNotFoundRoute); + } + + var failCount = 0; + var runtimeId = RuntimeId; + IResponse iRouteResponse = null; + + using (await AddressableRouteMessageLock.Lock(_parentId, "AddressableRouteComponent Call MemoryStream")) + { + while (!IsDisposed) + { + if (_addressableRouteId == 0) + { + _addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, _parentId); + } + + if (_addressableRouteId == 0) + { + return MessageDispatcherSystem.Instance.CreateResponse(requestType, CoreErrorCode.ErrNotFoundRoute); + } + + iRouteResponse = await MessageHelper.CallInnerRoute(Scene, _addressableRouteId, routeTypeOpCode, requestType, request); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = CoreErrorCode.ErrRouteTimeout; + } + + switch (iRouteResponse.ErrorCode) + { + case CoreErrorCode.ErrRouteTimeout: + { + return iRouteResponse; + } + case CoreErrorCode.ErrNotFoundRoute: + { + if (++failCount > 20) + { + Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {_addressableRouteId} AddressableRouteComponent:{Id}"); + return iRouteResponse; + } + + await TimerScheduler.Instance.Core.WaitAsync(500); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = CoreErrorCode.ErrRouteTimeout; + } + + _addressableRouteId = 0; + continue; + } + default: + { + return iRouteResponse; + } + } + } + } + + return iRouteResponse; + } + + public async FTask Call(IAddressableRouteMessage request) + { + if (IsDisposed) + { + return MessageDispatcherSystem.Instance.CreateResponse(request, CoreErrorCode.ErrNotFoundRoute); + } + + var failCount = 0; + var runtimeId = RuntimeId; + + using (await AddressableRouteMessageLock.Lock(_parentId,"AddressableRouteComponent Call")) + { + while (true) + { + if (_addressableRouteId == 0) + { + _addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, _parentId); + } + + if (_addressableRouteId == 0) + { + return MessageDispatcherSystem.Instance.CreateResponse(request, CoreErrorCode.ErrNotFoundRoute); + } + + var iRouteResponse = await MessageHelper.CallInnerRoute(Scene, _addressableRouteId, request); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = CoreErrorCode.ErrRouteTimeout; + } + + switch (iRouteResponse.ErrorCode) + { + case CoreErrorCode.ErrNotFoundRoute: + { + if (++failCount > 20) + { + Log.Error($"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {_addressableRouteId} AddressableRouteComponent:{Id}"); + return iRouteResponse; + } + + await TimerScheduler.Instance.Core.WaitAsync(500); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = CoreErrorCode.ErrRouteTimeout; + } + + _addressableRouteId = 0; + continue; + } + case CoreErrorCode.ErrRouteTimeout: + { + return iRouteResponse; + } + default: + { + return iRouteResponse; + } + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs.meta new file mode 100644 index 00000000..329d7751 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/AddressableRouteComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 897c9af3c45fd8e429f8a851eb1b8aab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler.meta new file mode 100644 index 00000000..b02da368 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 09bf59c6e0e10e644aa18a17e8a305e8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs new file mode 100644 index 00000000..2edd4a5d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs @@ -0,0 +1,11 @@ +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class I_AddressableAddHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_AddressableAdd_Request request, I_AddressableAdd_Response response, Action reply) + { + await scene.GetComponent().Add(request.AddressableId, request.RouteId); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs.meta new file mode 100644 index 00000000..223efc26 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableAddHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 974e720fa811f47418b83d5b3ffdd41c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs new file mode 100644 index 00000000..849228b3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs @@ -0,0 +1,11 @@ +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class I_AddressableGetHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_AddressableGet_Request request, I_AddressableGet_Response response, Action reply) + { + response.RouteId = await scene.GetComponent().Get(request.AddressableId); + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs.meta new file mode 100644 index 00000000..7deb2d8c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableGetHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24a968aef62bae54f9fef9bc39b94959 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs new file mode 100644 index 00000000..297a2187 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs @@ -0,0 +1,11 @@ +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class I_AddressableLockHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_AddressableLock_Request request, I_AddressableLock_Response response, Action reply) + { + await scene.GetComponent().Lock(request.AddressableId); + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs.meta new file mode 100644 index 00000000..e6a38cff --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableLockHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95e84c7e22ea5f047a3c524d8472e967 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs new file mode 100644 index 00000000..9cf5b6ae --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs @@ -0,0 +1,11 @@ +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class I_AddressableRemoveHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_AddressableRemove_Request request, I_AddressableRemove_Response response, Action reply) + { + await scene.GetComponent().Remove(request.AddressableId); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs.meta new file mode 100644 index 00000000..70cf5b3a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableRemoveHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 660cdf9f50994c94182e3a64c569da85 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs new file mode 100644 index 00000000..b86c5854 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs @@ -0,0 +1,12 @@ +#if TENGINE_NET +namespace TEngine.Core.Network; + +public sealed class I_AddressableUnLockHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_AddressableUnLock_Request request, I_AddressableUnLock_Response response, Action reply) + { + scene.GetComponent().UnLock(request.AddressableId, request.RouteId, request.Source); + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs.meta new file mode 100644 index 00000000..9d816cf6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Addressable/Handler/I_AddressableUnLockHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c4b465b4daeb9d7498cf33409ee72662 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher.meta new file mode 100644 index 00000000..88457117 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 77c1999fe41f3b743a8df80748ece7e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs new file mode 100644 index 00000000..7ced506a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using TEngine.DataStructure; +using TEngine.Core; +using Type = System.Type; +#pragma warning disable CS8602 +#pragma warning disable CS8600 +#pragma warning disable CS8618 + +// ReSharper disable PossibleNullReferenceException + +namespace TEngine.Core.Network +{ + public sealed class HandlerInfo + { + public T Obj; + public Type Type; + } + + public sealed class MessageDispatcherSystem : Singleton + { + private readonly Dictionary _responseTypes = new Dictionary(); + private readonly DoubleMapDictionary _networkProtocols = new DoubleMapDictionary(); + private readonly Dictionary _messageHandlers = new Dictionary(); + + private readonly OneToManyList _assemblyResponseTypes = new OneToManyList(); + private readonly OneToManyList _assemblyNetworkProtocols = new OneToManyList(); + private readonly OneToManyList> _assemblyMessageHandlers = new OneToManyList>(); + +#if TENGINE_NET + private readonly Dictionary _routeMessageHandlers = new Dictionary(); + private readonly OneToManyList> _assemblyRouteMessageHandlers= new OneToManyList>(); +#endif + private static readonly CoroutineLockQueueType ReceiveRouteMessageLock = new CoroutineLockQueueType("ReceiveRouteMessageLock"); + + protected override void OnLoad(int assemblyName) + { + foreach (var type in AssemblyManager.ForEach(assemblyName, typeof(IMessage))) + { + var obj = (IMessage) Activator.CreateInstance(type); + var opCode = obj.OpCode(); + + _networkProtocols.Add(opCode, type); + + var responseType = type.GetProperty("ResponseType"); + + if (responseType != null) + { + _responseTypes.Add(type, responseType.PropertyType); + _assemblyResponseTypes.Add(assemblyName, type); + } + + _assemblyNetworkProtocols.Add(assemblyName, opCode); + } + + foreach (var type in AssemblyManager.ForEach(assemblyName, typeof(IMessageHandler))) + { + var obj = (IMessageHandler) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var key = obj.Type(); + _messageHandlers.Add(key, obj); + _assemblyMessageHandlers.Add(assemblyName, new HandlerInfo() + { + Obj = obj, Type = key + }); + } +#if TENGINE_NET + foreach (var type in AssemblyManager.ForEach(assemblyName, typeof(IRouteMessageHandler))) + { + var obj = (IRouteMessageHandler) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var key = obj.Type(); + _routeMessageHandlers.Add(key, obj); + _assemblyRouteMessageHandlers.Add(assemblyName, new HandlerInfo() + { + Obj = obj, Type = key + }); + } +#endif + } + + protected override void OnUnLoad(int assemblyName) + { + if (_assemblyResponseTypes.TryGetValue(assemblyName, out var removeResponseTypes)) + { + foreach (var removeResponseType in removeResponseTypes) + { + _responseTypes.Remove(removeResponseType); + } + + _assemblyResponseTypes.RemoveByKey(assemblyName); + } + + if (_assemblyNetworkProtocols.TryGetValue(assemblyName, out var removeNetworkProtocols)) + { + foreach (var removeNetworkProtocol in removeNetworkProtocols) + { + _networkProtocols.RemoveByKey(removeNetworkProtocol); + } + + _assemblyNetworkProtocols.RemoveByKey(assemblyName); + } + + if (_assemblyMessageHandlers.TryGetValue(assemblyName, out var removeMessageHandlers)) + { + foreach (var removeMessageHandler in removeMessageHandlers) + { + _messageHandlers.Remove(removeMessageHandler.Type); + } + + _assemblyMessageHandlers.Remove(assemblyName); + } +#if TENGINE_NET + if (_assemblyRouteMessageHandlers.TryGetValue(assemblyName, out var removeRouteMessageHandlers)) + { + foreach (var removeRouteMessageHandler in removeRouteMessageHandlers) + { + _routeMessageHandlers.Remove(removeRouteMessageHandler.Type); + } + + _assemblyRouteMessageHandlers.Remove(assemblyName); + } +#endif + } + + public void MessageHandler(Session session, Type type, object message, uint rpcId, uint protocolCode) + { + if (!_messageHandlers.TryGetValue(type, out var messageHandler)) + { + Log.Warning($"Scene:{session.Scene.Id} Found Unhandled Message: {message.GetType()}"); + return; + } + + messageHandler.Handle(session, rpcId, protocolCode, message).Coroutine(); + } +#if TENGINE_NET + public async FTask RouteMessageHandler(Session session, Type type, Entity entity, object message, uint rpcId) + { + if (!_routeMessageHandlers.TryGetValue(type, out var routeMessageHandler)) + { + Log.Warning($"Scene:{session.Scene.Id} Found Unhandled RouteMessage: {message.GetType()}"); + + if (message is IRouteRequest request) + { + FailResponse(session, request, CoreErrorCode.Error_NotFindEntity, rpcId); + } + + return; + } + + var runtimeId = entity.RuntimeId; + var sessionRuntimeId = session.RuntimeId; + + if (entity is Scene) + { + // 如果是Sceneçš„è¯ã€å°±ä¸è¦åŠ é”了ã€å¦‚果加é”很一ä¸å°å¿ƒå°±å¯èƒ½ä¼šé€ æˆæ­»é” + await routeMessageHandler.Handle(session, entity, rpcId, message); + return; + } + + using (await ReceiveRouteMessageLock.Lock(runtimeId)) + { + if (sessionRuntimeId != session.RuntimeId) + { + return; + } + + if (runtimeId != entity.RuntimeId) + { + if (message is IRouteRequest request) + { + FailResponse(session, request, CoreErrorCode.Error_NotFindEntity, rpcId); + } + + return; + } + + await routeMessageHandler.Handle(session, entity, rpcId, message); + } + } +#endif + public void FailResponse(Session session, IRouteRequest iRouteRequest, int error, uint rpcId) + { + var response = CreateResponse(iRouteRequest, error); + session.Send(response, rpcId); + } + + public IRouteResponse CreateRouteResponse() + { + return new RouteResponse(); + } + + public IResponse CreateResponse(Type requestType, int error) + { + IResponse response; + + if (_responseTypes.TryGetValue(requestType, out var responseType)) + { + response = (IResponse) Activator.CreateInstance(responseType); + } + else + { + response = new Response(); + } + + response.ErrorCode = error; + return response; + } + + public IResponse CreateResponse(IRequest iRequest, int error) + { + IResponse response; + + if (_responseTypes.TryGetValue(iRequest.GetType(), out var responseType)) + { + response = (IResponse) Activator.CreateInstance(responseType); + } + else + { + response = new Response(); + } + + response.ErrorCode = error; + return response; + } + + public IRouteResponse CreateResponse(IRouteRequest iRouteRequest, int error) + { + IRouteResponse response; + + if (_responseTypes.TryGetValue(iRouteRequest.GetType(), out var responseType)) + { + response = (IRouteResponse) Activator.CreateInstance(responseType); + } + else + { + response = new RouteResponse(); + } + + response.ErrorCode = error; + return response; + } + + public uint GetOpCode(Type type) + { + return _networkProtocols.GetKeyByValue(type); + } + + public Type GetOpCodeType(uint code) + { + return _networkProtocols.GetValueByKey(code); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs.meta new file mode 100644 index 00000000..e4ecc6fd --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/MessageDispatcherSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6c1581e1f459cc4295685cafaa63dd4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs new file mode 100644 index 00000000..02693621 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs @@ -0,0 +1,50 @@ +namespace TEngine.Core.Network +{ + public static class Opcode + { + // å¤–ç½‘æ¶ˆæ¯ + public const uint OuterMessage = 100000000; + public const uint OuterRequest = 110000000; + // å†…ç½‘æ¶ˆæ¯ + public const uint InnerMessage = 120000000; + public const uint InnerRequest = 130000000; + // 内网Bsonæ¶ˆæ¯ + public const uint InnerBsonMessage = 140000000; + public const uint InnerBsonRequest = 150000000; + // å›žå¤æ¶ˆæ¯ + public const uint OuterResponse = 160000000; + public const uint InnerResponse = 170000000; + public const uint InnerBsonResponse = 180000000; + // å¤–ç½‘è·¯ç”±æ¶ˆæ¯ + public const uint OuterRouteMessage = 190000000; + public const uint OuterRouteRequest = 200000000; + // å†…ç½‘è·¯ç”±æ¶ˆæ¯ + public const uint InnerRouteMessage = 210000000; + public const uint InnerRouteRequest = 220000000; + // 内网Bsonè·¯ç”±æ¶ˆæ¯ + public const uint InnerBsonRouteMessage = 230000000; + public const uint InnerBsonRouteRequest = 240000000; + // è·¯ç”±å›žå¤æ¶ˆæ¯ + public const uint OuterRouteResponse = 250000000; + public const uint InnerRouteResponse = 260000000; + public const uint InnerBsonRouteResponse = 270000000; + // å¿ƒè·³æ¶ˆæ¯ + public const uint PingRequest = 1; + public const uint PingResponse = 2; + // é»˜è®¤å›žå¤æ¶ˆæ¯ + public const uint DefaultResponse = 3; + // Addressableå¯å¯»å€æ¶ˆæ¯ + public const uint AddressableAddRequest = InnerRouteRequest + 1; + public const uint AddressableAddResponse = InnerRouteResponse + 1; + public const uint AddressableGetRequest = InnerRouteRequest + 2; + public const uint AddressableGetResponse = InnerRouteResponse + 2; + public const uint AddressableRemoveRequest = InnerRouteRequest + 3; + public const uint AddressableRemoveResponse = InnerRouteResponse + 3; + public const uint AddressableLockRequest = InnerRouteRequest + 4; + public const uint AddressableLockResponse = InnerRouteResponse + 4; + public const uint AddressableUnLockRequest = InnerRouteRequest + 5; + public const uint AddressableUnLockResponse = InnerRouteResponse + 5; + // 默认的Routeè¿”å›žæ¶ˆæ¯ + public const uint DefaultRouteResponse = InnerRouteResponse + 6; + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs.meta new file mode 100644 index 00000000..2d38bdee --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Opcode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b40c482be660ad4090888728af062ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs new file mode 100644 index 00000000..78eb7bc8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs @@ -0,0 +1,6 @@ +using ProtoBuf; + +namespace TEngine.Core.Network +{ + +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs.meta new file mode 100644 index 00000000..0030966e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Dispatcher/Response.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8516b2b12ce77fb488b1b973adc680f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface.meta new file mode 100644 index 00000000..3637cda8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e639a64f385d7164799d03a031c3fabb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler.meta new file mode 100644 index 00000000..c9a7acc9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db5ee92b41263e2478cfb3dbb35e0ccf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs new file mode 100644 index 00000000..8dcb4fd7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs @@ -0,0 +1,87 @@ +// ReSharper disable InconsistentNaming + +using System; + +namespace TEngine.Core.Network +{ + public interface IMessageHandler + { + public Type Type(); + FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message); + } + + public abstract class Message : IMessageHandler + { + public Type Type() + { + return typeof(T); + } + + public async FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message) + { + try + { + await Run(session, (T) message); + } + catch (Exception e) + { + Log.Error(e); + } + } + + protected abstract FTask Run(Session session, T message); + } + + public abstract class MessageRPC : IMessageHandler where TRequest : IRequest where TResponse : IResponse + { + public Type Type() + { + return typeof(TRequest); + } + + public async FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message) + { + if (message is not TRequest request) + { + Log.Error($"消æ¯ç±»åž‹è½¬æ¢é”™è¯¯: {message.GetType().Name} to {typeof(TRequest).Name}"); + return; + } + + var response = Activator.CreateInstance(); + var isReply = false; + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(session, request, response, Reply); + } + catch (Exception e) + { + Log.Error(e); + response.ErrorCode = CoreErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + protected abstract FTask Run(Session session, TRequest request, TResponse response, Action reply); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs.meta new file mode 100644 index 00000000..49aac094 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IMessageHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0e1cb825e8847a479ac69acee8f0bb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs new file mode 100644 index 00000000..17985b44 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs @@ -0,0 +1,222 @@ +#if TENGINE_NET +// ReSharper disable InconsistentNaming + +namespace TEngine.Core.Network +{ + public interface IRouteMessageHandler + { + public Type Type(); + FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage); + } + + public abstract class Route : IRouteMessageHandler where TEntity : Entity where TMessage : IRouteMessage + { + public Type Type() + { + return typeof(TMessage); + } + + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TMessage ruteMessage) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + try + { + await Run(tEntity, ruteMessage); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}"); + } + } + + protected abstract FTask Run(TEntity entity, TMessage message); + } + + public abstract class RouteRPC : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IRouteRequest where TRouteResponse : IRouteResponse + { + public Type Type() + { + return typeof(TRouteRequest); + } + + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TRouteRequest tRouteRequest) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + var isReply = false; + var response = Activator.CreateInstance(); + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(tEntity, tRouteRequest, response, Reply); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}"); + response.ErrorCode = CoreErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + protected abstract FTask Run(TEntity entity, TRouteRequest request, TRouteResponse response, Action reply); + } + + public abstract class Addressable : IRouteMessageHandler where TEntity : Entity where TMessage : IAddressableRouteMessage + { + public Type Type() + { + return typeof(TMessage); + } + + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TMessage ruteMessage) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + try + { + await Run(tEntity, ruteMessage); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}"); + } + finally + { + session.Send(MessageDispatcherSystem.Instance.CreateRouteResponse(), rpcId); + } + } + + protected abstract FTask Run(TEntity entity, TMessage message); + } + + public abstract class AddressableRPC : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IAddressableRouteRequest where TRouteResponse : IAddressableRouteResponse + { + public Type Type() + { + return typeof(TRouteRequest); + } + + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TRouteRequest tRouteRequest) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + var isReply = false; + var response = Activator.CreateInstance(); + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(tEntity, tRouteRequest, response, Reply); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}"); + response.ErrorCode = CoreErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + protected abstract FTask Run(TEntity entity, TRouteRequest request, TRouteResponse response, Action reply); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs.meta new file mode 100644 index 00000000..119addb6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Handler/IRouteMessageHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6de532c5ce565747817a22793fb85c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol.meta new file mode 100644 index 00000000..0a08e51c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4767a374666197f4ab971d74378a5040 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs new file mode 100644 index 00000000..5aab02be --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs @@ -0,0 +1,17 @@ +namespace TEngine.Core.Network +{ + public interface IBsonMessage : IMessage + { + + } + + public interface IBsonRequest : IBsonMessage, IRequest + { + + } + + public interface IBsonResponse : IBsonMessage, IResponse + { + + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs.meta new file mode 100644 index 00000000..0c96d820 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IBsonMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cb1d2907b6893e643b0f5c4644b412d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs new file mode 100644 index 00000000..89da78b9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs @@ -0,0 +1,17 @@ +namespace TEngine.Core.Network +{ + public interface IMessage + { + uint OpCode(); + } + + public interface IRequest : IMessage + { + + } + + public interface IResponse : IMessage + { + int ErrorCode { get; set; } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs.meta new file mode 100644 index 00000000..325caa10 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0828643e6b72684fb0e4917cef34b97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs new file mode 100644 index 00000000..9eb9429d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs @@ -0,0 +1,26 @@ +namespace TEngine.Core.Network +{ + // æ™®é€šè·¯ç”±æ¶ˆæ¯ + public interface IRouteMessage : IRequest + { + long RouteTypeOpCode(); + } + public interface IRouteRequest : IRouteMessage { } + public interface IRouteResponse : IResponse { } + // 普通路由Bsonæ¶ˆæ¯ + public interface IBsonRouteMessage : IBsonMessage, IRouteMessage { } + public interface IBsonRouteRequest : IBsonRouteMessage, IRouteRequest { } + public interface IBsonRouteResponse : IBsonResponse, IRouteResponse { } + // å¯å¯»å€åè®® + public interface IAddressableRouteMessage : IRouteMessage { } + public interface IAddressableRouteRequest : IRouteRequest { } + public interface IAddressableRouteResponse : IRouteResponse { } + // å¯å¯»å€Bsonåè®® + public interface IBsonAddressableRouteMessage : IBsonMessage, IAddressableRouteMessage { } + public interface IBsonAddressableRouteRequest : IBsonRouteMessage, IAddressableRouteRequest { } + public interface IBsonAddressableRouteResponse : IBsonResponse, IAddressableRouteResponse { } + // 自定义Routeåè®® + public interface ICustomRouteMessage : IRouteMessage { } + public interface ICustomRouteRequest : IRouteRequest { } + public interface ICustomRouteResponse : IRouteResponse { } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs.meta new file mode 100644 index 00000000..cd1aa99e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Protocol/IRouteMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d836956c7e220f4d945807e0fd90c1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler.meta new file mode 100644 index 00000000..b25478c1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e8ff64952cfa44645a9410738b63d26b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs new file mode 100644 index 00000000..853313e2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using TEngine.Core; +#pragma warning disable CS8600 + +namespace TEngine.Core.Network +{ + public abstract class ANetworkMessageScheduler + { + private readonly PingResponse _pingResponse = new PingResponse(); + + public async FTask Scheduler(Session session, APackInfo packInfo) + { + Type messageType = null; + var packInfoMemoryStream = packInfo.MemoryStream; + + try + { + if (session.IsDisposed) + { + return; + } + + if (packInfo.ProtocolCode == Opcode.PingRequest) + { + _pingResponse.Now = TimeHelper.Now; + session.Send(_pingResponse, packInfo.RpcId); + return; + } + + messageType = MessageDispatcherSystem.Instance.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"å¯èƒ½é­å—åˆ°æ¶æ„å‘包或没有å议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + switch (packInfo.ProtocolCode) + { + case >= Opcode.OuterRouteMessage: + { + await Handler(session, messageType, packInfo); + return; + } + case < Opcode.OuterResponse: + { + var message = packInfo.Deserialize(messageType); + MessageDispatcherSystem.Instance.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode); + return; + } + default: + { + var aResponse = (IResponse)packInfo.Deserialize(messageType); +#if TENGINE_NET + // æœåС噍之间å‘逿¶ˆæ¯å› ä¸ºèµ°çš„æ˜¯MessageHelperã€æ‰€ä»¥æŽ¥æ”¶æ¶ˆæ¯çš„回调也应该放到MessageHelperé‡Œå¤„ç† + MessageHelper.ResponseHandler(packInfo.RpcId, aResponse); +#else + // 这个一般是客户端Session.Callå‘逿—¶ä½¿ç”¨çš„ã€ç›®å‰è¿™ä¸ªé€»è¾‘åªæœ‰Unity客户端时使用 + + if (!session.RequestCallback.TryGetValue(packInfo.RpcId, out var action)) + { + Log.Error($"not found rpc {packInfo.RpcId}, response message: {aResponse.GetType().Name}"); + return; + } + + session.RequestCallback.Remove(packInfo.RpcId); + action.SetResult(aResponse); +#endif + return; + } + } + } + catch (Exception e) + { + if (packInfoMemoryStream.CanRead) + { + // ReSharper disable once MethodHasAsyncOverload + packInfoMemoryStream.Dispose(); + } + + Log.Error($"NetworkMessageScheduler error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}"); + } + finally + { + packInfo.Dispose(); + } + } + + protected abstract FTask Handler(Session session, Type messageType, APackInfo packInfo); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs.meta new file mode 100644 index 00000000..629d2dc2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b71e62d5efece8a408fc90048164af53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs new file mode 100644 index 00000000..2866f1da --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs @@ -0,0 +1,21 @@ +// using System; +// using System.IO; +// +// namespace TEngine.Core.Network +// { +// public enum NetworkMessageSchedulerHandlerType +// { +// None = 0, +// ClientMessage = 1, +// OuterMessageRoute = 2, +// InnerMessage = 3, +// ServerInnerMessage = 4 +// } +// +// public interface INetworkMessageSchedulerHandler +// { +// NetworkMessageSchedulerHandlerType HandlerType(); +// +// FTask Handler(Session session, Type messageType, APackInfo packInfo); +// } +// } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs.meta new file mode 100644 index 00000000..e66bde52 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/INetworkMessageSchedulerHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5653343ec7befa48a924bed253321ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs b/Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs new file mode 100644 index 00000000..51fda256 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs @@ -0,0 +1,196 @@ +using TEngine.Core; +#pragma warning disable CS8603 + +#if TENGINE_NET +namespace TEngine.Core.Network; + +public static class MessageHelper +{ + private static uint _rpcId; + public const long Timeout = 40000; + public static readonly SortedDictionary RequestCallback = new(); + public static readonly Dictionary TimeoutRouteMessageSenders = new(); + + /// + /// 定时检查过期的Call消æ¯äº‹ä»¶ã€‚ + /// + public struct NetworkMessageUpdate { } + + static MessageHelper() + { + TimerScheduler.Instance.Core.RepeatedTimer(10000, new NetworkMessageUpdate()); + } + + public static void SendInnerServer(Scene scene, uint routeId, IMessage message) + { + scene.Server.GetSession(routeId).Send(message); + } + + public static void SendInnerRoute(Scene scene, long entityId, IRouteMessage message) + { + if (entityId == 0) + { + Log.Error($"SendInnerRoute appId == 0"); + return; + } + + EntityIdStruct entityIdStruct = entityId; + var session = scene.Server.GetSession(entityIdStruct.RouteId); + session.Send(message, 0, entityId); + } + + public static void SendInnerRoute(Scene scene, long entityId, long routeTypeOpCode, MemoryStream message) + { + if (entityId == 0) + { + Log.Error($"SendInnerRoute appId == 0"); + return; + } + + EntityIdStruct entityIdStruct = entityId; + var session = scene.Server.GetSession(entityIdStruct.RouteId); + session.Send(message, 0, routeTypeOpCode, entityId); + } + + public static void SendInnerRoute(Scene scene, ICollection routeIdCollection, IRouteMessage message) + { + if (routeIdCollection.Count <= 0) + { + Log.Error($"SendInnerRoute routeId.Count <= 0"); + return; + } + + foreach (var routeId in routeIdCollection) + { + SendInnerRoute(scene, routeId, message); + } + } + + public static void SendAddressable(Scene scene, long addressableId, IRouteMessage message) + { + CallAddressable(scene, addressableId, message).Coroutine(); + } + + public static async FTask CallInnerRoute(Scene scene, long entityId, long routeTypeOpCode, Type requestType, MemoryStream request) + { + if (entityId == 0) + { + Log.Error($"CallInnerRoute appId == 0"); + return null; + } + + EntityIdStruct entityIdStruct = entityId; + var rpcId = ++_rpcId; + var session = scene.Server.GetSession(entityIdStruct.RouteId); + var requestCallback = FTask.Create(false); + RequestCallback.Add(rpcId, MessageSender.Create(rpcId, requestType, requestCallback)); + session.Send(request, rpcId, routeTypeOpCode, entityId); + return await requestCallback; + } + + public static async FTask CallInnerRoute(Scene scene, long entityId, IRouteMessage request) + { + if (entityId == 0) + { + Log.Error($"CallInnerRoute appId == 0"); + return null; + } + + EntityIdStruct entityIdStruct = entityId; + var rpcId = ++_rpcId; + var session = scene.Server.GetSession(entityIdStruct.RouteId); + var requestCallback = FTask.Create(false); + RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback)); + session.Send(request, rpcId, entityId); + return await requestCallback; + } + + public static async FTask CallInnerServer(Scene scene, uint targetServerId, IRequest request) + { + var rpcId = ++_rpcId; + var session = scene.Server.GetSession(targetServerId); + var requestCallback = FTask.Create(false); + RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback)); + session.Send(request, rpcId); + return await requestCallback; + } + + public static async FTask CallAddressable(Scene scene, long addressableId, IRouteMessage request) + { + var failCount = 0; + + using (await AddressableRouteComponent.AddressableRouteMessageLock.Lock(addressableId,"CallAddressable")) + { + var addressableRouteId = await AddressableHelper.GetAddressableRouteId(scene, addressableId); + + while (true) + { + if (addressableRouteId == 0) + { + addressableRouteId = await AddressableHelper.GetAddressableRouteId(scene, addressableId); + } + + if (addressableRouteId == 0) + { + return MessageDispatcherSystem.Instance.CreateResponse(request, CoreErrorCode.ErrNotFoundRoute); + } + + var iRouteResponse = await MessageHelper.CallInnerRoute(scene, addressableRouteId, request); + + switch (iRouteResponse.ErrorCode) + { + case CoreErrorCode.ErrNotFoundRoute: + { + if (++failCount > 20) + { + Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {addressableRouteId} AddressableMessageComponent:{addressableId}"); + return iRouteResponse; + } + + await TimerScheduler.Instance.Core.WaitAsync(500); + addressableRouteId = 0; + continue; + } + case CoreErrorCode.ErrRouteTimeout: + { + Log.Error($"CallAddressableRoute ErrorCode.ErrRouteTimeout Error:{iRouteResponse.ErrorCode} Message:{request}"); + return iRouteResponse; + } + default: + { + return iRouteResponse; + } + } + } + } + } + + public static void ResponseHandler(uint rpcId, IResponse response) + { + if (!RequestCallback.Remove(rpcId, out var routeMessageSender)) + { + throw new Exception($"not found rpc, response.RpcId:{rpcId} response message: {response.GetType().Name}"); + } + + ResponseHandler(routeMessageSender, response); + } + + private static void ResponseHandler(MessageSender messageSender, IResponse response) + { + if (response.ErrorCode == CoreErrorCode.ErrRouteTimeout) + { +#if TENGINE_DEVELOP + messageSender.Tcs.SetException(new Exception($"Rpc error: request, 注æ„RouteId消æ¯è¶…æ—¶ï¼Œè¯·æ³¨æ„æŸ¥çœ‹æ˜¯å¦æ­»é”或者没有reply: RouteId: {messageSender.RouteId} {messageSender.Request.ToJson()}, response: {response}")); +#else + messageSender.Tcs.SetException(new Exception($"Rpc error: request, 注æ„RouteId消æ¯è¶…æ—¶ï¼Œè¯·æ³¨æ„æŸ¥çœ‹æ˜¯å¦æ­»é”或者没有reply: RouteId: {messageSender.RouteId} {messageSender.Request}, response: {response}")); +#endif + messageSender.Dispose(); + return; + } + + messageSender.Tcs.SetResult(response); + messageSender.Dispose(); + } +} + +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs.meta new file mode 100644 index 00000000..a38d2c99 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/MessageHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2dce1a84fcf96e4a8dfbe49a155660a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs b/Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs new file mode 100644 index 00000000..129cf20b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs @@ -0,0 +1,59 @@ +using System; +using TEngine.Core; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class MessageSender : IDisposable + { + public uint RpcId { get; private set; } + public long RouteId { get; private set; } + public long CreateTime { get; private set; } + public Type MessageType { get; private set; } + public IMessage Request { get; private set; } + public FTask Tcs { get; private set; } + + public void Dispose() + { + RpcId = 0; + RouteId = 0; + CreateTime = 0; + Tcs = null; + Request = null; + MessageType = null; + Pool.Return(this); + } + + public static MessageSender Create(uint rpcId, Type requestType, FTask tcs) + { + var routeMessageSender = Pool.Rent(); + routeMessageSender.Tcs = tcs; + routeMessageSender.RpcId = rpcId; + routeMessageSender.MessageType = requestType; + routeMessageSender.CreateTime = TimeHelper.Now; + return routeMessageSender; + } + + public static MessageSender Create(uint rpcId, IRequest request, FTask tcs) + { + var routeMessageSender = Pool.Rent(); + routeMessageSender.Tcs = tcs; + routeMessageSender.RpcId = rpcId; + routeMessageSender.Request = request; + routeMessageSender.CreateTime = TimeHelper.Now; + return routeMessageSender; + } + + public static MessageSender Create(uint rpcId, long routeId, IRouteMessage request, FTask tcs) + { + var routeMessageSender = Pool.Rent(); + routeMessageSender.Tcs = tcs; + routeMessageSender.RpcId = rpcId; + routeMessageSender.RouteId = routeId; + routeMessageSender.Request = request; + routeMessageSender.CreateTime = TimeHelper.Now; + return routeMessageSender; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs.meta new file mode 100644 index 00000000..0624a9ea --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/MessageSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39b99a8dcf9c7e946a72b1008bbfe1ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs b/Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs new file mode 100644 index 00000000..1fbddb2f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs @@ -0,0 +1,71 @@ +#if TENGINE_NET +using TEngine.Core; + +namespace TEngine.Core.Network; + +public sealed class OnNetworkMessageUpdateCheckTimeout : TimerHandler +{ + public override void Handler(MessageHelper.NetworkMessageUpdate self) + { + var timeNow = TimeHelper.Now; + + foreach (var (rpcId, value) in MessageHelper.RequestCallback) + { + if (timeNow < value.CreateTime + MessageHelper.Timeout) + { + break; + } + + MessageHelper.TimeoutRouteMessageSenders.Add(rpcId, value); + } + + if (MessageHelper.TimeoutRouteMessageSenders.Count == 0) + { + return; + } + + foreach (var (rpcId, routeMessageSender) in MessageHelper.TimeoutRouteMessageSenders) + { + uint responseRpcId = 0; + + try + { + switch (routeMessageSender.Request) + { + case IRouteMessage iRouteMessage: + { + // var routeResponse = RouteMessageDispatcher.CreateResponse(iRouteMessage, ErrorCode.ErrRouteTimeout); + // responseRpcId = routeResponse.RpcId; + // routeResponse.RpcId = routeMessageSender.RpcId; + // MessageHelper.ResponseHandler(routeResponse); + break; + } + case IRequest iRequest: + { + var response = MessageDispatcherSystem.Instance.CreateResponse(iRequest, CoreErrorCode.ErrRpcFail); + responseRpcId = routeMessageSender.RpcId; + MessageHelper.ResponseHandler(responseRpcId, response); + Log.Warning($"timeout rpcId:{rpcId} responseRpcId:{responseRpcId} {iRequest.ToJson()}"); + break; + } + default: + { + Log.Error(routeMessageSender.Request != null + ? $"Unsupported protocol type {routeMessageSender.Request.GetType()} rpcId:{rpcId}" + : $"Unsupported protocol type:{routeMessageSender.MessageType.FullName} rpcId:{rpcId}"); + + MessageHelper.RequestCallback.Remove(rpcId); + break; + } + } + } + catch (Exception e) + { + Log.Error($"responseRpcId:{responseRpcId} routeMessageSender.RpcId:{routeMessageSender.RpcId} {e}"); + } + } + + MessageHelper.TimeoutRouteMessageSenders.Clear(); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs.meta new file mode 100644 index 00000000..6ac0b85f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/OnNetworkMessageUpdateCheckTimeout.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3152b1d220a7acb45b874cf286c4a341 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Protocols.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Protocols.meta new file mode 100644 index 00000000..ebec6188 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Protocols.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1eb9f0783453cf7439a4f31708fde2d6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs new file mode 100644 index 00000000..a95dea82 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs @@ -0,0 +1,160 @@ +using TEngine.Core.Network; +using ProtoBuf; + +namespace TEngine +{ + [ProtoContract] + public sealed class Response : AProto, IResponse + { + public uint OpCode() + { + return Opcode.DefaultResponse; + } + + [ProtoMember(90)] public long RpcId { get; set; } + [ProtoMember(91, IsRequired = true)] public int ErrorCode { get; set; } + } + + [ProtoContract] + public sealed class RouteResponse : AProto, IRouteResponse + { + public uint OpCode() + { + return Opcode.DefaultRouteResponse; + } + + [ProtoMember(90)] public long RpcId { get; set; } + [ProtoMember(91, IsRequired = true)] public int ErrorCode { get; set; } + } + [ProtoContract] + public class PingRequest : AProto, IRequest + { + public uint OpCode() + { + return Opcode.PingRequest; + } + + [ProtoIgnore] public PingResponse ResponseType { get; set; } + [ProtoMember(90)] public long RpcId { get; set; } + } + + public class PingResponse : AProto, IResponse + { + public uint OpCode() + { + return Opcode.PingResponse; + } + + [ProtoMember(90)] public long RpcId { get; set; } + [ProtoMember(91, IsRequired = true)] public int ErrorCode { get; set; } + [ProtoMember(1)] public long Now; + } + /// + /// 添加一个å¯å¯»å€åœ°å€ + /// + [ProtoContract] + public partial class I_AddressableAdd_Request : AProto, IRouteRequest + { + [ProtoIgnore] + public I_AddressableAdd_Response ResponseType { get; set; } + public uint OpCode() { return Opcode.AddressableAddRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + [ProtoMember(2)] + public long RouteId { get; set; } + } + [ProtoContract] + public partial class I_AddressableAdd_Response : AProto, IRouteResponse + { + public uint OpCode() { return Opcode.AddressableAddResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + } + /// + /// 查询一个å¯å¯»å€ + /// + [ProtoContract] + public partial class I_AddressableGet_Request : AProto, IRouteRequest + { + [ProtoIgnore] + public I_AddressableGet_Response ResponseType { get; set; } + public uint OpCode() { return Opcode.AddressableGetRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + } + [ProtoContract] + public partial class I_AddressableGet_Response : AProto, IRouteResponse + { + public uint OpCode() { return Opcode.AddressableGetResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + [ProtoMember(1)] + public long RouteId { get; set; } + } + /// + /// 删除一个å¯å¯»å€ + /// + [ProtoContract] + public partial class I_AddressableRemove_Request : AProto, IRouteRequest + { + [ProtoIgnore] + public I_AddressableRemove_Response ResponseType { get; set; } + public uint OpCode() { return Opcode.AddressableRemoveRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + } + [ProtoContract] + public partial class I_AddressableRemove_Response : AProto, IRouteResponse + { + public uint OpCode() { return Opcode.AddressableRemoveResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + } + /// + /// é”定一个å¯å¯»å€ + /// + [ProtoContract] + public partial class I_AddressableLock_Request : AProto, IRouteRequest + { + [ProtoIgnore] + public I_AddressableLock_Response ResponseType { get; set; } + public uint OpCode() { return Opcode.AddressableLockRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + } + [ProtoContract] + public partial class I_AddressableLock_Response : AProto, IRouteResponse + { + public uint OpCode() { return Opcode.AddressableLockResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + } + /// + /// è§£é”一个å¯å¯»å€ + /// + [ProtoContract] + public partial class I_AddressableUnLock_Request : AProto, IRouteRequest + { + [ProtoIgnore] + public I_AddressableUnLock_Response ResponseType { get; set; } + public uint OpCode() { return Opcode.AddressableUnLockRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + [ProtoMember(2)] + public long RouteId { get; set; } + [ProtoMember(3)] + public string Source { get; set; } + } + [ProtoContract] + public partial class I_AddressableUnLock_Response : AProto, IRouteResponse + { + public uint OpCode() { return Opcode.AddressableUnLockResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs.meta new file mode 100644 index 00000000..fe28cecc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Protocols/CoreMessageProtocols.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6c821a26b3bfad4e9e240a15cf3a745 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Route.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Route.meta new file mode 100644 index 00000000..7dbb1653 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Route.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8099ddc29c0dd8748ba72a0c621a9df9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs new file mode 100644 index 00000000..0dc9fe91 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs @@ -0,0 +1,37 @@ +#if TENGINE_NET +namespace TEngine.Core.Network; + +/// +/// 自定义Route组件ã€å¦‚æžœè¦è‡ªå®šä¹‰Routeå议必须使用这个组件 +/// +public sealed class RouteComponent : Entity +{ + private readonly Dictionary _routeAddress = new Dictionary(); + + public void AddAddress(long routeType, long routeId) + { + _routeAddress.Add(routeType, routeId); + } + + public void RemoveAddress(long routeType) + { + _routeAddress.Remove(routeType); + } + + public long GetRouteId(long routeType) + { + return _routeAddress.TryGetValue(routeType, out var routeId) ? routeId : 0; + } + + public bool TryGetRouteId(long routeType, out long routeId) + { + return _routeAddress.TryGetValue(routeType, out routeId); + } + + public override void Dispose() + { + _routeAddress.Clear(); + base.Dispose(); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs.meta new file mode 100644 index 00000000..34d9eaba --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Route/RouteComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef3b01b95662a0e47abe0576ff53efc5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler.meta new file mode 100644 index 00000000..b9e14c3e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 756c1c61f6e131d4aaf2f05901bc343a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs new file mode 100644 index 00000000..c8e881b5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs @@ -0,0 +1,69 @@ +using System; + +namespace TEngine.Core.Network +{ +#if TENGINE_UNITY + public sealed class ClientMessageScheduler : ANetworkMessageScheduler + { + protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo) + { + var packInfoMemoryStream = packInfo.MemoryStream; + + try + { + switch (packInfo.ProtocolCode) + { + case > Opcode.InnerRouteResponse: + { + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + case > Opcode.OuterRouteResponse: + { + // 这个一般是客户端Session.Callå‘逿—¶ä½¿ç”¨çš„ã€ç›®å‰è¿™ä¸ªé€»è¾‘åªæœ‰Unity客户端时使用 + var aResponse = (IResponse)packInfo.Deserialize(messageType); + + if (!session.RequestCallback.TryGetValue(packInfo.RpcId, out var action)) + { + Log.Error($"not found rpc {packInfo.RpcId}, response message: {aResponse.GetType().Name}"); + return; + } + + session.RequestCallback.Remove(packInfo.RpcId); + action.SetResult(aResponse); + return; + } + case < Opcode.OuterRouteRequest: + { + var message = packInfo.Deserialize(messageType); + MessageDispatcherSystem.Instance.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode); + return; + } + } + } + catch (Exception e) + { + if (packInfoMemoryStream.CanRead) + { + // ReSharper disable once MethodHasAsyncOverload + packInfoMemoryStream.Dispose(); + } + + Log.Error(e); + return; + } + + await FTask.CompletedTask; + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + } +#endif +#if TENGINE_NET + public sealed class ClientMessageScheduler : ANetworkMessageScheduler + { + protected override FTask Handler(Session session, Type messageType, APackInfo packInfo) + { + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + } +#endif +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs.meta new file mode 100644 index 00000000..d86b2bba --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 14525ca558e639341992505398c7aae5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs new file mode 100644 index 00000000..28be4b76 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs @@ -0,0 +1,22 @@ +namespace TEngine.Core.Network +{ + public class CoreRouteType + { + /// + /// 基础Routeåè®®ã€æ¡†æž¶å†…ç½®åƒä¸‡ä¸è¦åˆ é™¤ + /// + public const long Route = 1; + /// + /// 基础BsonRouteåè®®ã€æ¡†æž¶å†…ç½®åƒä¸‡ä¸è¦åˆ é™¤ + /// + public const long BsonRoute = 2; + /// + /// 基础Addressableåè®®ã€æ¡†æž¶å†…ç½®åƒä¸‡ä¸è¦åˆ é™¤ + /// + public const long Addressable = 3; + /// + /// 自定义RouteTypeã€æ¡†æž¶å†…ç½®åƒä¸‡ä¸è¦åˆ é™¤ + /// + public const long CustomRouteType = 1000; + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs.meta new file mode 100644 index 00000000..80280bc6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/CoreRouteType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0061d503cf7e47544addd8730096469d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs new file mode 100644 index 00000000..2e9b2695 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs @@ -0,0 +1,115 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#if TENGINE_NET + +namespace TEngine.Core.Network +{ + public sealed class InnerMessageScheduler : ANetworkMessageScheduler + { + protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo) + { + var disposeMemoryStream = true; + var packInfoMemoryStream = packInfo.MemoryStream; + + try + { + switch (packInfo.ProtocolCode) + { + case >= Opcode.InnerBsonRouteResponse: + case >= Opcode.InnerRouteResponse: + { + var response = (IRouteResponse)packInfo.Deserialize(messageType); + MessageHelper.ResponseHandler(packInfo.RpcId, response); + return; + } + case >= Opcode.OuterRouteResponse: + { + // 如果GateæœåС噍ã€éœ€è¦è½¬å‘Addressableåè®®ã€æ‰€ä»¥è¿™é‡Œæœ‰å¯èƒ½ä¼šæŽ¥æ”¶åˆ°è¯¥ç±»åž‹åè®® + var aResponse = (IResponse)packInfo.Deserialize(messageType); + MessageHelper.ResponseHandler(packInfo.RpcId, aResponse); + return; + } + case > Opcode.InnerBsonRouteMessage: + { + var obj = packInfo.Deserialize(messageType); + var entity = Entity.GetEntity(packInfo.RouteId); + + if (entity == null) + { + if (packInfo.ProtocolCode > Opcode.InnerBsonRouteRequest) + { + MessageDispatcherSystem.Instance.FailResponse(session, (IRouteRequest)obj, CoreErrorCode.ErrNotFoundRoute, packInfo.RpcId); + } + + return; + } + + await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, obj, packInfo.RpcId); + return; + } + case > Opcode.InnerRouteMessage: + { + var obj = packInfo.Deserialize(messageType); + var entity = Entity.GetEntity(packInfo.RouteId); + + if (entity == null) + { + if (packInfo.ProtocolCode > Opcode.InnerRouteRequest) + { + MessageDispatcherSystem.Instance.FailResponse(session, (IRouteRequest)obj, CoreErrorCode.ErrNotFoundRoute, packInfo.RpcId); + } + + return; + } + + await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, obj, packInfo.RpcId); + return; + } + case > Opcode.OuterRouteMessage: + { + var entity = Entity.GetEntity(packInfo.RouteId); + + switch (entity) + { + case null: + { + var obj = packInfo.Deserialize(messageType); + var response = MessageDispatcherSystem.Instance.CreateResponse((IRouteMessage)obj, CoreErrorCode.ErrNotFoundRoute); + session.Send(response, packInfo.RpcId, packInfo.RouteId); + return; + } + case Session gateSession: + { + // 这里如果是Sessionåªå¯èƒ½æ˜¯Gateçš„Sessionã€å¦‚果是的è¯ã€è‚¯å®šæ˜¯è½¬å‘Addressæ¶ˆæ¯ + disposeMemoryStream = false; + gateSession.Send(packInfoMemoryStream, packInfo.RpcId); + return; + } + default: + { + var obj = packInfo.Deserialize(messageType); + await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, obj, packInfo.RpcId); + return; + } + } + } + default: + { + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + } + } + catch (Exception e) + { + if (disposeMemoryStream && packInfoMemoryStream.CanRead) + { + // ReSharper disable once MethodHasAsyncOverload + packInfoMemoryStream.Dispose(); + } + + Log.Error($"InnerMessageSchedulerHandler error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} {e}"); + } + } + } +} +#endif + diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs.meta new file mode 100644 index 00000000..e5ec78c8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dcfa2c899967c7f45bd265785358ad93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs new file mode 100644 index 00000000..78b9d120 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs @@ -0,0 +1,129 @@ +using System; +using TEngine.IO; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +namespace TEngine.Core.Network +{ +#if TENGINE_UNITY + public sealed class OuterMessageScheduler : ANetworkMessageScheduler + { + protected override FTask Handler(Session session, Type messageType, APackInfo packInfo) + { + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + } +#endif +#if TENGINE_NET + public sealed class OuterMessageScheduler : ANetworkMessageScheduler + { + protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo) + { + if (packInfo.ProtocolCode >= Opcode.InnerRouteMessage) + { + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + + var packInfoMemoryStream = packInfo.MemoryStream; + + try + { + switch (packInfo.RouteTypeCode) + { + case CoreRouteType.Route: + case CoreRouteType.BsonRoute: + { + break; + } + case CoreRouteType.Addressable: + { + var addressableRouteComponent = session.GetComponent(); + + if (addressableRouteComponent == null) + { + Log.Error("Session does not have an AddressableRouteComponent component"); + return; + } + + switch (packInfo.ProtocolCode) + { + case > Opcode.OuterRouteRequest: + { + var runtimeId = session.RuntimeId; + var response = await addressableRouteComponent.Call(packInfo.RouteTypeCode, messageType, packInfoMemoryStream); + + // sessionå¯èƒ½å·²ç»æ–­å¼€äº†ï¼Œæ‰€ä»¥è¿™é‡Œéœ€è¦åˆ¤æ–­ + + if (session.RuntimeId == runtimeId) + { + session.Send(response, packInfo.RpcId); + } + + return; + } + case > Opcode.OuterRouteMessage: + { + addressableRouteComponent.Send(packInfo.RouteTypeCode, messageType, packInfoMemoryStream); + return; + } + } + + return; + } + case > CoreRouteType.CustomRouteType: + { + var routeComponent = session.GetComponent(); + + if (routeComponent == null) + { + Log.Error("Session does not have an routeComponent component"); + return; + } + + if (!routeComponent.TryGetRouteId(packInfo.RouteTypeCode, out var routeId)) + { + Log.Error($"RouteComponent cannot find RouteId with RouteTypeCode {packInfo.RouteTypeCode}"); + return; + } + + switch (packInfo.ProtocolCode) + { + case > Opcode.OuterRouteRequest: + { + var runtimeId = session.RuntimeId; + var response = await MessageHelper.CallInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, messageType, packInfoMemoryStream); + // sessionå¯èƒ½å·²ç»æ–­å¼€äº†ï¼Œæ‰€ä»¥è¿™é‡Œéœ€è¦åˆ¤æ–­ + if (session.RuntimeId == runtimeId) + { + session.Send(response, packInfo.RpcId); + } + + return; + } + case > Opcode.OuterRouteMessage: + { + MessageHelper.SendInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, packInfoMemoryStream); + return; + } + } + + return; + } + } + } + catch (Exception e) + { + if (packInfoMemoryStream.CanRead) + { + // ReSharper disable once MethodHasAsyncOverload + packInfoMemoryStream.Dispose(); + } + + Log.Error(e); + return; + } + + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + } + } +#endif +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs.meta b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs.meta new file mode 100644 index 00000000..224b0d07 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4433fa0a9cf819a4593784711b023154 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol.meta new file mode 100644 index 00000000..f5c0bcb2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f3117a3ccddcf1a41b63d24c87945e51 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP.meta new file mode 100644 index 00000000..9a4763d2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d4432f2a80d63c44f9b38176ebd993f2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base.meta new file mode 100644 index 00000000..911531f1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 822cde18961b6d54db72134259341fac +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs new file mode 100644 index 00000000..10837be4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs @@ -0,0 +1,218 @@ +using System; +using System.Runtime.InteropServices; + +namespace TEngine.Core.Network +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int KcpOutput(IntPtr buf, int len, IntPtr kcp, IntPtr user); + + public static class KCP + { +#if UNITY_IPHONE && !UNITY_EDITOR + const string KcpDll = "__Internal"; +#else + const string KcpDll = "kcp"; +#endif + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern uint ikcp_check(IntPtr kcp, uint current); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr ikcp_create(uint conv, IntPtr user); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern void ikcp_flush(IntPtr kcp); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern uint ikcp_getconv(IntPtr ptr); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_input(IntPtr kcp, byte[] data, int offset, int size); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_nodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_peeksize(IntPtr kcp); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_recv(IntPtr kcp, byte[] buffer, int len); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern void ikcp_release(IntPtr kcp); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_send(IntPtr kcp, byte[] buffer, int len); + + [DllImport(KcpDll, CallingConvention=CallingConvention.Cdecl)] + private static extern void ikcp_setminrto(IntPtr ptr, int minrto); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_setmtu(IntPtr kcp, int mtu); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern void ikcp_setoutput(IntPtr kcp, KcpOutput output); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern void ikcp_update(IntPtr kcp, uint current); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_waitsnd(IntPtr kcp); + + [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int ikcp_wndsize(IntPtr kcp, int sndwnd, int rcvwnd); + + public static uint KcpCheck(IntPtr kcp, uint current) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_check(kcp, current); + } + + public static IntPtr KcpCreate(uint conv, IntPtr user) + { + return ikcp_create(conv, user); + } + + public static void KcpFlush(IntPtr kcp) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + ikcp_flush(kcp); + } + + public static uint KcpGetconv(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_getconv(ptr); + } + + public static int KcpInput(IntPtr kcp, byte[] data, int offset, int size) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_input(kcp, data, offset, size); + } + + public static int KcpNodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_nodelay(kcp, nodelay, interval, resend, nc); + } + + public static int KcpPeeksize(IntPtr kcp) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_peeksize(kcp); + } + + public static int KcpRecv(IntPtr kcp, byte[] buffer, int len) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_recv(kcp, buffer, len); + } + + public static void KcpRelease(IntPtr kcp) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + ikcp_release(kcp); + } + + public static int KcpSend(IntPtr kcp, byte[] buffer, int len) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_send(kcp, buffer, len); + } + + public static void KcpSetminrto(IntPtr kcp, int minrto) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + ikcp_setminrto(kcp, minrto); + } + + public static int KcpSetmtu(IntPtr kcp, int mtu) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_setmtu(kcp, mtu); + } + + public static void KcpSetoutput(IntPtr kcp, KcpOutput output) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + ikcp_setoutput(kcp, output); + } + + public static void KcpUpdate(IntPtr kcp, uint current) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + ikcp_update(kcp, current); + } + + public static int KcpWaitsnd(IntPtr kcp) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_waitsnd(kcp); + } + + public static int KcpWndsize(IntPtr kcp, int sndwnd, int rcvwnd) + { + if (kcp == IntPtr.Zero) + { + throw new Exception($"kcp error, kcp point is zero"); + } + + return ikcp_wndsize(kcp, sndwnd, rcvwnd); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta new file mode 100644 index 00000000..593d8d6e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb202d4e9e36ea846861fa8378dd1c23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs new file mode 100644 index 00000000..72bd966b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs @@ -0,0 +1,51 @@ +using System; + +namespace TEngine.Core.Network +{ + public class KCPSettings + { + public int Mtu { get; private set; } + public int SendWindowSize { get; private set; } + public int ReceiveWindowSize { get; private set; } + public int MaxSendWindowSize { get; private set; } + + public static KCPSettings Create(NetworkTarget networkTarget) + { + var settings = new KCPSettings(); + + switch (networkTarget) + { + case NetworkTarget.Outer: + { + // 外网设置470的原因: + // 1ã€mtu设置过大有å¯èƒ½è·¯ç”±å™¨è¿‡æ»¤æŽ‰ + // 2ã€é™ä½Ž mtu 到 470ï¼ŒåŒæ ·æ•°æ®è™½ç„¶ä¼šå‘更多的包,但是å°åŒ…在路由层优先级更高 + settings.Mtu = 470; + settings.SendWindowSize = 256; + settings.ReceiveWindowSize = 256; + settings.MaxSendWindowSize = 256 * 2; + break; + } + case NetworkTarget.Inner: + { + // 内网设置1400的原因 + // 1ã€ä¸€èˆ¬éƒ½æ˜¯åŒä¸€å°æœåС噍æ¥è¿è¡Œå¤šä¸ªè¿›ç¨‹æ¥å¤„ç† + // 2ã€å†…网æ¯ä¸ªè¿›ç¨‹è·Ÿå…¶ä»–è¿›ç¨‹åªæœ‰ä¸€ä¸ªé€šé“进行å‘é€ã€æ‰€ä»¥å‘é€çš„æ•°é‡ä¼šæ¯”较大 + // 3ã€å¦‚æžœä¸æŠŠçª—å£è®¾ç½®å¤§ç‚¹ã€ä¼šå‡ºçŽ°æ¶ˆæ¯æ»žåŽã€‚ + // 4ã€å› ä¸ºå†…网å‘é€çš„å¯ä¸åªæ˜¯å¤–ç½‘è½¬å‘æ•°æ®ã€è¿˜æœ‰å¯èƒ½æ˜¯å…¶ä»–进程的通讯 + settings.Mtu = 1400; + settings.SendWindowSize = 1024; + settings.ReceiveWindowSize = 1024; + settings.MaxSendWindowSize = 1024 * 2; + break; + } + default: + { + throw new NotSupportedException($"KCPServerNetwork NotSupported NetworkType:{networkTarget}"); + } + } + + return settings; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs.meta new file mode 100644 index 00000000..ff2942c1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 756afce4e7fb29b4fab97d429a0aea90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs new file mode 100644 index 00000000..9989d5ab --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs @@ -0,0 +1,15 @@ +namespace TEngine.Core.Network +{ + public enum KcpHeader : byte + { + None = 0x00, + RequestConnection = 0x01, + WaitConfirmConnection = 0x02, + ConfirmConnection = 0x03, + // InnerHandshake = 0x03, + RepeatChannelId = 0x04, + // ConfirmHandshake = 0x05, + ReceiveData = 0x06, + Disconnect = 0x07 + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs.meta new file mode 100644 index 00000000..a276ad44 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpHeader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e5f946dde2d78f4b9e3425a3ad647b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs new file mode 100644 index 00000000..e58e56f8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs @@ -0,0 +1,14 @@ +namespace TEngine +{ + public static class KcpProtocalType + { + public const byte SYN = 1; + public const byte ACK = 2; + public const byte FIN = 3; + public const byte MSG = 4; + public const byte RouterReconnectSYN = 5; + public const byte RouterReconnectACK = 6; + public const byte RouterSYN = 7; + public const byte RouterACK = 8; + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta new file mode 100644 index 00000000..f01e12a0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9bd29f721d3e458d88d43160fd7cca55 +timeCreated: 1689230822 \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client.meta new file mode 100644 index 00000000..ea58fcb9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1473c7e4a4d90be48be9a6350645b8d0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs new file mode 100644 index 00000000..6c057661 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs @@ -0,0 +1,622 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using TEngine.Core; +// ReSharper disable PossibleNullReferenceException +// ReSharper disable InconsistentNaming +#pragma warning disable CS8602 +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class KCPClientNetwork : AClientNetwork, INetworkUpdate + { + #region 逻辑线程 + + private bool _isInit; + private Action _onConnectFail; + private Action _onConnectComplete; + private long _connectTimeoutId; + public override event Action OnDispose; + public override event Action OnChangeChannelId; + public override event Action OnReceiveMemoryStream; + + public KCPClientNetwork(Scene scene, NetworkTarget networkTarget) : base(scene, NetworkType.Client, NetworkProtocolType.KCP, networkTarget) + { + _startTime = TimeHelper.Now; + NetworkThread.Instance.AddNetwork(this); + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + IsDisposed = true; + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + if (!_isDisconnect) + { + SendHeader(KcpHeader.Disconnect); + } + + if (_socket.Connected) + { + _socket.Disconnect(false); + _socket.Close(); + } + + _maxSndWnd = 0; + _updateMinTime = 0; + + _sendAction = null; + _onConnectFail = null; + _rawSendBuffer = null; + _rawReceiveBuffer = null; + + _packetParser?.Dispose(); + _receiveMemoryStream?.Dispose(); + + ClearConnectTimeout(ref _connectTimeoutId); + + if (_messageCache != null) + { + _messageCache.Clear(); + _messageCache = null; + } + + if (_kcpIntPtr != IntPtr.Zero) + { + KCP.KcpRelease(_kcpIntPtr); + ConnectionPtrChannel.Remove(_kcpIntPtr); + _kcpIntPtr = IntPtr.Zero; + } +#if NETDEBUG + Log.Debug($"KCPClientNetwork ConnectionPtrChannel:{ConnectionPtrChannel.Count}"); +#endif + _updateTimer.Clear(); + _updateTimeOutTime.Clear(); + ThreadSynchronizationContext.Main.Post(OnDispose); + base.Dispose(); + }); + } + + public override uint Connect(IPEndPoint remoteEndPoint, Action onConnectComplete, Action onConnectFail, int connectTimeout = 5000) + { + if (_isInit) + { + throw new NotSupportedException($"KCPClientNetwork Id:{Id} Has already been initialized. If you want to call Connect again, please re instantiate it."); + } + + _isInit = true; + _onConnectFail = onConnectFail; + _onConnectComplete = onConnectComplete; + ChannelId = CreateChannelId(); + _kcpSettings = KCPSettings.Create(NetworkTarget); + _maxSndWnd = _kcpSettings.MaxSendWindowSize; + _messageCache = new Queue(); + _rawReceiveBuffer = new byte[_kcpSettings.Mtu + 5]; + + _sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) => + { + _messageCache.Enqueue(new MessageCacheInfo() + { + RpcId = rpcId, + RouteId = routeId, + RouteTypeOpCode = routeTypeOpCode, + Message = message, + MemoryStream = memoryStream + }); + }; + + _connectTimeoutId = TimerScheduler.Instance.Core.OnceTimer(connectTimeout, () => + { + if (_onConnectFail == null) + { + return; + } + _onConnectFail(); + Dispose(); + }); + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + _socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + _socket.SetSocketBufferToOsLimit(); + NetworkHelper.SetSioUdpConnReset(_socket); + _socket.Connect(remoteEndPoint); + SendHeader(KcpHeader.RequestConnection); + }); + + return ChannelId; + } + + #endregion + + #region 网络主线程 + + private Socket _socket; + private int _maxSndWnd; + private IntPtr _kcpIntPtr; + private bool _isDisconnect; + private long _updateMinTime; + private byte[] _rawSendBuffer; + private readonly long _startTime; + private byte[] _rawReceiveBuffer; + private KCPSettings _kcpSettings; + private APacketParser _packetParser; + private MemoryStream _receiveMemoryStream; + private Queue _messageCache; + private Action _sendAction; + + private readonly Queue _updateTimeOutTime = new Queue(); + private EndPoint _clientEndPoint = new IPEndPoint(IPAddress.Any, 0); + private readonly SortedDictionary _updateTimer = new SortedDictionary(); + private static readonly Dictionary ConnectionPtrChannel = new Dictionary(); + + private uint TimeNow => (uint) (TimeHelper.Now - _startTime); + + private void Receive() + { + while (_socket != null && _socket.Poll(0, SelectMode.SelectRead)) + { + try + { + var receiveLength = _socket.ReceiveFrom(_rawReceiveBuffer, ref _clientEndPoint); + + if (receiveLength > _rawReceiveBuffer.Length) + { + Log.Error($"KCP ClientConnection: message of size {receiveLength} does not fit into buffer of size {_rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); + Dispose(); + return; + } + + var header = (KcpHeader) _rawReceiveBuffer[0]; + var channelId = BitConverter.ToUInt32(_rawReceiveBuffer, 1); + + switch (header) + { + case KcpHeader.RepeatChannelId: + { + if (receiveLength != 5 || channelId != ChannelId) + { + break; + } + + // 到这里是客户端的channelId冿œåŠ¡å™¨ä¸Šå·²ç»å­˜åœ¨ã€éœ€è¦é‡æ–°ç”Ÿæˆä¸€ä¸ªå†æ¬¡å°è¯•连接 + ChannelId = CreateChannelId(); + SendHeader(KcpHeader.RequestConnection); + // 这里è¦å¤„ç†å…¥å¦‚果有å‘é€çš„æ¶ˆæ¯çš„问题ã€åŽé¢å¤„ç† + break; + } + case KcpHeader.WaitConfirmConnection: + { + if (receiveLength != 5 || channelId != ChannelId) + { + break; + } + + SendHeader(KcpHeader.ConfirmConnection); + ClearConnectTimeout(ref _connectTimeoutId); + // 创建KCP和相关的åˆå§‹åŒ– + _kcpIntPtr = KCP.KcpCreate(channelId, new IntPtr(channelId)); + KCP.KcpNodelay(_kcpIntPtr, 1, 5, 2, 1); + KCP.KcpWndsize(_kcpIntPtr, _kcpSettings.SendWindowSize, _kcpSettings.ReceiveWindowSize); + KCP.KcpSetmtu(_kcpIntPtr, _kcpSettings.Mtu); + KCP.KcpSetminrto(_kcpIntPtr, 30); + KCP.KcpSetoutput(_kcpIntPtr, KcpOutput); + _rawSendBuffer = new byte[ushort.MaxValue]; + _receiveMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + _packetParser = APacketParser.CreatePacketParser(NetworkTarget); + + // 把缓存的消æ¯å…¨éƒ¨å‘é€ç»™æœåС噍 + + _sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) => + { + if (IsDisposed) + { + return; + } + + memoryStream = Pack(rpcId, routeTypeOpCode, routeId, memoryStream, message); + Send(memoryStream); + }; + + while (_messageCache.TryDequeue(out var messageCache)) + { + _sendAction( + messageCache.RpcId, + messageCache.RouteTypeOpCode, + messageCache.RouteId, + messageCache.MemoryStream, + messageCache.Message); + } + + _messageCache.Clear(); + _messageCache = null; + ConnectionPtrChannel.Add(_kcpIntPtr, this); + // 调用ChannelId改å˜äº‹ä»¶ã€å°±ç®—没有改å˜ä¹Ÿè¦å‘ä¸‹ã€æŽ¥æ”¶äº‹ä»¶çš„åœ°æ–¹ä¼šåˆ¤å®šä¸‹ + ThreadSynchronizationContext.Main.Post(() => + { + OnChangeChannelId(ChannelId); + _onConnectComplete?.Invoke(); + }); + // 到这里正确创建上连接了ã€å¯ä»¥æ­£å¸¸å‘逿¶ˆæ¯äº† + break; + } + case KcpHeader.ReceiveData: + { + var messageLength = receiveLength - 5; + + if (messageLength <= 0) + { + Log.Warning($"KCPClient KcpHeader.Data messageLength <= 0"); + break; + } + + if (channelId != ChannelId) + { + break; + } + + KCP.KcpInput(_kcpIntPtr, _rawReceiveBuffer, 5, messageLength); + AddToUpdate(0); + KcpReceive(); + break; + } + case KcpHeader.Disconnect: + { + if (channelId != ChannelId) + { + break; + } + + _isDisconnect = true; + Dispose(); + break; + } + } + } + // this is fine, the socket might have been closed in the other end + catch (SocketException) { } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private void Send(MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + // 检查等待å‘é€çš„æ¶ˆæ¯ï¼Œå¦‚果超出两å€çª—å£å¤§å°ï¼ŒKCPä½œè€…ç»™çš„å»ºè®®æ˜¯è¦æ–­å¼€è¿žæŽ¥ + + var waitSendSize = KCP.KcpWaitsnd(_kcpIntPtr); + + if (waitSendSize > _maxSndWnd) + { + Log.Warning($"ERR_KcpWaitSendSizeTooLarge {waitSendSize} > {_maxSndWnd}"); + Dispose(); + return; + } + + // å‘逿¶ˆæ¯ + + KCP.KcpSend(_kcpIntPtr, memoryStream.GetBuffer(), (int) memoryStream.Length); + + // 因为memoryStream对象池出æ¥çš„ã€æ‰€ä»¥éœ€è¦æ‰‹åŠ¨å›žæ”¶ä¸‹ + + memoryStream.Dispose(); + + AddToUpdate(0); + } + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long entityId, MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + _sendAction(rpcId, routeTypeOpCode, entityId, memoryStream, null); + } + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long entityId, object message) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + _sendAction(rpcId, routeTypeOpCode, entityId, null, message); + } + + private void Output(IntPtr bytes, int count) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed || _kcpIntPtr == IntPtr.Zero) + { + return; + } + + try + { + if (count == 0) + { + throw new Exception("KcpOutput count 0"); + } + + _rawSendBuffer.WriteTo(0, (byte) KcpHeader.ReceiveData); + _rawSendBuffer.WriteTo(1, ChannelId); + Marshal.Copy(bytes, _rawSendBuffer, 5, count); + _socket.Send(_rawSendBuffer, 0, count + 5, SocketFlags.None); + } + catch (Exception e) + { + Log.Error(e); + } + } + + private void KcpReceive() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed || _kcpIntPtr == IntPtr.Zero) + { + return; + } + + for (;;) + { + try + { + // 获得一个完整消æ¯çš„长度 + + var peekSize = KCP.KcpPeeksize(_kcpIntPtr); + + // 如果没有接收的消æ¯é‚£å°±è·³å‡ºå½“å‰å¾ªçŽ¯ã€‚ + + if (peekSize < 0) + { + return; + } + + // 如果为0ï¼Œè¡¨ç¤ºå½“å‰æ¶ˆæ¯å‘生错误。æç¤ºä¸‹ã€ä¸€èˆ¬æƒ…况ä¸ä¼šå‘生的 + + if (peekSize == 0) + { + throw new Exception("SocketError.NetworkReset"); + } + + _receiveMemoryStream.SetLength(peekSize); + _receiveMemoryStream.Seek(0, SeekOrigin.Begin); + var receiveCount = KCP.KcpRecv(_kcpIntPtr, _receiveMemoryStream.GetBuffer(), peekSize); + + // 如果接收的长度跟peekSizeä¸ä¸€æ ·ï¼Œä¸éœ€è¦å¤„ç†ï¼Œå› ä¸ºæ¶ˆæ¯è‚¯å®šæœ‰é—®é¢˜çš„(虽然ä¸å¯èƒ½å‡ºçް)。 + + if (receiveCount != peekSize) + { + Log.Error($"receiveCount != peekSize receiveCount:{receiveCount} peekSize:{peekSize}"); + break; + } + + var packInfo = _packetParser.UnPack(_receiveMemoryStream); + + if (packInfo == null) + { + break; + } + + + ThreadSynchronizationContext.Main.Post(() => + { + if (IsDisposed) + { + return; + } + + OnReceiveMemoryStream(packInfo); + }); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public void Update() + { + Receive(); + + var nowTime = TimeNow; + + if (_updateTimer.Count == 0 || nowTime < _updateMinTime) + { + return; + } + + foreach (var timeId in _updateTimer) + { + var key = timeId.Key; + + if (key > nowTime) + { + _updateMinTime = key; + break; + } + + _updateTimeOutTime.Enqueue(key); + } + + while (_updateTimeOutTime.TryDequeue(out var time)) + { + KcpUpdate(); + _updateTimer.Remove(time); + } + } + + private void AddToUpdate(uint tillTime) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (tillTime == 0) + { + KcpUpdate(); + return; + } + + if (tillTime < _updateMinTime || _updateMinTime == 0) + { + _updateMinTime = tillTime; + } + + _updateTimer[tillTime] = KcpUpdate; + } + + private void KcpUpdate() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + var nowTime = TimeNow; + + try + { + KCP.KcpUpdate(_kcpIntPtr, nowTime); + } + catch (Exception e) + { + Log.Error(e); + } + + if (_kcpIntPtr != IntPtr.Zero) + { + AddToUpdate(KCP.KcpCheck(_kcpIntPtr, nowTime)); + } + } + + public override void RemoveChannel(uint channelId) + { + Dispose(); + } + + private uint CreateChannelId() + { + return 0xC0000000 | (uint) new Random().Next(); + } + + private void SendHeader(KcpHeader kcpHeader) + { + if (_socket == null || !_socket.Connected) + { + return; + } + + var buff = new byte[5]; + buff.WriteTo(0, (byte) kcpHeader); + buff.WriteTo(1, ChannelId); + _socket.Send(buff, 5, SocketFlags.None); + } + + private void ClearConnectTimeout(ref long connectTimeoutId) + { + if (connectTimeoutId == 0) + { + return; + } + + if (NetworkThread.Instance.ManagedThreadId == Thread.CurrentThread.ManagedThreadId) + { + var timeoutId = connectTimeoutId; + ThreadSynchronizationContext.Main.Post(() => { TimerScheduler.Instance.Core.Remove(timeoutId); }); + connectTimeoutId = 0; + return; + } + + TimerScheduler.Instance.Core.RemoveByRef(ref connectTimeoutId); + } + +#if ENABLE_IL2CPP + [AOT.MonoPInvokeCallback(typeof(KcpOutput))] +#endif + private static int KcpOutput(IntPtr bytes, int count, IntPtr kcp, IntPtr user) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return 0; + } +#endif + try + { + if (kcp == IntPtr.Zero || !ConnectionPtrChannel.TryGetValue(kcp, out var channel)) + { + return 0; + } + + if (!channel.IsDisposed) + { + channel.Output(bytes, count); + } + } + catch (Exception e) + { + Log.Error(e); + } + + return count; + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs.meta new file mode 100644 index 00000000..6f34f121 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d5d5ba58fd350045bd57bf3ab5d5c12 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server.meta new file mode 100644 index 00000000..cc28e0b4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 574d8620bd02b254bbf2cc4a821fd371 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs new file mode 100644 index 00000000..3523e140 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs @@ -0,0 +1,502 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using TEngine.DataStructure; +using TEngine.Core; +// ReSharper disable InconsistentNaming +#pragma warning disable CS8601 +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public class KCPServerNetwork : ANetwork, INetworkUpdate + { + #region 逻辑线程 + + public KCPServerNetwork(Scene scene, NetworkTarget networkTarget, IPEndPoint address) : base(scene, NetworkType.Server, NetworkProtocolType.KCP, networkTarget) + { + _startTime = TimeHelper.Now; + NetworkThread.Instance.AddNetwork(this); + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + KcpSettings = KCPSettings.Create(NetworkTarget); + _rawReceiveBuffer = new byte[KcpSettings.Mtu + 5]; + + _socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); + } + + _socket.Bind(address); + _socket.SetSocketBufferToOsLimit(); + NetworkHelper.SetSioUdpConnReset(_socket); + }); + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + IsDisposed = true; + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + if (_socket.Connected) + { + _socket.Disconnect(false); + _socket.Close(); + } + + var channels = new List(); + + channels.AddRange(_connectionChannel.Values); + channels.AddRange(_pendingConnection.Values); + + foreach (var channel in channels.Where(channel => !channel.IsDisposed)) + { + channel.Dispose(); + } + + _updateTimeOutTime.Clear(); + _updateChannels.Clear(); + _pendingTimeOutTime.Clear(); + _updateTimer.Clear(); + _pendingConnectionTimer.Clear(); + _pendingConnection.Clear(); + _connectionChannel.Clear(); + + _socket = null; + KcpSettings = null; + _updateMinTime = 0; + _pendingMinTime = 0; + base.Dispose(); + }); + } + + #endregion + + #region 网络主线程 + + private Socket _socket; + private uint _updateMinTime; + private uint _pendingMinTime; + private byte[] _rawReceiveBuffer; + private readonly long _startTime; + private readonly byte[] _sendBuff = new byte[5]; + private EndPoint _clientEndPoint = new IPEndPoint(IPAddress.Any, 0); + private readonly Queue _updateTimeOutTime = new Queue(); + private readonly HashSet _updateChannels = new HashSet(); + private readonly Queue _pendingTimeOutTime = new Queue(); + private readonly SortedOneToManyHashSet _updateTimer = new SortedOneToManyHashSet(); + private readonly SortedOneToManyList _pendingConnectionTimer = new SortedOneToManyList(); + private readonly Dictionary _pendingConnection = new Dictionary(); + private readonly Dictionary _connectionChannel = new Dictionary(); + public static readonly Dictionary ConnectionPtrChannel = new Dictionary(); + private KCPSettings KcpSettings { get; set; } + private uint TimeNow => (uint) (TimeHelper.Now - _startTime); + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (!_connectionChannel.TryGetValue(channelId, out var channel)) + { + return; + } + + var sendMemoryStream = Pack(rpcId, routeTypeOpCode, routeId, memoryStream, null); + channel.Send(sendMemoryStream); + } + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, object message) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (!_connectionChannel.TryGetValue(channelId, out var channel)) + { + return; + } + + var memoryStream = Pack(rpcId, routeTypeOpCode, routeId, null, message); + channel.Send(memoryStream); + } + + private void SendToRepeatChannelId(uint channelId, EndPoint clientEndPoint) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + _sendBuff.WriteTo(0, (byte) KcpHeader.RepeatChannelId); + _sendBuff.WriteTo(1, channelId); + _socket.SendTo(_sendBuff, 0, 5, SocketFlags.None, clientEndPoint); + } + + private void Receive() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + while (_socket != null && _socket.Available > 0) + { + try + { + var receiveLength = _socket.ReceiveFrom(_rawReceiveBuffer, ref _clientEndPoint); + // Log.Debug($"_socket.ReceiveFrom receiveLength:{receiveLength}"); + if (receiveLength < 1) + { + continue; + } + + var header = (KcpHeader) _rawReceiveBuffer[0]; + var channelId = BitConverter.ToUInt32(_rawReceiveBuffer, 1); + + switch (header) + { + case KcpHeader.RequestConnection: + { + // Log.Debug("KcpHeader.RequestConnection"); + if (receiveLength != 5) + { + break; + } + + if (_pendingConnection.TryGetValue(channelId, out var channel)) + { + if (!_clientEndPoint.Equals(channel.RemoteEndPoint)) + { + SendToRepeatChannelId(channelId, _clientEndPoint); + } + + break; + } + + if (_connectionChannel.ContainsKey(channelId)) + { + SendToRepeatChannelId(channelId, _clientEndPoint); + break; + } + + var timeNow = TimeNow; + var tillTime = timeNow + 10 * 1000; + var pendingChannel = new KCPServerNetworkChannel(Scene, channelId, Id, _clientEndPoint, _socket, timeNow); + + if (tillTime < _pendingMinTime || _pendingMinTime == 0) + { + _pendingMinTime = tillTime; + } + + _pendingConnection.Add(channelId, pendingChannel); + _pendingConnectionTimer.Add(tillTime, channelId); + _sendBuff.WriteTo(0, (byte) KcpHeader.WaitConfirmConnection); + _sendBuff.WriteTo(1, channelId); + _socket.SendTo(_sendBuff, 0, 5, SocketFlags.None, _clientEndPoint); + break; + } + case KcpHeader.ConfirmConnection: + { + // Log.Debug("KcpHeader.ConfirmConnection"); + if (receiveLength != 5) + { + break; + } + + if (!RemovePendingConnection(channelId, _clientEndPoint, out var channel)) + { + break; + } + + var kcpIntPtr = KCP.KcpCreate(channelId, new IntPtr(channelId)); + + KCP.KcpNodelay(kcpIntPtr, 1, 5, 2, 1); + KCP.KcpWndsize(kcpIntPtr, KcpSettings.SendWindowSize, KcpSettings.ReceiveWindowSize); + KCP.KcpSetmtu(kcpIntPtr, KcpSettings.Mtu); + KCP.KcpSetminrto(kcpIntPtr, 30); + KCP.KcpSetoutput(kcpIntPtr, KcpOutput); + + _connectionChannel.Add(channel.Id, channel); + ConnectionPtrChannel.Add(kcpIntPtr, channel); + channel.Connect(kcpIntPtr, AddToUpdate, KcpSettings.MaxSendWindowSize, NetworkTarget, NetworkMessageScheduler); + break; + } + case KcpHeader.ReceiveData: + { + var messageLength = receiveLength - 5; + + if (messageLength <= 0) + { + Log.Warning($"KCP Server KcpHeader.Data messageLength <= 0"); + break; + } + + if (!_connectionChannel.TryGetValue(channelId, out var channel)) + { + break; + } + + KCP.KcpInput(channel.KcpIntPtr, _rawReceiveBuffer, 5, messageLength); + AddToUpdate(0, channel.Id); + channel.Receive(); + break; + } + case KcpHeader.Disconnect: + { + // Log.Debug("KcpHeader.Disconnect"); + RemoveChannel(channelId); + break; + } + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private bool RemovePendingConnection(uint channelId, EndPoint remoteEndPoint, out KCPServerNetworkChannel channel) + { +#if TENGINE_DEVELOP + channel = null; + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return false; + } +#endif + if (!_pendingConnection.TryGetValue(channelId, out channel) || channel.IsDisposed) + { + return false; + } + + if (remoteEndPoint != null && !remoteEndPoint.Equals(channel.RemoteEndPoint)) + { + Log.Error($"KCPNetworkChannel syn address diff: {channelId} {channel.RemoteEndPoint} {remoteEndPoint}"); + return false; + } + + _pendingConnection.Remove(channelId); + _pendingConnectionTimer.RemoveValue(channel.CreateTime + 10 * 1000, channelId); +#if NETDEBUG + Log.Debug($"KCPServerNetwork _pendingConnection:{_pendingConnection.Count} _pendingConnectionTimer:{_pendingConnectionTimer.Count}"); +#endif + return true; + } + + public override void RemoveChannel(uint channelId) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + if (_connectionChannel.Remove(channelId, out var channel)) + { +#if NETDEBUG + Log.Debug($"KCPServerNetwork _connectionChannel:{_connectionChannel.Count}"); +#endif + channel.Dispose(); + return; + } + + if (RemovePendingConnection(channelId, null, out channel) && !channel.IsDisposed) + { + channel.Dispose(); + } + } + + public void Update() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + Receive(); + + var nowTime = TimeNow; + + if (nowTime >= _updateMinTime && _updateTimer.Count > 0) + { + foreach (var timeId in _updateTimer) + { + var key = timeId.Key; + + if (key > nowTime) + { + _updateMinTime = key; + break; + } + + _updateTimeOutTime.Enqueue(key); + } + + while (_updateTimeOutTime.TryDequeue(out var time)) + { + foreach (var channelId in _updateTimer[time]) + { + _updateChannels.Add(channelId); + } + + _updateTimer.RemoveKey(time); + } + } + + if (_updateChannels.Count > 0) + { + foreach (var channelId in _updateChannels) + { + if (!_connectionChannel.TryGetValue(channelId, out var channel)) + { + continue; + } + + if (channel.IsDisposed) + { + continue; + } + + try + { + KCP.KcpUpdate(channel.KcpIntPtr, nowTime); + } + catch (Exception e) + { + Log.Error(e); + } + + if (channel.KcpIntPtr != IntPtr.Zero) + { + AddToUpdate(KCP.KcpCheck(channel.KcpIntPtr, nowTime), channelId); + } + } + + _updateChannels.Clear(); + } + + if (_pendingConnection.Count <= 0 || nowTime < _pendingMinTime) + { + return; + } + + foreach (var timeId in _pendingConnectionTimer) + { + var key = timeId.Key; + + if (key > nowTime) + { + _pendingMinTime = key; + break; + } + + foreach (var channelId in timeId.Value) + { + _pendingTimeOutTime.Enqueue(channelId); + } + } + + while (_pendingTimeOutTime.TryDequeue(out var channelId)) + { + if (RemovePendingConnection(channelId, null, out var channel)) + { + channel.Dispose(); + } + } + } + + private void AddToUpdate(uint tillTime, uint channelId) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (tillTime == 0) + { + _updateChannels.Add(channelId); + return; + } + + if (tillTime < _updateMinTime || _updateMinTime == 0) + { + _updateMinTime = tillTime; + } + + _updateTimer.Add(tillTime, channelId); + } + + +#if ENABLE_IL2CPP + [AOT.MonoPInvokeCallback(typeof(KcpOutput))] +#endif + private static int KcpOutput(IntPtr bytes, int count, IntPtr kcp, IntPtr user) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return 0; + } +#endif + try + { + if (kcp == IntPtr.Zero || !ConnectionPtrChannel.TryGetValue(kcp, out var channel)) + { + return 0; + } + + if (!channel.IsDisposed) + { + channel.Output(bytes, count); + } + } + catch (Exception e) + { + Log.Error(e); + } + + return count; + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs.meta new file mode 100644 index 00000000..aeacb476 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a2a741a97ea685468eef08c6f5544fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs new file mode 100644 index 00000000..350af147 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs @@ -0,0 +1,252 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using TEngine.Core; +// ReSharper disable InconsistentNaming +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class KCPServerNetworkChannel : ANetworkChannel + { + #region 网络主线程 + + private int _maxSndWnd; + private byte[] _rawSendBuffer; + public readonly uint CreateTime; + private readonly Socket _socket; + private Action _addToUpdate; + private MemoryStream _receiveMemoryStream; + public IntPtr KcpIntPtr { get; private set; } + + public override event Action OnDispose; + public override event Action OnReceiveMemoryStream; + + public KCPServerNetworkChannel(Scene scene, uint id, long networkId, EndPoint remoteEndPoint, Socket socket, uint createTime) : base(scene, id, networkId) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + _socket = socket; + CreateTime = createTime; + RemoteEndPoint = remoteEndPoint; + } + + public override void Dispose() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + var buff = new byte[5]; + buff.WriteTo(0, (byte) KcpHeader.Disconnect); + buff.WriteTo(1, Id); + _socket.SendTo(buff, 5, SocketFlags.None, RemoteEndPoint); + + if (KcpIntPtr != IntPtr.Zero) + { + KCPServerNetwork.ConnectionPtrChannel.Remove(KcpIntPtr); + KCP.KcpRelease(KcpIntPtr); + KcpIntPtr = IntPtr.Zero; + } +#if NETDEBUG + Log.Debug($"KCPServerNetworkChannel ConnectionPtrChannel:{KCPServerNetwork.ConnectionPtrChannel.Count}"); +#endif + _maxSndWnd = 0; + _addToUpdate = null; + _receiveMemoryStream?.Dispose(); + ThreadSynchronizationContext.Main.Post(OnDispose); + base.Dispose(); + } + + public void Connect(IntPtr kcpIntPtr, Action addToUpdate, int maxSndWnd, NetworkTarget networkTarget, ANetworkMessageScheduler networkMessageScheduler) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + KcpIntPtr = kcpIntPtr; + _maxSndWnd = maxSndWnd; + _addToUpdate = addToUpdate; + _rawSendBuffer = new byte[ushort.MaxValue]; + _receiveMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + PacketParser = APacketParser.CreatePacketParser(networkTarget); + + ThreadSynchronizationContext.Main.Post(() => + { + if (IsDisposed) + { + return; + } + + Session.Create(networkMessageScheduler, this); + }); + } + + public void Send(MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed || KcpIntPtr == IntPtr.Zero) + { + return; + } + + // 检查等待å‘é€çš„æ¶ˆæ¯ï¼Œå¦‚果超出两å€çª—å£å¤§å°ï¼ŒKCPä½œè€…ç»™çš„å»ºè®®æ˜¯è¦æ–­å¼€è¿žæŽ¥ + + var waitSendSize = KCP.KcpWaitsnd(KcpIntPtr); + + if (waitSendSize > _maxSndWnd) + { + Log.Warning($"ERR_KcpWaitSendSizeTooLarge {waitSendSize} > {_maxSndWnd}"); + Dispose(); + return; + } + + // å‘逿¶ˆæ¯ + + KCP.KcpSend(KcpIntPtr, memoryStream.GetBuffer(), (int) memoryStream.Length); + + // 因为memoryStream对象池出æ¥çš„ã€æ‰€ä»¥éœ€è¦æ‰‹åŠ¨å›žæ”¶ä¸‹ + + memoryStream.Dispose(); + + _addToUpdate(0, Id); + } + + public void Receive() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed || KcpIntPtr == IntPtr.Zero) + { + return; + } + + for (;;) + { + try + { + if (KcpIntPtr == IntPtr.Zero) + { + return; + } + + // 获得一个完整消æ¯çš„长度 + + var peekSize = KCP.KcpPeeksize(KcpIntPtr); + + // 如果没有接收的消æ¯é‚£å°±è·³å‡ºå½“å‰å¾ªçŽ¯ã€‚ + + if (peekSize < 0) + { + return; + } + + // 如果为0ï¼Œè¡¨ç¤ºå½“å‰æ¶ˆæ¯å‘生错误。æç¤ºä¸‹ã€ä¸€èˆ¬æƒ…况ä¸ä¼šå‘生的 + + if (peekSize == 0) + { + throw new Exception("SocketError.NetworkReset"); + } + + _receiveMemoryStream.SetLength(peekSize); + _receiveMemoryStream.Seek(0, SeekOrigin.Begin); + var receiveCount = KCP.KcpRecv(KcpIntPtr, _receiveMemoryStream.GetBuffer(), peekSize); + + // 如果接收的长度跟peekSizeä¸ä¸€æ ·ï¼Œä¸éœ€è¦å¤„ç†ï¼Œå› ä¸ºæ¶ˆæ¯è‚¯å®šæœ‰é—®é¢˜çš„(虽然ä¸å¯èƒ½å‡ºçް)。 + + if (receiveCount != peekSize) + { + Log.Error($"receiveCount != peekSize receiveCount:{receiveCount} peekSize:{peekSize}"); + break; + } + + var packInfo = PacketParser.UnPack(_receiveMemoryStream); + + if (packInfo == null) + { + break; + } + + ThreadSynchronizationContext.Main.Post(() => + { + if (IsDisposed) + { + return; + } + + // ReSharper disable once PossibleNullReferenceException + OnReceiveMemoryStream(packInfo); + }); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public void Output(IntPtr bytes, int count) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed || KcpIntPtr == IntPtr.Zero) + { + return; + } + + try + { + if (count == 0) + { + throw new Exception("KcpOutput count 0"); + } + + _rawSendBuffer.WriteTo(0, (byte) KcpHeader.ReceiveData); + _rawSendBuffer.WriteTo(1, Id); + Marshal.Copy(bytes, _rawSendBuffer, 5, count); + _socket.SendTo(_rawSendBuffer, 0, count + 5, SocketFlags.None, RemoteEndPoint); + } + catch (Exception e) + { + Log.Error(e); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs.meta new file mode 100644 index 00000000..29fd3f5d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec35331bec12b514fa057b062368710a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP.meta new file mode 100644 index 00000000..b24a42f9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 47d85ee74f0898440834159ab0083e2e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client.meta new file mode 100644 index 00000000..84c5cb26 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a1b4c600a0229c845a8a16b5e3b85099 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs new file mode 100644 index 00000000..40566124 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs @@ -0,0 +1,535 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using TEngine.DataStructure; +#pragma warning disable CS8622 +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +// ReSharper disable InconsistentNaming + +namespace TEngine.Core.Network +{ + public sealed class TCPClientNetwork : AClientNetwork + { + #region 逻辑线程 + + private bool _isInit; + private Action _onConnectFail; + private Action _onConnectComplete; + private long _connectTimeoutId; + public override event Action OnDispose; + public override event Action OnChangeChannelId = channelId => { }; + public override event Action OnReceiveMemoryStream; + + public TCPClientNetwork(Scene scene, NetworkTarget networkTarget) : base(scene, NetworkType.Client, NetworkProtocolType.TCP, networkTarget) + { + NetworkThread.Instance.AddNetwork(this); + } + + public override uint Connect(IPEndPoint remoteEndPoint, Action onConnectComplete, Action onConnectFail, int connectTimeout = 5000) + { + if (_isInit) + { + throw new NotSupportedException($"KCPClientNetwork Id:{Id} Has already been initialized. If you want to call Connect again, please re instantiate it."); + } + + _isInit = true; + _onConnectFail = onConnectFail; + _onConnectComplete = onConnectComplete; + ChannelId = 0xC0000000 | (uint) new Random().Next(); + + _sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) => + { + if (IsDisposed) + { + return; + } + + _messageCache.Enqueue(new MessageCacheInfo() + { + RpcId = rpcId, + RouteId = routeId, + RouteTypeOpCode = routeTypeOpCode, + Message = message, + MemoryStream = memoryStream + }); + }; + + _packetParser = APacketParser.CreatePacketParser(NetworkTarget); + + _outArgs.Completed += OnComplete; + _innArgs.Completed += OnComplete; + + _connectTimeoutId = TimerScheduler.Instance.Core.OnceTimer(connectTimeout, () => + { + _onConnectFail?.Invoke(); + Dispose(); + }); + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + var outArgs = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint + }; + + outArgs.Completed += OnComplete; + + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) {NoDelay = true}; + _socket.SetSocketBufferToOsLimit(); + + if (_socket.ConnectAsync(outArgs)) + { + return; + } + + OnConnectComplete(outArgs); + }); + + return ChannelId; + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + IsDisposed = true; + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + if (_socket.Connected) + { + _socket.Disconnect(true); + _socket.Close(); + } + + _outArgs?.Dispose(); + _innArgs?.Dispose(); + _sendBuffer?.Dispose(); + _receiveBuffer?.Dispose(); + _packetParser?.Dispose(); + + _sendAction = null; + _packetParser = null; + _isSending = false; + + if (_messageCache != null) + { + _messageCache.Clear(); + _messageCache = null; + } + + ThreadSynchronizationContext.Main.Post(OnDispose); + base.Dispose(); + }); + } + + #endregion + + #region 网络主线程 + + private Socket _socket; + private bool _isSending; + private APacketParser _packetParser; + private Action _sendAction; + private readonly CircularBuffer _sendBuffer = new CircularBuffer(); + private readonly CircularBuffer _receiveBuffer = new CircularBuffer(); + private readonly SocketAsyncEventArgs _outArgs = new SocketAsyncEventArgs(); + private readonly SocketAsyncEventArgs _innArgs = new SocketAsyncEventArgs(); + private Queue _messageCache = new Queue(); + + private void OnConnectComplete(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + if (asyncEventArgs.SocketError != SocketError.Success) + { + Log.Error($"Unable to connect to the target server asyncEventArgs:{asyncEventArgs.SocketError}"); + + if (_onConnectFail != null) + { + ThreadSynchronizationContext.Main.Post(_onConnectFail); + } + + Dispose(); + return; + } + + Receive(); + ClearConnectTimeout(ref _connectTimeoutId); + + _sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) => + { + if (IsDisposed) + { + return; + } + + memoryStream = Pack(rpcId, routeTypeOpCode, routeId, memoryStream, message); + Send(memoryStream); + }; + + while (_messageCache.TryDequeue(out var messageCacheInfo)) + { + _sendAction( + messageCacheInfo.RpcId, + messageCacheInfo.RouteTypeOpCode, + messageCacheInfo.RouteId, + messageCacheInfo.MemoryStream, + messageCacheInfo.Message); + } + + _messageCache.Clear(); + _messageCache = null; + + if (_onConnectComplete != null) + { + ThreadSynchronizationContext.Main.Post(_onConnectComplete); + } + } + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, object message) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (channelId != ChannelId || IsDisposed) + { + return; + } + + _sendAction(rpcId, routeTypeOpCode, routeId, null, message); + } + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (channelId != ChannelId || IsDisposed) + { + return; + } + + _sendAction(rpcId, routeTypeOpCode, routeId, memoryStream, null); + } + + private void Send(MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + _sendBuffer.Write(memoryStream); + + // 因为memoryStream对象池出æ¥çš„ã€æ‰€ä»¥éœ€è¦æ‰‹åŠ¨å›žæ”¶ä¸‹ + + memoryStream.Dispose(); + + if (_isSending) + { + return; + } + + Send(); + } + + private void Send() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (_isSending || IsDisposed) + { + return; + } + + for (;;) + { + try + { + if (_sendBuffer.Length == 0) + { + _isSending = false; + return; + } + + _isSending = true; + + var sendSize = CircularBuffer.ChunkSize - _sendBuffer.FirstIndex; + + if (sendSize > _sendBuffer.Length) + { + sendSize = (int) _sendBuffer.Length; + } + + _outArgs.SetBuffer(_sendBuffer.First, _sendBuffer.FirstIndex, sendSize); + + if (_socket.SendAsync(_outArgs)) + { + return; + } + + SendCompletedHandler(_outArgs); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private void SendCompletedHandler(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (asyncEventArgs.SocketError != SocketError.Success || asyncEventArgs.BytesTransferred == 0) + { + return; + } + + _sendBuffer.FirstIndex += asyncEventArgs.BytesTransferred; + + if (_sendBuffer.FirstIndex == CircularBuffer.ChunkSize) + { + _sendBuffer.FirstIndex = 0; + _sendBuffer.RemoveFirst(); + } + } + + private void OnSendComplete(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + _isSending = false; + SendCompletedHandler(asyncEventArgs); + + if (_sendBuffer.Length > 0) + { + Send(); + } + } + + private void Receive() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + for (;;) + { + try + { + if (IsDisposed) + { + return; + } + + var size = CircularBuffer.ChunkSize - _receiveBuffer.LastIndex; + _innArgs.SetBuffer(_receiveBuffer.Last, _receiveBuffer.LastIndex, size); + + if (_socket.ReceiveAsync(_innArgs)) + { + return; + } + + ReceiveCompletedHandler(_innArgs); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private void ReceiveCompletedHandler(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (asyncEventArgs.SocketError != SocketError.Success) + { + return; + } + + if (asyncEventArgs.BytesTransferred == 0) + { + Dispose(); + return; + } + + _receiveBuffer.LastIndex += asyncEventArgs.BytesTransferred; + + if (_receiveBuffer.LastIndex >= CircularBuffer.ChunkSize) + { + _receiveBuffer.AddLast(); + _receiveBuffer.LastIndex = 0; + } + + for (;;) + { + try + { + if (IsDisposed) + { + return; + } + + if (!_packetParser.UnPack(_receiveBuffer,out var packInfo)) + { + break; + } + + ThreadSynchronizationContext.Main.Post(() => + { + if (IsDisposed) + { + return; + } + + // ReSharper disable once PossibleNullReferenceException + OnReceiveMemoryStream(packInfo); + }); + } + catch (ScanException e) + { + Log.Debug($"{e}"); + Dispose(); + } + catch (Exception e) + { + Log.Error($"{e}"); + Dispose(); + } + } + } + + private void OnReceiveComplete(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + ReceiveCompletedHandler(asyncEventArgs); + Receive(); + } + + public override void RemoveChannel(uint channelId) + { + Dispose(); + } + + private void ClearConnectTimeout(ref long connectTimeoutId) + { + if (connectTimeoutId == 0) + { + return; + } + + if (NetworkThread.Instance.ManagedThreadId == Thread.CurrentThread.ManagedThreadId) + { + var timeoutId = connectTimeoutId; + ThreadSynchronizationContext.Main.Post(() => { TimerScheduler.Instance.Core.Remove(timeoutId); }); + connectTimeoutId = 0; + return; + } + + TimerScheduler.Instance.Core.RemoveByRef(ref connectTimeoutId); + } + + #endregion + + #region 网络线程(由Socket底层产生的线程) + + private void OnComplete(object sender, SocketAsyncEventArgs asyncEventArgs) + { + if (IsDisposed) + { + return; + } + + switch (asyncEventArgs.LastOperation) + { + case SocketAsyncOperation.Connect: + { + NetworkThread.Instance.SynchronizationContext.Post(() => OnConnectComplete(asyncEventArgs)); + break; + } + case SocketAsyncOperation.Receive: + { + NetworkThread.Instance.SynchronizationContext.Post(() => OnReceiveComplete(asyncEventArgs)); + break; + } + case SocketAsyncOperation.Send: + { + NetworkThread.Instance.SynchronizationContext.Post(() => OnSendComplete(asyncEventArgs)); + break; + } + case SocketAsyncOperation.Disconnect: + { + NetworkThread.Instance.SynchronizationContext.Post(Dispose); + break; + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs.meta new file mode 100644 index 00000000..4512c5de --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Client/TCPClientNetwork.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 19a3e65e84ac7eb458f0b4354dabf39a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server.meta new file mode 100644 index 00000000..3ec11fe5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 94ad5582981b49347b1daf74950dc59b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs new file mode 100644 index 00000000..a0e61362 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +// ReSharper disable InconsistentNaming +#pragma warning disable CS8625 +#pragma warning disable CS8622 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class TCPServerNetwork : ANetwork + { + #region 逻辑线程 + + public TCPServerNetwork(Scene scene, NetworkTarget networkTarget, IPEndPoint address) : base(scene, NetworkType.Server, NetworkProtocolType.TCP, networkTarget) + { + _acceptAsync = new SocketAsyncEventArgs(); + NetworkThread.Instance.AddNetwork(this); + NetworkThread.Instance.SynchronizationContext.Post(() => + { + _random = new Random(); + _socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); + } + + _socket.Bind(address); + _socket.Listen(int.MaxValue); + _socket.SetSocketBufferToOsLimit(); + _acceptAsync.Completed += OnCompleted; + AcceptAsync(); + }); + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + IsDisposed = true; + + NetworkThread.Instance.SynchronizationContext.Post(() => + { + if (_socket.Connected) + { + _socket.Disconnect(true); + _socket.Close(); + } + + _socket = null; + _random = null; + _acceptAsync = null; + + var channels = new List(_connectionChannel.Values); + + foreach (var tcpServerNetworkChannel in channels) + { + tcpServerNetworkChannel.Dispose(); + } + + channels.Clear(); + _connectionChannel.Clear(); + base.Dispose(); + }); + } + + #endregion + + #region 网络主线程 + + private Socket _socket; + private Random _random; + private SocketAsyncEventArgs _acceptAsync; + private readonly Dictionary _connectionChannel = new Dictionary(); + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (!_connectionChannel.TryGetValue(channelId, out var channel) || channel.IsDisposed) + { + return; + } + + var sendMemoryStream = Pack(rpcId, routeTypeOpCode, routeId, memoryStream, null); + channel.Send(sendMemoryStream); + } + + public override void Send(uint channelId, uint rpcId, long routeTypeOpCode, long routeId, object message) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (!_connectionChannel.TryGetValue(channelId, out var channel) || channel.IsDisposed) + { + return; + } + + var memoryStream = Pack(rpcId, routeTypeOpCode, routeId, null, message); + channel.Send(memoryStream); + } + + private void AcceptAsync() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + _acceptAsync.AcceptSocket = null; + + if (_socket.AcceptAsync(_acceptAsync)) + { + return; + } + + OnAcceptComplete(_acceptAsync); + } + + private void OnAcceptComplete(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (_socket == null || asyncEventArgs.AcceptSocket == null) + { + return; + } + + if (asyncEventArgs.SocketError != SocketError.Success) + { + Log.Error($"Socket Accept Error: {_acceptAsync.SocketError}"); + return; + } + + try + { + var channelId = 0xC0000000 | (uint) _random.Next(); + + while (_connectionChannel.ContainsKey(channelId)) + { + channelId = 0xC0000000 | (uint) _random.Next(); + } + + var channel = new TCPServerNetworkChannel(channelId, asyncEventArgs.AcceptSocket, this); + + ThreadSynchronizationContext.Main.Post(() => + { + if (channel.IsDisposed) + { + return; + } + + Session.Create(NetworkMessageScheduler, channel); + }); + + _connectionChannel.Add(channelId, channel); + channel.Receive(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + AcceptAsync(); + } + } + + public override void RemoveChannel(uint channelId) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed || !_connectionChannel.Remove(channelId, out var channel)) + { + return; + } +#if NETDEBUG + Log.Debug($"TCPServerNetwork _connectionChannel:{_connectionChannel.Count}"); +#endif + if (channel.IsDisposed) + { + return; + } + + channel.Dispose(); + } + + #endregion + + #region 网络线程(由Socket底层产生的线程) + + private void OnCompleted(object sender, SocketAsyncEventArgs asyncEventArgs) + { + switch (asyncEventArgs.LastOperation) + { + case SocketAsyncOperation.Accept: + { + NetworkThread.Instance.SynchronizationContext.Post(() => OnAcceptComplete(asyncEventArgs)); + break; + } + default: + { + throw new Exception($"Socket Accept Error: {asyncEventArgs.LastOperation}"); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs.meta new file mode 100644 index 00000000..8087603f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetwork.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b02f01e4179665a41801dbb47c1f1266 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs new file mode 100644 index 00000000..22ab8c05 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs @@ -0,0 +1,349 @@ +using System; +using System.IO; +using System.Net.Sockets; +using TEngine.DataStructure; +// ReSharper disable InconsistentNaming +#pragma warning disable CS8622 +#pragma warning disable CS8601 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class TCPServerNetworkChannel : ANetworkChannel + { + #region 网络主线程 + + private bool _isSending; + private readonly Socket _socket; + private readonly CircularBuffer _sendBuffer = new CircularBuffer(); + private readonly CircularBuffer _receiveBuffer = new CircularBuffer(); + private readonly SocketAsyncEventArgs _outArgs = new SocketAsyncEventArgs(); + private readonly SocketAsyncEventArgs _innArgs = new SocketAsyncEventArgs(); + + public override event Action OnDispose; + public override event Action OnReceiveMemoryStream; + + public TCPServerNetworkChannel(uint id, Socket socket, ANetwork network) : base(network.Scene, id, network.Id) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + _socket = socket; + _socket.NoDelay = true; + RemoteEndPoint = _socket.RemoteEndPoint; + + _innArgs.Completed += OnComplete; + _outArgs.Completed += OnComplete; + + PacketParser = APacketParser.CreatePacketParser(network.NetworkTarget); + } + + public override void Dispose() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + _isSending = false; + + if (_socket.Connected) + { + _socket.Disconnect(true); + _socket.Close(); + } + + _outArgs.Dispose(); + _innArgs.Dispose(); + _sendBuffer.Dispose(); + _receiveBuffer.Dispose(); + ThreadSynchronizationContext.Main.Post(OnDispose); + base.Dispose(); + } + + public void Send(MemoryStream memoryStream) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + _sendBuffer.Write(memoryStream); + + // 因为memoryStream对象池出æ¥çš„ã€æ‰€ä»¥éœ€è¦æ‰‹åŠ¨å›žæ”¶ä¸‹ + + memoryStream.Dispose(); + + if (_isSending) + { + return; + } + + Send(); + } + + private void Send() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (_isSending || IsDisposed) + { + return; + } + + for (;;) + { + try + { + if (IsDisposed) + { + return; + } + + if (_sendBuffer.Length == 0) + { + _isSending = false; + return; + } + + _isSending = true; + + var sendSize = CircularBuffer.ChunkSize - _sendBuffer.FirstIndex; + + if (sendSize > _sendBuffer.Length) + { + sendSize = (int) _sendBuffer.Length; + } + + _outArgs.SetBuffer(_sendBuffer.First, _sendBuffer.FirstIndex, sendSize); + + if (_socket.SendAsync(_outArgs)) + { + return; + } + + SendCompletedHandler(_outArgs); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private void SendCompletedHandler(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (asyncEventArgs.SocketError != SocketError.Success || asyncEventArgs.BytesTransferred == 0) + { + return; + } + + _sendBuffer.FirstIndex += asyncEventArgs.BytesTransferred; + + if (_sendBuffer.FirstIndex == CircularBuffer.ChunkSize) + { + _sendBuffer.FirstIndex = 0; + _sendBuffer.RemoveFirst(); + } + } + + private void OnSendComplete(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (IsDisposed) + { + return; + } + + _isSending = false; + SendCompletedHandler(asyncEventArgs); + + if (_sendBuffer.Length > 0) + { + Send(); + } + } + + public void Receive() + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + for (;;) + { + try + { + if (IsDisposed) + { + return; + } + + var size = CircularBuffer.ChunkSize - _receiveBuffer.LastIndex; + _innArgs.SetBuffer(_receiveBuffer.Last, _receiveBuffer.LastIndex, size); + + if (_socket.ReceiveAsync(_innArgs)) + { + return; + } + + ReceiveCompletedHandler(_innArgs); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private void ReceiveCompletedHandler(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + if (asyncEventArgs.SocketError != SocketError.Success) + { + return; + } + + if (asyncEventArgs.BytesTransferred == 0) + { + Dispose(); + return; + } + + _receiveBuffer.LastIndex += asyncEventArgs.BytesTransferred; + + if (_receiveBuffer.LastIndex >= CircularBuffer.ChunkSize) + { + _receiveBuffer.AddLast(); + _receiveBuffer.LastIndex = 0; + } + + for (;;) + { + try + { + if (IsDisposed) + { + return; + } + + if (!PacketParser.UnPack(_receiveBuffer, out var packInfo)) + { + break; + } + + ThreadSynchronizationContext.Main.Post(() => + { + if (IsDisposed) + { + return; + } + + // ReSharper disable once PossibleNullReferenceException + OnReceiveMemoryStream(packInfo); + }); + } + catch (ScanException e) + { + Log.Debug($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + catch (Exception e) + { + Log.Error($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + } + } + + private void OnReceiveComplete(SocketAsyncEventArgs asyncEventArgs) + { +#if TENGINE_DEVELOP + if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + Log.Error("not in NetworkThread!"); + return; + } +#endif + ReceiveCompletedHandler(asyncEventArgs); + Receive(); + } + + #endregion + + #region 网络线程(由Socket底层产生的线程) + + private void OnComplete(object sender, SocketAsyncEventArgs asyncEventArgs) + { + if (IsDisposed) + { + return; + } + + switch (asyncEventArgs.LastOperation) + { + case SocketAsyncOperation.Receive: + { + NetworkThread.Instance.SynchronizationContext.Post(() => OnReceiveComplete(asyncEventArgs)); + break; + } + case SocketAsyncOperation.Send: + { + NetworkThread.Instance.SynchronizationContext.Post(() => OnSendComplete(asyncEventArgs)); + break; + } + case SocketAsyncOperation.Disconnect: + { + NetworkThread.Instance.SynchronizationContext.Post(Dispose); + break; + } + default: + { + throw new Exception($"Socket Error: {asyncEventArgs.LastOperation}"); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs.meta new file mode 100644 index 00000000..1a766b06 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/TCP/Server/TCPServerNetworkChannel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2aaa818de3923cf45bf97b802b31cf79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkThread.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkThread.meta new file mode 100644 index 00000000..929efb9c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkThread.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 20a4d07783582164381847abd4a5e337 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs new file mode 100644 index 00000000..44018753 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public enum NetActionType + { + None = 0, + Send = 1, + SendMemoryStream = 2, + RemoveChannel = 3, + } + + public struct NetAction : IDisposable + { + public object Obj; + public uint RpcId; + public long EntityId; + public long NetworkId; + public uint ChannelId; + public long RouteTypeOpCode; + public MemoryStream MemoryStream; + public NetActionType NetActionType; + + public NetAction(long networkId, uint channelId, uint rpcId, long routeTypeOpCode, long entityId, NetActionType netActionType, MemoryStream memoryStream) + { + Obj = null; + RpcId = rpcId; + EntityId = entityId; + NetworkId = networkId; + ChannelId = channelId; + RouteTypeOpCode = routeTypeOpCode; + MemoryStream = memoryStream; + NetActionType = netActionType; + } + + public NetAction(long networkId, uint channelId, uint rpcId, long routeTypeOpCode, long entityId, NetActionType netActionType, object obj) + { + Obj = obj; + RpcId = rpcId; + EntityId = entityId; + NetworkId = networkId; + ChannelId = channelId; + MemoryStream = null; + RouteTypeOpCode = routeTypeOpCode; + NetActionType = netActionType; + } + + public void Dispose() + { + Obj = null; + MemoryStream = null; + RpcId = 0; + EntityId = 0; + NetworkId = 0; + ChannelId = 0; + RouteTypeOpCode = 0; + NetActionType = NetActionType.None; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs.meta new file mode 100644 index 00000000..7338d4b4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42b0f3cecca4bce48bbb6c5ff23adc31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs new file mode 100644 index 00000000..210ee245 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using TEngine.Core; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public sealed class NetworkThread : Singleton + { + #region 逻辑线程 + + private Thread _netWorkThread; + public ThreadSynchronizationContext SynchronizationContext; + private readonly ConcurrentQueue _actions = new ConcurrentQueue(); + + public int ManagedThreadId => _netWorkThread.ManagedThreadId; + + public override async Task Initialize() + { + _netWorkThread = new Thread(Update); + SynchronizationContext = new ThreadSynchronizationContext(_netWorkThread.ManagedThreadId); + _netWorkThread.Start(); + await Task.CompletedTask; + } + + public void Send(long networkId, uint channelId, uint rpcId, long routeTypeOpCode, long entityId, object message) + { + if (IsDisposed) + { + return; + } + + _actions.Enqueue(new NetAction(networkId, channelId, rpcId, routeTypeOpCode, entityId, NetActionType.Send, message)); + } + + public void SendStream(long networkId, uint channelId, uint rpcId, long routeTypeOpCode, long routeId, MemoryStream memoryStream) + { + if (IsDisposed) + { + return; + } + + _actions.Enqueue(new NetAction(networkId, channelId, rpcId, routeTypeOpCode, routeId, NetActionType.SendMemoryStream, memoryStream)); + } + + public void RemoveChannel(long networkId, uint channelId) + { + if (IsDisposed) + { + return; + } + + _actions.Enqueue(new NetAction(networkId, channelId, 0, 0, 0, NetActionType.RemoveChannel, null)); + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + SynchronizationContext.Post(() => + { + if (IsDisposed) + { + return; + } + + IsDisposed = true; + var removeList = new Queue(_networks.Values); + + foreach (var aNetwork in removeList) + { + if (aNetwork.IsDisposed) + { + continue; + } + + aNetwork.Dispose(); + } + + foreach (var netAction in _actions) + { + netAction.Dispose(); + } + + _actions.Clear(); + _networks.Clear(); + removeList.Clear(); + + _netWorkThread = null; + SynchronizationContext = null; + base.Dispose(); + }); + } + + #endregion + + #region 网络线程 + + private readonly Dictionary _networks = new Dictionary(); + private readonly Dictionary _updates = new Dictionary(); + + private void Update() + { + System.Threading.SynchronizationContext.SetSynchronizationContext(SynchronizationContext); + + while (!IsDisposed) + { + Thread.Sleep(1); + + foreach (var (_, aNetwork) in _updates) + { + try + { + aNetwork.Update(); + } + catch (Exception e) + { + Log.Error(e); + } + } + + SynchronizationContext.Update(); + + while (_actions.TryDequeue(out var action)) + { + if (!_networks.TryGetValue(action.NetworkId, out var network) || network.IsDisposed) + { + continue; + } + + try + { + switch (action.NetActionType) + { + case NetActionType.Send: + { + network.Send(action.ChannelId, action.RpcId, action.RouteTypeOpCode, action.EntityId, action.Obj); + break; + } + case NetActionType.SendMemoryStream: + { + network.Send(action.ChannelId, action.RpcId, action.RouteTypeOpCode, action.EntityId, action.MemoryStream); + break; + } + case NetActionType.RemoveChannel: + { + network.RemoveChannel(action.ChannelId); + break; + } + } + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + action.Dispose(); + } + } + } + } + + public void AddNetwork(ANetwork aNetwork) + { + if (IsDisposed) + { + return; + } + + SynchronizationContext.Post(() => + { + if (IsDisposed || aNetwork.IsDisposed) + { + return; + } + + _networks.Add(aNetwork.Id, aNetwork); + + if (aNetwork is INetworkUpdate iNetworkUpdate) + { + _updates.Add(aNetwork.Id, iNetworkUpdate); + } + }); + } + + public void RemoveNetwork(long networkId) + { + if (IsDisposed) + { + return; + } + + SynchronizationContext.Post(() => + { + if (IsDisposed || !_networks.Remove(networkId, out var network)) + { + return; + } + + if (network is INetworkUpdate) + { + _updates.Remove(networkId); + } + }); + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs.meta new file mode 100644 index 00000000..8725796a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkThread/NetworkThread.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8825296c9217dd4db734cba8527d51a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser.meta new file mode 100644 index 00000000..66dfe98a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21eec7ad23e968642a4be2f9d1eb4784 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs new file mode 100644 index 00000000..ea00b223 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs @@ -0,0 +1,262 @@ +#if TENGINE_NET +using TEngine.DataStructure; +using TEngine.Core; +#pragma warning disable CS8600 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace TEngine.Core.Network; + +public sealed class InnerPackInfo : APackInfo +{ + public static InnerPackInfo Create() + { + return Pool.Rent(); + } + + public static InnerPackInfo Create(uint rpcId, long routeId, uint protocolCode) + { + var innerPackInfo = Pool.Rent(); + innerPackInfo.RpcId = rpcId; + innerPackInfo.RouteId = routeId; + innerPackInfo.ProtocolCode = protocolCode; + return innerPackInfo; + } + + public static InnerPackInfo Create(uint rpcId, long routeId, uint protocolCode, long routeTypeCode, MemoryStream memoryStream) + { + var innerPackInfo = Pool.Rent(); + innerPackInfo.RpcId = rpcId; + innerPackInfo.RouteId = routeId; + innerPackInfo.ProtocolCode = protocolCode; + innerPackInfo.RouteTypeCode = routeTypeCode; + innerPackInfo.MemoryStream = memoryStream; + return innerPackInfo; + } + + public override void Dispose() + { + base.Dispose(); + Pool.Return(this); + } + + public override object Deserialize(Type messageType) + { + using (MemoryStream) + { + MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + switch (ProtocolCode) + { + case >= Opcode.InnerBsonRouteResponse: + { + return MongoHelper.Instance.DeserializeFrom(messageType, MemoryStream); + } + case >= Opcode.InnerRouteResponse: + { + return ProtoBufHelper.FromStream(messageType, MemoryStream); + } + case >= Opcode.OuterRouteResponse: + { + return ProtoBufHelper.FromStream(messageType, MemoryStream); + } + case >= Opcode.InnerBsonRouteMessage: + { + return MongoHelper.Instance.DeserializeFrom(messageType, MemoryStream); + } + case >= Opcode.InnerRouteMessage: + case >= Opcode.OuterRouteMessage: + { + return ProtoBufHelper.FromStream(messageType, MemoryStream); + } + default: + { + Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol"); + return null; + } + } + } + } +} + +public sealed class InnerPacketParser : APacketParser +{ + private uint _rpcId; + private long _routeId; + private uint _protocolCode; + private int _messagePacketLength; + private bool _isUnPackHead = true; + private readonly byte[] _messageHead = new byte[Packet.InnerPacketHeadLength]; + + public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo) + { + packInfo = null; + + while (!IsDisposed) + { + if (_isUnPackHead) + { + if (buffer.Length < Packet.InnerPacketHeadLength) + { + return false; + } + + _ = buffer.Read(_messageHead, 0, Packet.InnerPacketHeadLength); + _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); + + if (_messagePacketLength > Packet.PacketBodyMaxLength) + { + throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); + } + + _protocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); + _rpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); + _routeId = BitConverter.ToInt64(_messageHead, Packet.InnerPacketRouteRouteIdLocation); + _isUnPackHead = false; + } + + try + { + if (buffer.Length < _messagePacketLength) + { + return false; + } + + _isUnPackHead = true; + packInfo = InnerPackInfo.Create(_rpcId, _routeId, _protocolCode); + + if (_messagePacketLength > 0) + { + return true; + } + + var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + // 写入消æ¯ä½“的信æ¯åˆ°å†…存中 + memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + buffer.Read(memoryStream, _messagePacketLength); + // 写入消æ¯å¤´çš„ä¿¡æ¯åˆ°å†…存中 + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes(_messagePacketLength)); + memoryStream.Write(BitConverter.GetBytes(packInfo.ProtocolCode)); + memoryStream.Write(BitConverter.GetBytes(packInfo.RpcId)); + memoryStream.Write(BitConverter.GetBytes(packInfo.RouteId)); + memoryStream.Seek(0, SeekOrigin.Begin); + packInfo.MemoryStream = memoryStream; + return true; + } + catch (Exception e) + { + packInfo?.Dispose(); + Log.Error(e); + return false; + } + } + + return false; + } + + public override APackInfo UnPack(MemoryStream memoryStream) + { + InnerPackInfo packInfo = null; + + try + { + if (memoryStream == null || memoryStream.Length < Packet.InnerPacketHeadLength) + { + return null; + } + + _ = memoryStream.Read(_messageHead, 0, Packet.InnerPacketHeadLength); + _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); + + if (_messagePacketLength > Packet.PacketBodyMaxLength) + { + throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); + } + + packInfo = InnerPackInfo.Create(); + packInfo.ProtocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); + packInfo.RpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); + packInfo.RouteId = BitConverter.ToInt64(_messageHead, Packet.InnerPacketRouteRouteIdLocation); + + if (memoryStream.Length < _messagePacketLength) + { + return null; + } + + if (_messagePacketLength <= 0) + { + return packInfo; + } + + var outMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + memoryStream.WriteTo(outMemoryStream); + outMemoryStream.Seek(0, SeekOrigin.Begin); + packInfo.MemoryStream = outMemoryStream; + return packInfo; + } + catch (Exception e) + { + packInfo?.Dispose(); + Log.Error(e); + return null; + } + } + + public static void Serialize(object message, MemoryStream stream) + { + if (message is IBsonMessage) + { + MongoHelper.Instance.SerializeTo(message, stream); + return; + } + + ProtoBufHelper.ToStream(message, stream); + } + + public static MemoryStream Pack(uint rpcId, long routeId, MemoryStream memoryStream) + { + memoryStream.Seek(Packet.InnerPacketRpcIdLocation, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes(rpcId)); + memoryStream.Write(BitConverter.GetBytes(routeId)); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + public static MemoryStream Pack(uint rpcId, long routeId, object message) + { + var opCode = Opcode.PingRequest; + var packetBodyCount = 0; + var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + + memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + if (message != null) + { + Serialize(message, memoryStream); + opCode = MessageDispatcherSystem.Instance.GetOpCode(message.GetType()); + packetBodyCount = (int)(memoryStream.Position - Packet.InnerPacketHeadLength); + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes(packetBodyCount)); + memoryStream.Write(BitConverter.GetBytes(opCode)); + memoryStream.Write(BitConverter.GetBytes(rpcId)); + memoryStream.Write(BitConverter.GetBytes(routeId)); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + public override void Dispose() + { + _messagePacketLength = 0; + Array.Clear(_messageHead, 0, _messageHead.Length); + base.Dispose(); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs.meta new file mode 100644 index 00000000..bd55150e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e64861321cb2b34e9dedd6910bdfb7e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface.meta new file mode 100644 index 00000000..dba0c76a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7e60a19276ca5734297e6367b9a9d047 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs new file mode 100644 index 00000000..dd95008e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine.Core.Network +{ + public abstract class APackInfo : IDisposable + { + public uint RpcId; + public long RouteId; + public uint ProtocolCode; + public long RouteTypeCode; + public MemoryStream MemoryStream; + + public abstract object Deserialize(Type messageType); + + public virtual void Dispose() + { + RpcId = 0; + RouteId = 0; + ProtocolCode = 0; + RouteTypeCode = 0; + MemoryStream = null; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs.meta new file mode 100644 index 00000000..4ceb4128 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe7b65d261a51954cbdcaabc111f1cfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs new file mode 100644 index 00000000..6202565b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; +using TEngine.DataStructure; +using TEngine.Core; + +namespace TEngine.Core.Network +{ + public abstract class APacketParser : IDisposable + { + protected bool IsDisposed { get; private set; } + + public static APacketParser CreatePacketParser(NetworkTarget networkTarget) + { + switch (networkTarget) + { + case NetworkTarget.Inner: + { +#if TENGINE_NET + return new InnerPacketParser(); + #else + return null; +#endif + } + case NetworkTarget.Outer: + { + return new OuterPacketParser(); + } + default: + { + throw new NotSupportedException($"PacketParserHelper Create NotSupport {networkTarget}"); + } + } + } + + public abstract bool UnPack(CircularBuffer buffer, out APackInfo packInfo); + public abstract APackInfo UnPack(MemoryStream memoryStream); + + public virtual void Dispose() + { + IsDisposed = true; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs.meta new file mode 100644 index 00000000..ed8d80eb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64efc3c3308da5540a15195e8b1e10ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs new file mode 100644 index 00000000..745f62df --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs @@ -0,0 +1,216 @@ +using System; +using System.IO; +using TEngine.DataStructure; +using TEngine.Core; +#pragma warning disable CS8603 +#pragma warning disable CS8600 +#pragma warning disable CS8625 + +namespace TEngine.Core.Network +{ + public sealed class OuterPackInfo : APackInfo + { + public static OuterPackInfo Create() + { + return Pool.Rent(); + } + + public static OuterPackInfo Create(uint rpcId, uint protocolCode, long routeTypeCode) + { + var outerPackInfo = Pool.Rent(); + outerPackInfo.RpcId = rpcId; + outerPackInfo.ProtocolCode = protocolCode; + outerPackInfo.RouteTypeCode = routeTypeCode; + return outerPackInfo; + } + + public override void Dispose() + { + base.Dispose(); + Pool.Return(this); + } + + public override object Deserialize(Type messageType) + { + using (MemoryStream) + { + MemoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + return ProtoBufHelper.FromStream(messageType, MemoryStream); + } + } + } + + public sealed class OuterPacketParser : APacketParser + { + private uint _rpcId; + private uint _protocolCode; + private long _routeTypeCode; + private int _messagePacketLength; + private bool _isUnPackHead = true; + private readonly byte[] _messageHead = new byte[Packet.OuterPacketHeadLength]; + + public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo) + { + packInfo = null; + + while (!IsDisposed) + { + if (_isUnPackHead) + { + if (buffer.Length < Packet.OuterPacketHeadLength) + { + return false; + } + + _ = buffer.Read(_messageHead, 0, Packet.OuterPacketHeadLength); + _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); +#if TENGINE_NET + if (_messagePacketLength > Packet.PacketBodyMaxLength) + { + throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); + } +#endif + _protocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); + _rpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); + _routeTypeCode = BitConverter.ToUInt16(_messageHead, Packet.OuterPacketRouteTypeOpCodeLocation); + _isUnPackHead = false; + } + + try + { + if (buffer.Length < _messagePacketLength) + { + return false; + } + + _isUnPackHead = true; + packInfo = OuterPackInfo.Create(_rpcId, _protocolCode, _routeTypeCode); + + if (_messagePacketLength <= 0) + { + return true; + } + + var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + // 写入消æ¯ä½“的信æ¯åˆ°å†…存中 + memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + buffer.Read(memoryStream, _messagePacketLength); + // 写入消æ¯å¤´çš„ä¿¡æ¯åˆ°å†…存中 + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes(_messagePacketLength)); + memoryStream.Write(BitConverter.GetBytes(_protocolCode)); + memoryStream.Write(BitConverter.GetBytes(_rpcId)); + memoryStream.Write(BitConverter.GetBytes(_routeTypeCode)); + memoryStream.Seek(0, SeekOrigin.Begin); + packInfo.MemoryStream = memoryStream; + return true; + } + catch (Exception e) + { + packInfo?.Dispose(); + Log.Error(e); + return false; + } + } + + return false; + } + + public override APackInfo UnPack(MemoryStream memoryStream) + { + OuterPackInfo packInfo = null; + + try + { + if (memoryStream == null) + { + return null; + } + + if (memoryStream.Length < Packet.OuterPacketHeadLength) + { + return null; + } + + _ = memoryStream.Read(_messageHead, 0, Packet.OuterPacketHeadLength); + _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); +#if TENGINE_NET + if (_messagePacketLength > Packet.PacketBodyMaxLength) + { + throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); + } +#endif + packInfo = OuterPackInfo.Create(); + packInfo.ProtocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); + packInfo.RpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); + packInfo.RouteTypeCode = BitConverter.ToUInt16(_messageHead, Packet.OuterPacketRouteTypeOpCodeLocation); + + if (memoryStream.Length < _messagePacketLength) + { + return null; + } + + if (_messagePacketLength <= 0) + { + return packInfo; + } + + var outMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + memoryStream.WriteTo(outMemoryStream); + outMemoryStream.Seek(0, SeekOrigin.Begin); + packInfo.MemoryStream = outMemoryStream; + return packInfo; + } + catch (Exception e) + { + packInfo?.Dispose(); + Log.Error(e); + return null; + } + } + + public static MemoryStream Pack(uint rpcId, long routeTypeOpCode, MemoryStream memoryStream) + { + memoryStream.Seek(Packet.OuterPacketRpcIdLocation, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes(rpcId)); + memoryStream.Write(BitConverter.GetBytes(routeTypeOpCode)); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + public static MemoryStream Pack(uint rpcId, long routeTypeOpCode, object message) + { + var opCode = Opcode.PingRequest; + var packetBodyCount = 0; + var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + + if (message != null) + { + ProtoBufHelper.ToStream(message, memoryStream); + opCode = MessageDispatcherSystem.Instance.GetOpCode(message.GetType()); + packetBodyCount = (int)(memoryStream.Position - Packet.OuterPacketHeadLength); + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes(packetBodyCount)); + memoryStream.Write(BitConverter.GetBytes(opCode)); + memoryStream.Write(BitConverter.GetBytes(rpcId)); + memoryStream.Write(BitConverter.GetBytes(routeTypeOpCode)); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + public override void Dispose() + { + _messagePacketLength = 0; + Array.Clear(_messageHead, 0, _messageHead.Length); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs.meta new file mode 100644 index 00000000..1bca875b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a25f5b0703b81ef4598618dca71905f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs new file mode 100644 index 00000000..044d50b5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs @@ -0,0 +1,58 @@ +namespace TEngine.Core.Network +{ + public struct Packet + { + /// + /// 消æ¯ä½“最大长度 + /// + public const int PacketBodyMaxLength = ushort.MaxValue * 16; + /// + /// 消æ¯ä½“长度在消æ¯å¤´å ç”¨çš„长度 + /// + public const int PacketLength = sizeof(int); + /// + /// å议编å·åœ¨æ¶ˆæ¯å¤´å ç”¨çš„长度 + /// + public const int ProtocolCodeLength = sizeof(uint); + /// + /// RouteId长度 + /// + public const int PacketRouteIdLength = sizeof(long); + /// + /// RpcId在消æ¯å¤´å ç”¨çš„长度 + /// + public const int RpcIdLength = sizeof(uint); + /// + /// RouteTypeOpCode在消æ¯å¤´å ç”¨çš„长度 + /// + public const int RouteTypeOpCodeLength = sizeof(long); + /// + /// OuterRPCId所在的ä½ç½® + /// + public const int OuterPacketRpcIdLocation = PacketLength + ProtocolCodeLength; + /// + /// InnerRPCId所在的ä½ç½® + /// + public const int InnerPacketRpcIdLocation = PacketLength + ProtocolCodeLength; + /// + /// RouteTypeOpCode所在的ä½ç½® + /// + public const int OuterPacketRouteTypeOpCodeLocation = OuterPacketRpcIdLocation + RpcIdLength; + /// + /// RouteId所在的ä½ç½® + /// + public const int InnerPacketRouteRouteIdLocation = PacketLength + ProtocolCodeLength + RpcIdLength; + /// + /// å¤–ç½‘æ¶ˆæ¯æ€»é•¿åº¦ï¼ˆæ¶ˆæ¯ä½“最大长度 + 外网消æ¯å¤´é•¿åº¦ï¼‰ + /// + public const int PacketMaxLength = OuterPacketHeadLength + PacketBodyMaxLength; + /// + /// 外网消æ¯å¤´é•¿åº¦ï¼ˆæ¶ˆæ¯ä½“长度在消æ¯å¤´å ç”¨çš„长度 + å议编å·åœ¨æ¶ˆæ¯å¤´å ç”¨çš„长度 + RPCId长度 + RouteTypeOpCode长度) + /// + public const int OuterPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + RouteTypeOpCodeLength; + /// + /// 内网消æ¯å¤´é•¿åº¦ï¼ˆæ¶ˆæ¯ä½“长度在消æ¯å¤´å ç”¨çš„长度 + å议编å·åœ¨æ¶ˆæ¯å¤´å ç”¨çš„长度 + RPCId长度 + RouteId长度) + /// + public const int InnerPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength; + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs.meta b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs.meta new file mode 100644 index 00000000..1b440ef4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 744a252c35e628f4cb0945449bda29e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Pool.meta b/Assets/GameScripts/DotNet/Core/Pool.meta new file mode 100644 index 00000000..ee5d507a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 07ae348aab8e8a34881a9918006b8fcb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs b/Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs new file mode 100644 index 00000000..4a92a5fb --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Concurrent; + +namespace TEngine +{ + /// + /// çº¿ç¨‹å®‰å…¨çš„é™æ€é€šç”¨å¯¹è±¡æ±  + /// + /// + public static class ConcurrentPool + { + private static readonly ConcurrentQueue PoolQueue = new ConcurrentQueue(); + + public static int Count => PoolQueue.Count; + + public static T Rent() + { + return PoolQueue.TryDequeue(out var t) ? t : Activator.CreateInstance(); + } + + public static T Rent(Func generator) + { + return PoolQueue.TryDequeue(out var t) ? t : generator(); + } + + public static void Return(T t) + { + if (t == null) + { + return; + } + + PoolQueue.Enqueue(t); + } + + public static void Return(T t, Action reset) + { + if (t == null) + { + return; + } + + reset(t); + PoolQueue.Enqueue(t); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs.meta b/Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs.meta new file mode 100644 index 00000000..eb24d42e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/ConcurrentPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7af82d3e27c94b0498507beb740f96de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Pool/Pool.cs b/Assets/GameScripts/DotNet/Core/Pool/Pool.cs new file mode 100644 index 00000000..ad0b2bbe --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/Pool.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace TEngine +{ + /// + /// 陿€é€šç”¨å¯¹è±¡æ±  + /// + /// + public static class Pool + { + private static readonly Queue PoolQueue = new Queue(); + + public static int Count => PoolQueue.Count; + + public static T Rent() + { + return PoolQueue.Count == 0 ? Activator.CreateInstance() : PoolQueue.Dequeue(); + } + + public static T Rent(Func generator) + { + return PoolQueue.Count == 0 ? generator() : PoolQueue.Dequeue(); + } + + public static void Return(T t) + { + if (t == null) + { + return; + } + + PoolQueue.Enqueue(t); + } + + public static void Return(T t, Action reset) + { + if (t == null) + { + return; + } + + reset(t); + PoolQueue.Enqueue(t); + } + + public static void Clear() + { + PoolQueue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/Kcp/Pool.cs.meta b/Assets/GameScripts/DotNet/Core/Pool/Pool.cs.meta similarity index 83% rename from Assets/GameScripts/ThirdParty/Kcp/Pool.cs.meta rename to Assets/GameScripts/DotNet/Core/Pool/Pool.cs.meta index 1c4e6519..19f95d8c 100644 --- a/Assets/GameScripts/ThirdParty/Kcp/Pool.cs.meta +++ b/Assets/GameScripts/DotNet/Core/Pool/Pool.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 10b61be855536d44abc52353ab94a453 +guid: 5b3d07ffa12833d4a805acc09d939e7f MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs b/Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs new file mode 100644 index 00000000..43c45754 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace TEngine +{ + public abstract class PoolCore + { + private readonly Action _reset; + private readonly Func _generator; + private readonly Stack _objects = new Stack(); + public int Count => _objects.Count; + + /// + /// åˆå§‹åŒ–ä¸€ä¸ªæ± å­ + /// + /// æŸäº›ç±»åž‹çš„æž„造函数中å¯èƒ½éœ€è¦é¢å¤–çš„å‚æ•°ï¼Œæ‰€ä»¥ä½¿ç”¨Func生æˆå™¨ + /// æŸäº›ç±»åž‹å¯èƒ½éœ€è¦å¯¹è¿”回的对象进行é¢å¤–æ¸…ç† + /// æ± å­çš„åˆå§‹å¤§å°ã€å¯ä»¥é¢„å…ˆåˆ†é… + protected PoolCore(Func generator, Action reset, int initialCapacity = 0) + { + _generator = generator; + _reset = reset; + + for (var i = 0; i < initialCapacity; ++i) + { + _objects.Push(generator()); + } + } + + public T Rent() + { + return _objects.Count > 0 ? _objects.Pop() : _generator(); + } + + public void Return(T item) + { + _reset(item); + _objects.Push(item); + } + + public void Clear() + { + _objects.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs.meta b/Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs.meta new file mode 100644 index 00000000..09fa56f4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/PoolCore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89a4d3700191cee49b83574d93b0be08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs b/Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs new file mode 100644 index 00000000..0d9b8bf6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; + +namespace TEngine +{ + /// + /// 陿€é€šç”¨å¯¹è±¡æ±  + /// + /// + public static class PoolWithDisposable where T : IDisposable + { + private static readonly Queue PoolQueue = new Queue(); + + public static int Count => PoolQueue.Count; + + public static T Rent() + { + return PoolQueue.Count == 0 ? Activator.CreateInstance() : PoolQueue.Dequeue(); + } + + public static T Rent(Func generator) + { + return PoolQueue.Count == 0 ? Activator.CreateInstance() : PoolQueue.Dequeue(); + } + + public static void Return(T t) + { + if (t == null) + { + return; + } + + PoolQueue.Enqueue(t); + t.Dispose(); + } + + public static void Return(T t, Action reset) + { + if (t == null) + { + return; + } + + reset(t); + PoolQueue.Enqueue(t); + t.Dispose(); + } + + public static void Clear() + { + PoolQueue.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs.meta b/Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs.meta new file mode 100644 index 00000000..824a865f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Pool/PoolWithDisposable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6c920c7d1f7495459e82d52b6403332 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream.meta b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream.meta new file mode 100644 index 00000000..7df2ce4a --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90f991de46d843c49b75b75dd03d10b2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs new file mode 100644 index 00000000..a354d34f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs @@ -0,0 +1,463 @@ +namespace TEngine.IO +{ + using System; + + public sealed partial class RecyclableMemoryStreamManager + { + /// + /// Arguments for the event. + /// + public sealed class StreamCreatedEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Requested stream size. + /// + public long RequestedSize { get; } + + /// + /// Actual stream size. + /// + public long ActualSize { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// The requested stream size. + /// The actual stream size. + public StreamCreatedEventArgs(Guid guid, string tag, long requestedSize, long actualSize) + { + this.Id = guid; + this.Tag = tag; + this.RequestedSize = requestedSize; + this.ActualSize = actualSize; + } + } + + /// + /// Arguments for the event. + /// + public sealed class StreamDisposedEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Stack where the stream was allocated. + /// + public string AllocationStack { get; } + + /// + /// Stack where stream was disposed. + /// + public string DisposeStack { get; } + + /// + /// Lifetime of the stream. + /// + public TimeSpan Lifetime { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Stack of original allocation. + /// Dispose stack. + [Obsolete("Use another constructor override")] + public StreamDisposedEventArgs(Guid guid, string tag, string allocationStack, string disposeStack) + :this(guid, tag, TimeSpan.Zero, allocationStack, disposeStack) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Lifetime of the stream + /// Stack of original allocation. + /// Dispose stack. + public StreamDisposedEventArgs(Guid guid, string tag, TimeSpan lifetime, string allocationStack, string disposeStack) + { + this.Id = guid; + this.Tag = tag; + this.Lifetime = lifetime; + this.AllocationStack = allocationStack; + this.DisposeStack = disposeStack; + } + } + + /// + /// Arguments for the event. + /// + public sealed class StreamDoubleDisposedEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Stack where the stream was allocated. + /// + public string AllocationStack { get; } + + /// + /// First dispose stack. + /// + public string DisposeStack1 { get; } + + /// + /// Second dispose stack. + /// + public string DisposeStack2 { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Stack of original allocation. + /// First dispose stack. + /// Second dispose stack. + public StreamDoubleDisposedEventArgs(Guid guid, string tag, string allocationStack, string disposeStack1, string disposeStack2) + { + this.Id = guid; + this.Tag = tag; + this.AllocationStack = allocationStack; + this.DisposeStack1 = disposeStack1; + this.DisposeStack2 = disposeStack2; + } + } + + /// + /// Arguments for the event. + /// + public sealed class StreamFinalizedEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Stack where the stream was allocated. + /// + public string AllocationStack { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Stack of original allocation. + public StreamFinalizedEventArgs(Guid guid, string tag, string allocationStack) + { + this.Id = guid; + this.Tag = tag; + this.AllocationStack = allocationStack; + } + } + + /// + /// Arguments for the event. + /// + public sealed class StreamConvertedToArrayEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Stack where ToArray was called. + /// + public string Stack { get; } + + /// + /// Length of stack. + /// + public long Length { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Stack of ToArray call. + /// Length of stream. + public StreamConvertedToArrayEventArgs(Guid guid, string tag, string stack, long length) + { + this.Id = guid; + this.Tag = tag; + this.Stack = stack; + this.Length = length; + } + } + + /// + /// Arguments for the event. + /// + public sealed class StreamOverCapacityEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Original allocation stack. + /// + public string AllocationStack { get; } + + /// + /// Requested capacity. + /// + public long RequestedCapacity { get; } + + /// + /// Maximum capacity. + /// + public long MaximumCapacity { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Requested capacity. + /// Maximum stream capacity of the manager. + /// Original allocation stack. + internal StreamOverCapacityEventArgs(Guid guid, string tag, long requestedCapacity, long maximumCapacity, string allocationStack) + { + this.Id = guid; + this.Tag = tag; + this.RequestedCapacity = requestedCapacity; + this.MaximumCapacity = maximumCapacity; + this.AllocationStack = allocationStack; + } + } + + /// + /// Arguments for the event. + /// + public sealed class BlockCreatedEventArgs : EventArgs + { + /// + /// How many bytes are currently in use from the small pool. + /// + public long SmallPoolInUse { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Number of bytes currently in use from the small pool. + internal BlockCreatedEventArgs(long smallPoolInUse) + { + this.SmallPoolInUse = smallPoolInUse; + } + } + + /// + /// Arguments for the events. + /// + public sealed class LargeBufferCreatedEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Whether the buffer was satisfied from the pool or not. + /// + public bool Pooled { get; } + + /// + /// Required buffer size. + /// + public long RequiredSize { get; } + + /// + /// How many bytes are in use from the large pool. + /// + public long LargePoolInUse { get; } + + /// + /// If the buffer was not satisfied from the pool, and is turned on, then. + /// this will contain the callstack of the allocation request. + /// + public string CallStack { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Required size of the new buffer. + /// How many bytes from the large pool are currently in use. + /// Whether the buffer was satisfied from the pool or not. + /// Callstack of the allocation, if it wasn't pooled. + internal LargeBufferCreatedEventArgs(Guid guid, string tag, long requiredSize, long largePoolInUse, bool pooled, string callStack) + { + this.RequiredSize = requiredSize; + this.LargePoolInUse = largePoolInUse; + this.Pooled = pooled; + this.Id = guid; + this.Tag = tag; + this.CallStack = callStack; + } + } + + /// + /// Arguments for the event. + /// + public sealed class BufferDiscardedEventArgs : EventArgs + { + /// + /// Unique ID for the stream. + /// + public Guid Id { get; } + + /// + /// Optional Tag for the event. + /// + public string Tag { get; } + + /// + /// Type of the buffer. + /// + public Events.MemoryStreamBufferType BufferType { get; } + + /// + /// The reason this buffer was discarded. + /// + public Events.MemoryStreamDiscardReason Reason { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Unique ID of the stream. + /// Tag of the stream. + /// Type of buffer being discarded. + /// The reason for the discard. + internal BufferDiscardedEventArgs(Guid guid, string tag, Events.MemoryStreamBufferType bufferType, Events.MemoryStreamDiscardReason reason) + { + this.Id = guid; + this.Tag = tag; + this.BufferType = bufferType; + this.Reason = reason; + } + } + + /// + /// Arguments for the event. + /// + public sealed class StreamLengthEventArgs : EventArgs + { + /// + /// Length of the stream. + /// + public long Length { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Length of the strength. + public StreamLengthEventArgs(long length) + { + this.Length = length; + } + } + + /// + /// Arguments for the event. + /// + public sealed class UsageReportEventArgs : EventArgs + { + /// + /// Bytes from the small pool currently in use. + /// + public long SmallPoolInUseBytes { get; } + + /// + /// Bytes from the small pool currently available. + /// + public long SmallPoolFreeBytes { get; } + + /// + /// Bytes from the large pool currently in use. + /// + public long LargePoolInUseBytes { get; } + + /// + /// Bytes from the large pool currently available. + /// + public long LargePoolFreeBytes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Bytes from the small pool currently in use. + /// Bytes from the small pool currently available. + /// Bytes from the large pool currently in use. + /// Bytes from the large pool currently available. + public UsageReportEventArgs( + long smallPoolInUseBytes, + long smallPoolFreeBytes, + long largePoolInUseBytes, + long largePoolFreeBytes) + { + this.SmallPoolInUseBytes = smallPoolInUseBytes; + this.SmallPoolFreeBytes = smallPoolFreeBytes; + this.LargePoolInUseBytes = largePoolInUseBytes; + this.LargePoolFreeBytes = largePoolFreeBytes; + } + } + } +} diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs.meta b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs.meta new file mode 100644 index 00000000..36b16e51 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/EventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 245f4b325bce62948854e163f9e6f56c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs new file mode 100644 index 00000000..53eeeb63 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs @@ -0,0 +1,258 @@ +// --------------------------------------------------------------------- +// Copyright (c) 2015 Microsoft +// +// 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. +// --------------------------------------------------------------------- + +namespace TEngine.IO +{ + using System; + using System.Diagnostics.Tracing; + + public sealed partial class RecyclableMemoryStreamManager + { + /// + /// ETW events for RecyclableMemoryStream. + /// + [EventSource(Name = "Microsoft-IO-RecyclableMemoryStream", Guid = "{B80CD4E4-890E-468D-9CBA-90EB7C82DFC7}")] + public sealed class Events : EventSource + { + /// + /// Static log object, through which all events are written. + /// + public static Events Writer = new(); + + /// + /// Type of buffer. + /// + public enum MemoryStreamBufferType + { + /// + /// Small block buffer. + /// + Small, + /// + /// Large pool buffer. + /// + Large + } + + /// + /// The possible reasons for discarding a buffer. + /// + public enum MemoryStreamDiscardReason + { + /// + /// Buffer was too large to be re-pooled. + /// + TooLarge, + /// + /// There are enough free bytes in the pool. + /// + EnoughFree + } + + /// + /// Logged when a stream object is created. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Requested size of the stream. + /// Actual size given to the stream from the pool. + [Event(1, Level = EventLevel.Verbose, Version = 2)] + public void MemoryStreamCreated(Guid guid, string tag, long requestedSize, long actualSize) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(1, guid, tag ?? string.Empty, requestedSize, actualSize); + } + } + + /// + /// Logged when the stream is disposed. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Lifetime in milliseconds of the stream + /// Call stack of initial allocation. + /// Call stack of the dispose. + [Event(2, Level = EventLevel.Verbose, Version = 3)] + public void MemoryStreamDisposed(Guid guid, string tag, long lifetimeMs, string allocationStack, string disposeStack) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(2, guid, tag ?? string.Empty, lifetimeMs, allocationStack ?? string.Empty, disposeStack ?? string.Empty); + } + } + + /// + /// Logged when the stream is disposed for the second time. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of initial allocation. + /// Call stack of the first dispose. + /// Call stack of the second dispose. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(3, Level = EventLevel.Critical)] + public void MemoryStreamDoubleDispose(Guid guid, string tag, string allocationStack, string disposeStack1, + string disposeStack2) + { + if (this.IsEnabled()) + { + this.WriteEvent(3, guid, tag ?? string.Empty, allocationStack ?? string.Empty, + disposeStack1 ?? string.Empty, disposeStack2 ?? string.Empty); + } + } + + /// + /// Logged when a stream is finalized. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of initial allocation. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(4, Level = EventLevel.Error)] + public void MemoryStreamFinalized(Guid guid, string tag, string allocationStack) + { + if (this.IsEnabled()) + { + WriteEvent(4, guid, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } + + /// + /// Logged when ToArray is called on a stream. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of the ToArray call. + /// Length of stream. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(5, Level = EventLevel.Verbose, Version = 2)] + public void MemoryStreamToArray(Guid guid, string tag, string stack, long size) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(5, guid, tag ?? string.Empty, stack ?? string.Empty, size); + } + } + + /// + /// Logged when the RecyclableMemoryStreamManager is initialized. + /// + /// Size of blocks, in bytes. + /// Size of the large buffer multiple, in bytes. + /// Maximum buffer size, in bytes. + [Event(6, Level = EventLevel.Informational)] + public void MemoryStreamManagerInitialized(int blockSize, int largeBufferMultiple, int maximumBufferSize) + { + if (this.IsEnabled()) + { + WriteEvent(6, blockSize, largeBufferMultiple, maximumBufferSize); + } + } + + /// + /// Logged when a new block is created. + /// + /// Number of bytes in the small pool currently in use. + [Event(7, Level = EventLevel.Warning, Version = 2)] + public void MemoryStreamNewBlockCreated(long smallPoolInUseBytes) + { + if (this.IsEnabled(EventLevel.Warning, EventKeywords.None)) + { + WriteEvent(7, smallPoolInUseBytes); + } + } + + /// + /// Logged when a new large buffer is created. + /// + /// Requested size. + /// Number of bytes in the large pool in use. + [Event(8, Level = EventLevel.Warning, Version = 3)] + public void MemoryStreamNewLargeBufferCreated(long requiredSize, long largePoolInUseBytes) + { + if (this.IsEnabled(EventLevel.Warning, EventKeywords.None)) + { + WriteEvent(8, requiredSize, largePoolInUseBytes); + } + } + + /// + /// Logged when a buffer is created that is too large to pool. + /// + /// Unique stream ID. + /// A temporary ID for this stream, usually indicates current usage. + /// Size requested by the caller. + /// Call stack of the requested stream. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(9, Level = EventLevel.Verbose, Version = 3)] + public void MemoryStreamNonPooledLargeBufferCreated(Guid guid, string tag, long requiredSize, string allocationStack) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(9, guid, tag ?? string.Empty, requiredSize, allocationStack ?? string.Empty); + } + } + + /// + /// Logged when a buffer is discarded (not put back in the pool, but given to GC to clean up). + /// + /// Unique stream ID. + /// A temporary ID for this stream, usually indicates current usage. + /// Type of the buffer being discarded. + /// Reason for the discard. + /// Number of free small pool blocks. + /// Bytes free in the small pool. + /// Bytes in use from the small pool. + /// Number of free large pool blocks. + /// Bytes free in the large pool. + /// Bytes in use from the large pool. + [Event(10, Level = EventLevel.Warning, Version = 2)] + public void MemoryStreamDiscardBuffer(Guid guid, string tag, MemoryStreamBufferType bufferType, + MemoryStreamDiscardReason reason, long smallBlocksFree, long smallPoolBytesFree, long smallPoolBytesInUse, long largeBlocksFree, long largePoolBytesFree, long largePoolBytesInUse) + { + if (this.IsEnabled(EventLevel.Warning, EventKeywords.None)) + { + WriteEvent(10, guid, tag ?? string.Empty, bufferType, reason, smallBlocksFree, smallPoolBytesFree, smallPoolBytesInUse, largeBlocksFree, largePoolBytesFree, largePoolBytesInUse); + } + } + + /// + /// Logged when a stream grows beyond the maximum capacity. + /// + /// Unique stream ID + /// The requested capacity. + /// Maximum capacity, as configured by RecyclableMemoryStreamManager. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack for the capacity request. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(11, Level = EventLevel.Error, Version = 3)] + public void MemoryStreamOverCapacity(Guid guid, string tag, long requestedCapacity, long maxCapacity, string allocationStack) + { + if (this.IsEnabled()) + { + WriteEvent(11, guid, tag ?? string.Empty, requestedCapacity, maxCapacity, allocationStack ?? string.Empty); + } + } + } + } +} diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs.meta b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs.meta new file mode 100644 index 00000000..a916c8e6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/Events.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25287e2fc3cca8444a5514df30ac7c13 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs new file mode 100644 index 00000000..9ab892f3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs @@ -0,0 +1,1613 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015-2016 Microsoft +// +// 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. + +#pragma warning disable CS8604 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8600 +namespace TEngine.IO +{ + using System; + using System.Buffers; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + + /// + /// MemoryStream implementation that deals with pooling and managing memory streams which use potentially large + /// buffers. + /// + /// + /// This class works in tandem with the to supply MemoryStream-derived + /// objects to callers, while avoiding these specific problems: + /// + /// + /// LOH allocations + /// Since all large buffers are pooled, they will never incur a Gen2 GC + /// + /// + /// Memory wasteA standard memory stream doubles its size when it runs out of room. This + /// leads to continual memory growth as each stream approaches the maximum allowed size. + /// + /// + /// Memory copying + /// Each time a MemoryStream grows, all the bytes are copied into new buffers. + /// This implementation only copies the bytes when is called. + /// + /// + /// Memory fragmentation + /// By using homogeneous buffer sizes, it ensures that blocks of memory + /// can be easily reused. + /// + /// + /// + /// + /// The stream is implemented on top of a series of uniformly-sized blocks. As the stream's length grows, + /// additional blocks are retrieved from the memory manager. It is these blocks that are pooled, not the stream + /// object itself. + /// + /// + /// The biggest wrinkle in this implementation is when is called. This requires a single + /// contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks + /// are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled, + /// split by size--they are multiples/exponentials of a chunk size (1 MB by default). + /// + /// + /// Once a large buffer is assigned to the stream the small blocks are NEVER again used for this stream. All operations take place on the + /// large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers + /// are maintained in the stream until the stream is disposed (unless AggressiveBufferReturn is enabled in the stream manager). + /// + /// + /// A further wrinkle is what happens when the stream is longer than the maximum allowable array length under .NET. This is allowed + /// when only blocks are in use, and only the Read/Write APIs are used. Once a stream grows to this size, any attempt to convert it + /// to a single buffer will result in an exception. Similarly, if a stream is already converted to use a single larger buffer, then + /// it cannot grow beyond the limits of the maximum allowable array size. + /// + /// + /// Any method that modifies the stream has the potential to throw an OutOfMemoryException, either because + /// the stream is beyond the limits set in RecyclableStreamManager, or it would result in a buffer larger than + /// the maximum array size supported by .NET. + /// + /// + public sealed class RecyclableMemoryStream : MemoryStream, IBufferWriter + { + private static readonly byte[] emptyArray = new byte[0]; + + /// + /// All of these blocks must be the same size. + /// + private readonly List blocks; + + private readonly Guid id; + + private readonly RecyclableMemoryStreamManager memoryManager; + + private readonly string tag; + + private readonly long creationTimestamp; + + /// + /// This list is used to store buffers once they're replaced by something larger. + /// This is for the cases where you have users of this class that may hold onto the buffers longer + /// than they should and you want to prevent race conditions which could corrupt the data. + /// + private List dirtyBuffers; + + private bool disposed; + + /// + /// This is only set by GetBuffer() if the necessary buffer is larger than a single block size, or on + /// construction if the caller immediately requests a single large buffer. + /// + /// If this field is non-null, it contains the concatenation of the bytes found in the individual + /// blocks. Once it is created, this (or a larger) largeBuffer will be used for the life of the stream. + /// + private byte[] largeBuffer; + + /// + /// Unique identifier for this stream across its entire lifetime. + /// + /// Object has been disposed. + internal Guid Id + { + get + { + this.CheckDisposed(); + return this.id; + } + } + + /// + /// A temporary identifier for the current usage of this stream. + /// + /// Object has been disposed. + internal string Tag + { + get + { + this.CheckDisposed(); + return this.tag; + } + } + + /// + /// Gets the memory manager being used by this stream. + /// + /// Object has been disposed. + internal RecyclableMemoryStreamManager MemoryManager + { + get + { + this.CheckDisposed(); + return this.memoryManager; + } + } + + /// + /// Callstack of the constructor. It is only set if is true, + /// which should only be in debugging situations. + /// + internal string AllocationStack { get; } + + /// + /// Callstack of the call. It is only set if is true, + /// which should only be in debugging situations. + /// + internal string DisposeStack { get; private set; } + + #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager) + : this(memoryManager, Guid.NewGuid(), null, 0, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + /// A unique identifier which can be used to trace usages of the stream. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id) + : this(memoryManager, id, null, 0, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + /// A string identifying this stream for logging and debugging purposes. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag) + : this(memoryManager, Guid.NewGuid(), tag, 0, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag) + : this(memoryManager, id, tag, 0, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager + /// A string identifying this stream for logging and debugging purposes. + /// The initial requested size to prevent future allocations. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize) + : this(memoryManager, Guid.NewGuid(), tag, requestedSize, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + /// A string identifying this stream for logging and debugging purposes. + /// The initial requested size to prevent future allocations. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, long requestedSize) + : this(memoryManager, Guid.NewGuid(), tag, requestedSize, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes. + /// The initial requested size to prevent future allocations. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, int requestedSize) + : this(memoryManager, id, tag, (long)requestedSize) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes. + /// The initial requested size to prevent future allocations. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, long requestedSize) + : this(memoryManager, id, tag, requestedSize, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The memory manager. + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes. + /// The initial requested size to prevent future allocations. + /// An initial buffer to use. This buffer will be owned by the stream and returned to the memory manager upon Dispose. + internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, long requestedSize, byte[] initialLargeBuffer) + : base(emptyArray) + { + this.memoryManager = memoryManager; + this.id = id; + this.tag = tag; + this.blocks = new List(); + this.creationTimestamp = Stopwatch.GetTimestamp(); + + var actualRequestedSize = Math.Max(requestedSize, this.memoryManager.BlockSize); + + if (initialLargeBuffer == null) + { + this.EnsureCapacity(actualRequestedSize); + } + else + { + this.largeBuffer = initialLargeBuffer; + } + + if (this.memoryManager.GenerateCallStacks) + { + this.AllocationStack = Environment.StackTrace; + } + + this.memoryManager.ReportStreamCreated(this.id, this.tag, requestedSize, actualRequestedSize); + this.memoryManager.ReportUsageReport(); + } + #endregion + + #region Dispose and Finalize + /// + /// The finalizer will be called when a stream is not disposed properly. + /// + /// Failing to dispose indicates a bug in the code using streams. Care should be taken to properly account for stream lifetime. + ~RecyclableMemoryStream() + { + this.Dispose(false); + } + + /// + /// Returns the memory used by this stream back to the pool. + /// + /// Whether we're disposing (true), or being called by the finalizer (false). + protected override void Dispose(bool disposing) + { + if (this.disposed) + { + string doubleDisposeStack = null; + if (this.memoryManager.GenerateCallStacks) + { + doubleDisposeStack = Environment.StackTrace; + } + + this.memoryManager.ReportStreamDoubleDisposed(this.id, this.tag, this.AllocationStack, this.DisposeStack, doubleDisposeStack); + return; + } + + this.disposed = true; + var lifetime = TimeSpan.FromTicks((Stopwatch.GetTimestamp() - this.creationTimestamp) * TimeSpan.TicksPerSecond / Stopwatch.Frequency); + + if (this.memoryManager.GenerateCallStacks) + { + this.DisposeStack = Environment.StackTrace; + } + + this.memoryManager.ReportStreamDisposed(this.id, this.tag, lifetime, this.AllocationStack, this.DisposeStack); + + if (disposing) + { + GC.SuppressFinalize(this); + } + else + { + // We're being finalized. + this.memoryManager.ReportStreamFinalized(this.id, this.tag, this.AllocationStack); + + if (AppDomain.CurrentDomain.IsFinalizingForUnload()) + { + // If we're being finalized because of a shutdown, don't go any further. + // We have no idea what's already been cleaned up. Triggering events may cause + // a crash. + base.Dispose(disposing); + return; + } + } + + this.memoryManager.ReportStreamLength(this.length); + + if (this.largeBuffer != null) + { + this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.id, this.tag); + } + + if (this.dirtyBuffers != null) + { + foreach (var buffer in this.dirtyBuffers) + { + this.memoryManager.ReturnLargeBuffer(buffer, this.id, this.tag); + } + } + + this.memoryManager.ReturnBlocks(this.blocks, this.id, this.tag); + this.memoryManager.ReportUsageReport(); + this.blocks.Clear(); + + base.Dispose(disposing); + } + + /// + /// Equivalent to Dispose. + /// + public override void Close() + { + this.Dispose(true); + } + #endregion + + #region MemoryStream overrides + /// + /// Gets or sets the capacity. + /// + /// + /// + /// Capacity is always in multiples of the memory manager's block size, unless + /// the large buffer is in use. Capacity never decreases during a stream's lifetime. + /// Explicitly setting the capacity to a lower value than the current value will have no effect. + /// This is because the buffers are all pooled by chunks and there's little reason to + /// allow stream truncation. + /// + /// + /// Writing past the current capacity will cause to automatically increase, until MaximumStreamCapacity is reached. + /// + /// + /// If the capacity is larger than int.MaxValue, then InvalidOperationException will be thrown. If you anticipate using + /// larger streams, use the property instead. + /// + /// + /// Object has been disposed. + /// Capacity is larger than int.MaxValue. + public override int Capacity + { + get + { + this.CheckDisposed(); + if (this.largeBuffer != null) + { + return this.largeBuffer.Length; + } + + long size = (long)this.blocks.Count * this.memoryManager.BlockSize; + if (size > int.MaxValue) + { + throw new InvalidOperationException($"{nameof(Capacity)} is larger than int.MaxValue. Use {nameof(Capacity64)} instead."); + } + return (int)size; + } + set + { + this.Capacity64 = value; + } + } + + /// + /// Returns a 64-bit version of capacity, for streams larger than int.MaxValue in length. + /// + public long Capacity64 + { + get + { + this.CheckDisposed(); + if (this.largeBuffer != null) + { + return this.largeBuffer.Length; + } + + long size = (long)this.blocks.Count * this.memoryManager.BlockSize; + return size; + } + set + { + this.CheckDisposed(); + this.EnsureCapacity(value); + } + } + + private long length; + + /// + /// Gets the number of bytes written to this stream. + /// + /// Object has been disposed. + /// If the buffer has already been converted to a large buffer, then the maximum length is limited by the maximum allowed array length in .NET. + public override long Length + { + get + { + this.CheckDisposed(); + return this.length; + } + } + + private long position; + + /// + /// Gets the current position in the stream. + /// + /// Object has been disposed. + /// A negative value was passed. + /// Stream is in large-buffer mode, but an attempt was made to set the position past the maximum allowed array length. + /// If the buffer has already been converted to a large buffer, then the maximum length (and thus position) is limited by the maximum allowed array length in .NET. + public override long Position + { + get + { + this.CheckDisposed(); + return this.position; + } + set + { + this.CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be non-negative."); + } + + if (this.largeBuffer != null && value > RecyclableMemoryStreamManager.MaxArrayLength) + { + throw new InvalidOperationException($"Once the stream is converted to a single large buffer, position cannot be set past {RecyclableMemoryStreamManager.MaxArrayLength}."); + } + this.position = value; + } + } + + /// + /// Whether the stream can currently read. + /// + public override bool CanRead => !this.Disposed; + + /// + /// Whether the stream can currently seek. + /// + public override bool CanSeek => !this.Disposed; + + /// + /// Always false. + /// + public override bool CanTimeout => false; + + /// + /// Whether the stream can currently write. + /// + public override bool CanWrite => !this.Disposed; + + /// + /// Returns a single buffer containing the contents of the stream. + /// The buffer may be longer than the stream length. + /// + /// A byte[] buffer. + /// IMPORTANT: Doing a after calling GetBuffer invalidates the buffer. The old buffer is held onto + /// until is called, but the next time GetBuffer is called, a new buffer from the pool will be required. + /// Object has been disposed. + /// stream is too large for a contiguous buffer. + public override byte[] GetBuffer() + { + this.CheckDisposed(); + + if (this.largeBuffer != null) + { + return this.largeBuffer; + } + + if (this.blocks.Count == 1) + { + return this.blocks[0]; + } + + // Buffer needs to reflect the capacity, not the length, because + // it's possible that people will manipulate the buffer directly + // and set the length afterward. Capacity sets the expectation + // for the size of the buffer. + + var newBuffer = this.memoryManager.GetLargeBuffer(this.Capacity64, this.id, this.tag); + + // InternalRead will check for existence of largeBuffer, so make sure we + // don't set it until after we've copied the data. + this.AssertLengthIsSmall(); + this.InternalRead(newBuffer, 0, (int)this.length, 0); + this.largeBuffer = newBuffer; + + if (this.blocks.Count > 0 && this.memoryManager.AggressiveBufferReturn) + { + this.memoryManager.ReturnBlocks(this.blocks, this.id, this.tag); + this.blocks.Clear(); + } + + return this.largeBuffer; + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 + /// + public override void CopyTo(Stream destination, int bufferSize) + { + WriteTo(destination, this.position, this.length - this.position); + } +#endif + + /// Asynchronously reads all the bytes from the current position in this stream and writes them to another stream. + /// The stream to which the contents of the current stream will be copied. + /// This parameter is ignored. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous copy operation. + /// + /// is . + /// Either the current stream or the destination stream is disposed. + /// The current stream does not support reading, or the destination stream does not support writing. + /// Similarly to MemoryStream's behavior, CopyToAsync will adjust the source stream's position by the number of bytes written to the destination stream, as a Read would do. + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + if (destination == null) + { + throw new ArgumentNullException(nameof(destination)); + } + + this.CheckDisposed(); + + if (this.length == 0) + { + return Task.CompletedTask; + } + + long startPos = this.position; + var count = this.length - startPos; + this.position += count; + + if (destination is MemoryStream destinationRMS) + { + this.WriteTo(destinationRMS, startPos, count); + return Task.CompletedTask; + } + else + { + if (this.largeBuffer == null) + { + if (this.blocks.Count == 1) + { + AssertLengthIsSmall(); + return destination.WriteAsync(this.blocks[0], (int)startPos, (int)count, cancellationToken); + } + else + { + return CopyToAsyncImpl(destination, this.GetBlockAndRelativeOffset(startPos), count, this.blocks, cancellationToken); + } + } + else + { + AssertLengthIsSmall(); + return destination.WriteAsync(this.largeBuffer, (int)startPos, (int)count, cancellationToken); + } + } + + static async Task CopyToAsyncImpl(Stream destination, BlockAndOffset blockAndOffset, long count, List blocks, CancellationToken cancellationToken) + { + var bytesRemaining = count; + int currentBlock = blockAndOffset.Block; + var currentOffset = blockAndOffset.Offset; + while (bytesRemaining > 0) + { + byte[] block = blocks[currentBlock]; + int amountToCopy = (int)Math.Min(block.Length - currentOffset, bytesRemaining); + await destination.WriteAsync(block, currentOffset, amountToCopy, cancellationToken); + bytesRemaining -= amountToCopy; + ++currentBlock; + currentOffset = 0; + } + } + } + + private byte[] bufferWriterTempBuffer; + + /// + /// Notifies the stream that bytes were written to the buffer returned by or . + /// Seeks forward by bytes. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + /// How many bytes to advance. + /// Object has been disposed. + /// is negative. + /// is larger than the size of the previously requested buffer. + public void Advance(int count) + { + this.CheckDisposed(); + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), $"{nameof(count)} must be non-negative."); + } + + byte[] buffer = this.bufferWriterTempBuffer; + if (buffer != null) + { + if (count > buffer.Length) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + + this.Write(buffer, 0, count); + this.ReturnTempBuffer(buffer); + this.bufferWriterTempBuffer = null; + } + else + { + long bufferSize = this.largeBuffer == null + ? this.memoryManager.BlockSize - this.GetBlockAndRelativeOffset(this.position).Offset + : this.largeBuffer.Length - this.position; + + if (count > bufferSize) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {bufferSize}."); + } + + this.position += count; + this.length = Math.Max(this.position, this.length); + } + } + + private void ReturnTempBuffer(byte[] buffer) + { + if (buffer.Length == this.memoryManager.BlockSize) + { + this.memoryManager.ReturnBlock(buffer, this.id, this.tag); + } + else + { + this.memoryManager.ReturnLargeBuffer(buffer, this.id, this.tag); + } + } + + /// + /// + /// IMPORTANT: Calling Write(), GetBuffer(), TryGetBuffer(), Seek(), GetLength(), Advance(), + /// or setting Position after calling GetMemory() invalidates the memory. + /// + public Memory GetMemory(int sizeHint = 0) => this.GetWritableBuffer(sizeHint); + + /// + /// + /// IMPORTANT: Calling Write(), GetBuffer(), TryGetBuffer(), Seek(), GetLength(), Advance(), + /// or setting Position after calling GetSpan() invalidates the span. + /// + public Span GetSpan(int sizeHint = 0) => this.GetWritableBuffer(sizeHint); + + /// + /// When callers to GetSpan() or GetMemory() request a buffer that is larger than the remaining size of the current block + /// this method return a temp buffer. When Advance() is called, that temp buffer is then copied into the stream. + /// + private ArraySegment GetWritableBuffer(int sizeHint) + { + this.CheckDisposed(); + if (sizeHint < 0) + { + throw new ArgumentOutOfRangeException(nameof(sizeHint), $"{nameof(sizeHint)} must be non-negative."); + } + + var minimumBufferSize = Math.Max(sizeHint, 1); + + this.EnsureCapacity(this.position + minimumBufferSize); + if (this.bufferWriterTempBuffer != null) + { + this.ReturnTempBuffer(this.bufferWriterTempBuffer); + this.bufferWriterTempBuffer = null; + } + + if (this.largeBuffer != null) + { + return new ArraySegment(this.largeBuffer, (int)this.position, this.largeBuffer.Length - (int)this.position); + } + + BlockAndOffset blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + int remainingBytesInBlock = this.MemoryManager.BlockSize - blockAndOffset.Offset; + if (remainingBytesInBlock >= minimumBufferSize) + { + return new ArraySegment(this.blocks[blockAndOffset.Block], blockAndOffset.Offset, this.MemoryManager.BlockSize - blockAndOffset.Offset); + } + + this.bufferWriterTempBuffer = minimumBufferSize > this.memoryManager.BlockSize ? + this.memoryManager.GetLargeBuffer(minimumBufferSize, this.id, this.tag) : + this.memoryManager.GetBlock(); + + return new ArraySegment(this.bufferWriterTempBuffer); + } + + /// + /// Returns a sequence containing the contents of the stream. + /// + /// A ReadOnlySequence of bytes. + /// IMPORTANT: Calling Write(), GetMemory(), GetSpan(), Dispose(), or Close() after calling GetReadOnlySequence() invalidates the sequence. + /// Object has been disposed. + public ReadOnlySequence GetReadOnlySequence() + { + this.CheckDisposed(); + + if (this.largeBuffer != null) + { + AssertLengthIsSmall(); + return new ReadOnlySequence(this.largeBuffer, 0, (int)this.length); + } + + if (this.blocks.Count == 1) + { + AssertLengthIsSmall(); + return new ReadOnlySequence(this.blocks[0], 0, (int)this.length); + } + + var first = new BlockSegment(this.blocks[0]); + var last = first; + + for (int blockIdx = 1; last.RunningIndex + last.Memory.Length < this.length; blockIdx++) + { + last = last.Append(this.blocks[blockIdx]); + } + + return new ReadOnlySequence(first, 0, last, (int)(this.length - last.RunningIndex)); + } + + private sealed class BlockSegment : ReadOnlySequenceSegment + { + public BlockSegment(Memory memory) => Memory = memory; + + public BlockSegment Append(Memory memory) + { + var nextSegment = new BlockSegment(memory) { RunningIndex = RunningIndex + Memory.Length }; + Next = nextSegment; + return nextSegment; + } + } + + /// + /// Returns an ArraySegment that wraps a single buffer containing the contents of the stream. + /// + /// An ArraySegment containing a reference to the underlying bytes. + /// Returns if a buffer can be returned; otherwise, . + public override bool TryGetBuffer(out ArraySegment buffer) + { + this.CheckDisposed(); + + try + { + if (this.length <= RecyclableMemoryStreamManager.MaxArrayLength) + { + buffer = new ArraySegment(this.GetBuffer(), 0, (int)this.Length); + return true; + } + } + catch (OutOfMemoryException) + { + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 + buffer = ArraySegment.Empty; +#else + buffer = new ArraySegment(); +#endif + return false; + } + + /// + /// Returns a new array with a copy of the buffer's contents. You should almost certainly be using combined with the to + /// access the bytes in this stream. Calling ToArray will destroy the benefits of pooled buffers, but it is included + /// for the sake of completeness. + /// + /// Object has been disposed. + /// The current object disallows ToArray calls. + /// The length of the stream is too long for a contiguous array. +#pragma warning disable CS0809 + [Obsolete("This method has degraded performance vs. GetBuffer and should be avoided.")] + public override byte[] ToArray() + { + this.CheckDisposed(); + + string stack = this.memoryManager.GenerateCallStacks ? Environment.StackTrace : null; + this.memoryManager.ReportStreamToArray(this.id, this.tag, stack, this.length); + + if (this.memoryManager.ThrowExceptionOnToArray) + { + throw new NotSupportedException("The underlying RecyclableMemoryStreamManager is configured to not allow calls to ToArray."); + } + + var newBuffer = new byte[this.Length]; + + Debug.Assert(this.length <= int.MaxValue); + this.InternalRead(newBuffer, 0, (int)this.length, 0); + + return newBuffer; + } +#pragma warning restore CS0809 + + /// + /// Reads from the current position into the provided buffer. + /// + /// Destination buffer. + /// Offset into buffer at which to start placing the read bytes. + /// Number of bytes to read. + /// The number of bytes read. + /// buffer is null. + /// offset or count is less than 0. + /// offset subtracted from the buffer length is less than count. + /// Object has been disposed. + public override int Read(byte[] buffer, int offset, int count) + { + return this.SafeRead(buffer, offset, count, ref this.position); + } + + /// + /// Reads from the specified position into the provided buffer. + /// + /// Destination buffer. + /// Offset into buffer at which to start placing the read bytes. + /// Number of bytes to read. + /// Position in the stream to start reading from. + /// The number of bytes read. + /// is null. + /// or is less than 0. + /// subtracted from the buffer length is less than . + /// Object has been disposed. + /// Stream position is beyond int.MaxValue. + public int SafeRead(byte[] buffer, int offset, int count, ref int streamPosition) + { + long longPosition = streamPosition; + var retVal = this.SafeRead(buffer, offset, count, ref longPosition); + if (longPosition > int.MaxValue) + { + throw new InvalidOperationException("Stream position is beyond int.MaxValue. Use SafeRead(byte[], int, int, ref long) override."); + } + streamPosition = (int)longPosition; + return retVal; + } + + /// + /// Reads from the specified position into the provided buffer. + /// + /// Destination buffer. + /// Offset into buffer at which to start placing the read bytes. + /// Number of bytes to read. + /// Position in the stream to start reading from. + /// The number of bytes read. + /// is null. + /// or is less than 0. + /// subtracted from the buffer length is less than . + /// Object has been disposed. + public int SafeRead(byte[] buffer, int offset, int count, ref long streamPosition) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), $"{nameof(offset)} cannot be negative."); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), $"{nameof(count)} cannot be negative."); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException($"{nameof(buffer)} length must be at least {nameof(offset)} + {nameof(count)}."); + } + + int amountRead = this.InternalRead(buffer, offset, count, streamPosition); + streamPosition += amountRead; + return amountRead; + } + + /// + /// Reads from the current position into the provided buffer. + /// + /// Destination buffer. + /// The number of bytes read. + /// Object has been disposed. +#if NETSTANDARD2_0 || NET462 + public int Read(Span buffer) +#else + public override int Read(Span buffer) +#endif + { + return this.SafeRead(buffer, ref this.position); + } + + /// + /// Reads from the specified position into the provided buffer. + /// + /// Destination buffer. + /// Position in the stream to start reading from. + /// The number of bytes read. + /// Object has been disposed. + /// Stream position is beyond int.MaxValue. + public int SafeRead(Span buffer, ref int streamPosition) + { + long longPosition = streamPosition; + int retVal = this.SafeRead(buffer, ref longPosition); + if (longPosition > int.MaxValue) + { + throw new InvalidOperationException("Stream position is beyond int.MaxValue. Use SafeRead(Span, ref long) override."); + } + streamPosition = (int)longPosition; + return retVal; + } + + /// + /// Reads from the specified position into the provided buffer. + /// + /// Destination buffer. + /// Position in the stream to start reading from. + /// The number of bytes read. + /// Object has been disposed. + public int SafeRead(Span buffer, ref long streamPosition) + { + this.CheckDisposed(); + + int amountRead = this.InternalRead(buffer, streamPosition); + streamPosition += amountRead; + return amountRead; + } + + /// + /// Writes the buffer to the stream. + /// + /// Source buffer. + /// Start position. + /// Number of bytes to write. + /// buffer is null. + /// offset or count is negative. + /// buffer.Length - offset is not less than count. + /// Object has been disposed. + public override void Write(byte[] buffer, int offset, int count) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), offset, + $"{nameof(offset)} must be in the range of 0 - {nameof(buffer)}.{nameof(buffer.Length)}-1."); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), count, $"{nameof(count)} must be non-negative."); + } + + if (count + offset > buffer.Length) + { + throw new ArgumentException($"{nameof(count)} must be greater than {nameof(buffer)}.{nameof(buffer.Length)} - {nameof(offset)}."); + } + + int blockSize = this.memoryManager.BlockSize; + long end = (long)this.position + count; + + this.EnsureCapacity(end); + + if (this.largeBuffer == null) + { + int bytesRemaining = count; + int bytesWritten = 0; + var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + + while (bytesRemaining > 0) + { + byte[] currentBlock = this.blocks[blockAndOffset.Block]; + int remainingInBlock = blockSize - blockAndOffset.Offset; + int amountToWriteInBlock = Math.Min(remainingInBlock, bytesRemaining); + + Buffer.BlockCopy(buffer, offset + bytesWritten, currentBlock, blockAndOffset.Offset, + amountToWriteInBlock); + + bytesRemaining -= amountToWriteInBlock; + bytesWritten += amountToWriteInBlock; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + } + else + { + Buffer.BlockCopy(buffer, offset, this.largeBuffer, (int)this.position, count); + } + this.position = end; + this.length = Math.Max(this.position, this.length); + } + + /// + /// Writes the buffer to the stream. + /// + /// Source buffer. + /// buffer is null. + /// Object has been disposed. +#if NETSTANDARD2_0 || NET462 + public void Write(ReadOnlySpan source) +#else + public override void Write(ReadOnlySpan source) +#endif + + { + this.CheckDisposed(); + + int blockSize = this.memoryManager.BlockSize; + long end = (long)this.position + source.Length; + + this.EnsureCapacity(end); + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + + while (source.Length > 0) + { + byte[] currentBlock = this.blocks[blockAndOffset.Block]; + int remainingInBlock = blockSize - blockAndOffset.Offset; + int amountToWriteInBlock = Math.Min(remainingInBlock, source.Length); + + source.Slice(0, amountToWriteInBlock) + .CopyTo(currentBlock.AsSpan(blockAndOffset.Offset)); + + source = source.Slice(amountToWriteInBlock); + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + } + else + { + source.CopyTo(this.largeBuffer.AsSpan((int)this.position)); + } + this.position = end; + this.length = Math.Max(this.position, this.length); + } + + /// + /// Returns a useful string for debugging. This should not normally be called in actual production code. + /// + public override string ToString() + { + if (!this.disposed) + { + return $"Id = {this.Id}, Tag = {this.Tag}, Length = {this.Length:N0} bytes"; + } + else + { + // Avoid properties because of the dispose check, but the fields themselves are not cleared. + return $"Disposed: Id = {this.id}, Tag = {this.tag}, Final Length: {this.length:N0} bytes"; + } + } + + /// + /// Writes a single byte to the current position in the stream. + /// + /// byte value to write. + /// Object has been disposed. + public override void WriteByte(byte value) + { + this.CheckDisposed(); + + long end = (long)this.position + 1; + + if (this.largeBuffer == null) + { + var blockSize = this.memoryManager.BlockSize; + + var block = (int)Math.DivRem(this.position, blockSize, out var index); + + if (block >= this.blocks.Count) + { + this.EnsureCapacity(end); + } + + this.blocks[block][index] = value; + } + else + { + if (this.position >= this.largeBuffer.Length) + { + this.EnsureCapacity(end); + } + + this.largeBuffer[this.position] = value; + } + + this.position = end; + + if (this.position > this.length) + { + this.length = this.position; + } + } + + /// + /// Reads a single byte from the current position in the stream. + /// + /// The byte at the current position, or -1 if the position is at the end of the stream. + /// Object has been disposed. + public override int ReadByte() + { + return this.SafeReadByte(ref this.position); + } + + /// + /// Reads a single byte from the specified position in the stream. + /// + /// The position in the stream to read from. + /// The byte at the current position, or -1 if the position is at the end of the stream. + /// Object has been disposed. + /// Stream position is beyond int.MaxValue. + public int SafeReadByte(ref int streamPosition) + { + long longPosition = streamPosition; + int retVal = this.SafeReadByte(ref longPosition); + if (longPosition > int.MaxValue) + { + throw new InvalidOperationException("Stream position is beyond int.MaxValue. Use SafeReadByte(ref long) override."); + } + streamPosition = (int)longPosition; + return retVal; + } + + /// + /// Reads a single byte from the specified position in the stream. + /// + /// The position in the stream to read from. + /// The byte at the current position, or -1 if the position is at the end of the stream. + /// Object has been disposed. + public int SafeReadByte(ref long streamPosition) + { + this.CheckDisposed(); + if (streamPosition == this.length) + { + return -1; + } + byte value; + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(streamPosition); + value = this.blocks[blockAndOffset.Block][blockAndOffset.Offset]; + } + else + { + value = this.largeBuffer[streamPosition]; + } + streamPosition++; + return value; + } + + /// + /// Sets the length of the stream. + /// + /// value is negative or larger than . + /// Object has been disposed. + public override void SetLength(long value) + { + this.CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be non-negative."); + } + + this.EnsureCapacity(value); + + this.length = value; + if (this.position > value) + { + this.position = value; + } + } + + /// + /// Sets the position to the offset from the seek location. + /// + /// How many bytes to move. + /// From where. + /// The new position. + /// Object has been disposed. + /// is larger than . + /// Invalid seek origin. + /// Attempt to set negative position. + public override long Seek(long offset, SeekOrigin loc) + { + this.CheckDisposed(); + long newPosition = loc switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => offset + this.position, + SeekOrigin.End => offset + this.length, + _ => throw new ArgumentException("Invalid seek origin.", nameof(loc)), + }; + if (newPosition < 0) + { + throw new IOException("Seek before beginning."); + } + this.position = newPosition; + return this.position; + } + + /// + /// Synchronously writes this stream's bytes to the argument stream. + /// + /// Destination stream. + /// Important: This does a synchronous write, which may not be desired in some situations. + /// is null. + /// Object has been disposed. + public override void WriteTo(Stream stream) + { + this.WriteTo(stream, 0, this.length); + } + + /// + /// Synchronously writes this stream's bytes, starting at offset, for count bytes, to the argument stream. + /// + /// Destination stream. + /// Offset in source. + /// Number of bytes to write. + /// is null. + /// + /// is less than 0, or + is beyond this 's length. + /// + /// Object has been disposed. + public void WriteTo(Stream stream, int offset, int count) + { + this.WriteTo(stream, (long)offset, (long)count); + } + + /// + /// Synchronously writes this stream's bytes, starting at offset, for count bytes, to the argument stream. + /// + /// Destination stream. + /// Offset in source. + /// Number of bytes to write. + /// is null. + /// + /// is less than 0, or + is beyond this 's length. + /// + /// Object has been disposed. + public void WriteTo(Stream stream, long offset, long count) + { + this.CheckDisposed(); + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (offset < 0 || offset + count > this.length) + { + throw new ArgumentOutOfRangeException( + message: $"{nameof(offset)} must not be negative and {nameof(offset)} + {nameof(count)} must not exceed the length of the {nameof(stream)}.", + innerException: null); + } + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(offset); + long bytesRemaining = count; + int currentBlock = blockAndOffset.Block; + int currentOffset = blockAndOffset.Offset; + + while (bytesRemaining > 0) + { + byte[] block = this.blocks[currentBlock]; + int amountToCopy = (int)Math.Min((long)block.Length - currentOffset, bytesRemaining); + stream.Write(block, currentOffset, amountToCopy); + + bytesRemaining -= amountToCopy; + + ++currentBlock; + currentOffset = 0; + } + } + else + { + stream.Write(this.largeBuffer, (int)offset, (int)count); + } + } + + /// + /// Writes bytes from the current stream to a destination byte array. + /// + /// Target buffer. + /// The entire stream is written to the target array. + /// > is null. + /// Object has been disposed. + public void WriteTo(byte[] buffer) + { + this.WriteTo(buffer, 0, this.Length); + } + + /// + /// Writes bytes from the current stream to a destination byte array. + /// + /// Target buffer. + /// Offset in the source stream, from which to start. + /// Number of bytes to write. + /// > is null. + /// + /// is less than 0, or + is beyond this stream's length. + /// + /// Object has been disposed. + public void WriteTo(byte[] buffer, long offset, long count) + { + this.WriteTo(buffer, offset, count, 0); + } + + /// + /// Writes bytes from the current stream to a destination byte array. + /// + /// Target buffer. + /// Offset in the source stream, from which to start. + /// Number of bytes to write. + /// Offset in the target byte array to start writing + /// buffer is null + /// + /// is less than 0, or + is beyond this stream's length. + /// + /// + /// is less than 0, or + is beyond the target 's length. + /// + /// Object has been disposed. + public void WriteTo(byte[] buffer, long offset, long count, int targetOffset) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0 || offset + count > this.length) + { + throw new ArgumentOutOfRangeException( + message: $"{nameof(offset)} must not be negative and {nameof(offset)} + {nameof(count)} must not exceed the length of the stream.", + innerException: null); + } + + if (targetOffset < 0 || count + targetOffset > buffer.Length) + { + throw new ArgumentOutOfRangeException( + message: $"{nameof(targetOffset)} must not be negative and {nameof(targetOffset)} + {nameof(count)} must not exceed the length of the target {nameof(buffer)}.", + innerException: null); + } + + if (this.largeBuffer == null) + { + var blockAndOffset = GetBlockAndRelativeOffset(offset); + long bytesRemaining = count; + int currentBlock = blockAndOffset.Block; + int currentOffset = blockAndOffset.Offset; + int currentTargetOffset = targetOffset; + + while (bytesRemaining > 0) + { + byte[] block = this.blocks[currentBlock]; + int amountToCopy = (int)Math.Min((long)block.Length - currentOffset, bytesRemaining); + Buffer.BlockCopy(block, currentOffset, buffer, currentTargetOffset, amountToCopy); + + bytesRemaining -= amountToCopy; + + ++currentBlock; + currentOffset = 0; + currentTargetOffset += amountToCopy; + } + } + else + { + AssertLengthIsSmall(); + Buffer.BlockCopy(this.largeBuffer, (int)offset, buffer, targetOffset, (int)count); + } + } + #endregion + + #region Helper Methods + private bool Disposed => this.disposed; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckDisposed() + { + if (this.Disposed) + { + this.ThrowDisposedException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowDisposedException() + { + throw new ObjectDisposedException($"The stream with Id {this.id} and Tag {this.tag} is disposed."); + } + + private int InternalRead(byte[] buffer, int offset, int count, long fromPosition) + { + if (this.length - fromPosition <= 0) + { + return 0; + } + + int amountToCopy; + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); + int bytesWritten = 0; + int bytesRemaining = (int)Math.Min((long)count, this.length - fromPosition); + + while (bytesRemaining > 0) + { + byte[] block = this.blocks[blockAndOffset.Block]; + amountToCopy = Math.Min(block.Length - blockAndOffset.Offset, + bytesRemaining); + Buffer.BlockCopy(block, blockAndOffset.Offset, buffer, + bytesWritten + offset, amountToCopy); + + bytesWritten += amountToCopy; + bytesRemaining -= amountToCopy; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + return bytesWritten; + } + amountToCopy = (int)Math.Min((long)count, this.length - fromPosition); + Buffer.BlockCopy(this.largeBuffer, (int)fromPosition, buffer, offset, amountToCopy); + return amountToCopy; + } + + private int InternalRead(Span buffer, long fromPosition) + { + if (this.length - fromPosition <= 0) + { + return 0; + } + + int amountToCopy; + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); + int bytesWritten = 0; + int bytesRemaining = (int)Math.Min(buffer.Length, this.length - fromPosition); + + while (bytesRemaining > 0) + { + byte[] block = this.blocks[blockAndOffset.Block]; + amountToCopy = Math.Min(block.Length - blockAndOffset.Offset, + bytesRemaining); + block.AsSpan(blockAndOffset.Offset, amountToCopy) + .CopyTo(buffer.Slice(bytesWritten)); + + bytesWritten += amountToCopy; + bytesRemaining -= amountToCopy; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + return bytesWritten; + } + amountToCopy = (int)Math.Min((long)buffer.Length, this.length - fromPosition); + this.largeBuffer.AsSpan((int)fromPosition, amountToCopy).CopyTo(buffer); + return amountToCopy; + } + + private struct BlockAndOffset + { + public int Block; + public int Offset; + + public BlockAndOffset(int block, int offset) + { + this.Block = block; + this.Offset = offset; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BlockAndOffset GetBlockAndRelativeOffset(long offset) + { + var blockSize = this.memoryManager.BlockSize; + int blockIndex = (int)Math.DivRem(offset, blockSize, out long offsetIndex); + return new BlockAndOffset(blockIndex, (int)offsetIndex); + } + + private void EnsureCapacity(long newCapacity) + { + if (newCapacity > this.memoryManager.MaximumStreamCapacity && this.memoryManager.MaximumStreamCapacity > 0) + { + this.memoryManager.ReportStreamOverCapacity(this.id, this.tag, newCapacity, this.AllocationStack); + + throw new OutOfMemoryException($"Requested capacity is too large: {newCapacity}. Limit is {this.memoryManager.MaximumStreamCapacity}."); + } + + if (this.largeBuffer != null) + { + if (newCapacity > this.largeBuffer.Length) + { + var newBuffer = this.memoryManager.GetLargeBuffer(newCapacity, this.id, this.tag); + Debug.Assert(this.length <= Int32.MaxValue); + this.InternalRead(newBuffer, 0, (int)this.length, 0); + this.ReleaseLargeBuffer(); + this.largeBuffer = newBuffer; + } + } + else + { + // Let's save some re-allocs of the blocks list + var blocksRequired = (newCapacity / this.memoryManager.BlockSize) + 1; + if (this.blocks.Capacity < blocksRequired) + { + this.blocks.Capacity = (int)blocksRequired; + } + while (this.Capacity64 < newCapacity) + { + this.blocks.Add((this.memoryManager.GetBlock())); + } + } + } + + /// + /// Release the large buffer (either stores it for eventual release or returns it immediately). + /// + private void ReleaseLargeBuffer() + { + if (this.memoryManager.AggressiveBufferReturn) + { + this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.id, this.tag); + } + else + { + // We most likely will only ever need space for one + this.dirtyBuffers ??= new List(1); + this.dirtyBuffers.Add(this.largeBuffer); + } + + this.largeBuffer = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AssertLengthIsSmall() + { + Debug.Assert(this.length <= Int32.MaxValue, "this.length was assumed to be <= Int32.MaxValue, but was larger."); + } +#endregion + } +} diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs.meta b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs.meta new file mode 100644 index 00000000..fc7b6212 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStream.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 943657a525c4a1f469a62b5e8cb2c319 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs new file mode 100644 index 00000000..c8a733aa --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs @@ -0,0 +1,1077 @@ +// --------------------------------------------------------------------- +// Copyright (c) 2015-2016 Microsoft +// +// 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. +// --------------------------------------------------------------------- + +#pragma warning disable CS8625 +#pragma warning disable CS8604 +#pragma warning disable CS8600 +#pragma warning disable CS8618 +namespace TEngine.IO +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Runtime.CompilerServices; + using System.Threading; + + /// + /// Manages pools of objects. + /// + /// + /// + /// There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams + /// as they write more data. + /// + /// + /// For scenarios that need to call , the large pool contains buffers of various sizes, all + /// multiples/exponentials of (1 MB by default). They are split by size to avoid overly-wasteful buffer + /// usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example. + /// + /// + public partial class RecyclableMemoryStreamManager + { + /// + /// Maximum length of a single array. + /// + /// See documentation at https://docs.microsoft.com/dotnet/api/system.array?view=netcore-3.1 + /// + internal const int MaxArrayLength = 0X7FFFFFC7; + + /// + /// Default block size, in bytes. + /// + public const int DefaultBlockSize = 128 * 1024; + + /// + /// Default large buffer multiple, in bytes. + /// + public const int DefaultLargeBufferMultiple = 1024 * 1024; + + /// + /// Default maximum buffer size, in bytes. + /// + public const int DefaultMaximumBufferSize = 128 * 1024 * 1024; + + // 0 to indicate unbounded + private const long DefaultMaxSmallPoolFreeBytes = 0L; + private const long DefaultMaxLargePoolFreeBytes = 0L; + + private readonly long[] largeBufferFreeSize; + private readonly long[] largeBufferInUseSize; + + private readonly ConcurrentStack[] largePools; + + private readonly ConcurrentStack smallPool; + + private long smallPoolFreeSize; + private long smallPoolInUseSize; + + /// + /// Initializes the memory manager with the default block/buffer specifications. This pool may have unbounded growth unless you modify and . + /// + public RecyclableMemoryStreamManager() + : this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize, false, DefaultMaxSmallPoolFreeBytes, DefaultMaxLargePoolFreeBytes) { } + + /// + /// Initializes the memory manager with the default block/buffer specifications and maximum free bytes specifications. + /// + /// Maximum number of bytes to keep available in the small pool before future buffers get dropped for garbage collection + /// Maximum number of bytes to keep available in the large pool before future buffers get dropped for garbage collection + /// is negative, or is negative. + public RecyclableMemoryStreamManager(long maximumSmallPoolFreeBytes, long maximumLargePoolFreeBytes) + :this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize, useExponentialLargeBuffer:false, maximumSmallPoolFreeBytes, maximumLargePoolFreeBytes) + { + } + + /// + /// Initializes the memory manager with the given block requiredSize. This pool may have unbounded growth unless you modify and . + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple of this value. + /// Buffers larger than this are not pooled + /// + /// is not a positive number, + /// or is not a positive number, + /// or is less than . + /// is not a multiple of . + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize) + : this(blockSize, largeBufferMultiple, maximumBufferSize, false, DefaultMaxSmallPoolFreeBytes, DefaultMaxLargePoolFreeBytes) { } + + /// + /// Initializes the memory manager with the given block requiredSize. + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple of this value. + /// Buffers larger than this are not pooled + /// Maximum number of bytes to keep available in the small pool before future buffers get dropped for garbage collection + /// Maximum number of bytes to keep available in the large pool before future buffers get dropped for garbage collection + /// + /// is not a positive number, + /// or is not a positive number, + /// or is less than , + /// or is negative, + /// or is negative. + /// + /// is not a multiple of . + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize, long maximumSmallPoolFreeBytes, long maximumLargePoolFreeBytes) + : this(blockSize, largeBufferMultiple, maximumBufferSize, false, maximumSmallPoolFreeBytes, maximumLargePoolFreeBytes) { } + + + /// + /// Initializes the memory manager with the given block requiredSize. This pool may have unbounded growth unless you modify and . + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple/exponential of this value. + /// Buffers larger than this are not pooled + /// Switch to exponential large buffer allocation strategy + /// + /// is not a positive number, + /// or is not a positive number, + /// or is less than . + /// is not a multiple/exponential of . + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize, bool useExponentialLargeBuffer) + :this(blockSize, largeBufferMultiple, maximumBufferSize, useExponentialLargeBuffer, DefaultMaxSmallPoolFreeBytes, DefaultMaxLargePoolFreeBytes) + { + } + + /// + /// Initializes the memory manager with the given block requiredSize. + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple/exponential of this value. + /// Buffers larger than this are not pooled. + /// Switch to exponential large buffer allocation strategy. + /// Maximum number of bytes to keep available in the small pool before future buffers get dropped for garbage collection. + /// Maximum number of bytes to keep available in the large pool before future buffers get dropped for garbage collection. + /// + /// is not a positive number, + /// or is not a positive number, + /// or is less than , + /// or is negative, + /// or is negative. + /// + /// is not a multiple/exponential of . + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize, bool useExponentialLargeBuffer, long maximumSmallPoolFreeBytes, long maximumLargePoolFreeBytes) + { + if (blockSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(blockSize), blockSize, $"{nameof(blockSize)} must be a positive number"); + } + + if (largeBufferMultiple <= 0) + { + throw new ArgumentOutOfRangeException(nameof(largeBufferMultiple), $"{nameof(largeBufferMultiple)} must be a positive number"); + } + + if (maximumBufferSize < blockSize) + { + throw new ArgumentOutOfRangeException(nameof(maximumBufferSize), $"{nameof(maximumBufferSize)} must be at least {nameof(blockSize)}"); + } + + if (maximumSmallPoolFreeBytes < 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumSmallPoolFreeBytes), $"{nameof(maximumSmallPoolFreeBytes)} must be non-negative"); + } + + if (maximumLargePoolFreeBytes < 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumLargePoolFreeBytes), $"{nameof(maximumLargePoolFreeBytes)} must be non-negative"); + } + + this.BlockSize = blockSize; + this.LargeBufferMultiple = largeBufferMultiple; + this.MaximumBufferSize = maximumBufferSize; + this.UseExponentialLargeBuffer = useExponentialLargeBuffer; + this.MaximumFreeSmallPoolBytes = maximumSmallPoolFreeBytes; + this.MaximumFreeLargePoolBytes = maximumLargePoolFreeBytes; + + if (!this.IsLargeBufferSize(maximumBufferSize)) + { + throw new ArgumentException( + $"{nameof(maximumBufferSize)} is not {(this.UseExponentialLargeBuffer ? "an exponential" : "a multiple")} of {nameof(largeBufferMultiple)}.", + nameof(maximumBufferSize)); + } + + this.smallPool = new ConcurrentStack(); + var numLargePools = useExponentialLargeBuffer + ? ((int)Math.Log(maximumBufferSize / largeBufferMultiple, 2) + 1) + : (maximumBufferSize / largeBufferMultiple); + + // +1 to store size of bytes in use that are too large to be pooled + this.largeBufferInUseSize = new long[numLargePools + 1]; + this.largeBufferFreeSize = new long[numLargePools]; + + this.largePools = new ConcurrentStack[numLargePools]; + + for (var i = 0; i < this.largePools.Length; ++i) + { + this.largePools[i] = new ConcurrentStack(); + } + + Events.Writer.MemoryStreamManagerInitialized(blockSize, largeBufferMultiple, maximumBufferSize); + } + + /// + /// The size of each block. It must be set at creation and cannot be changed. + /// + public int BlockSize { get; } + + /// + /// All buffers are multiples/exponentials of this number. It must be set at creation and cannot be changed. + /// + public int LargeBufferMultiple { get; } + + /// + /// Use multiple large buffer allocation strategy. It must be set at creation and cannot be changed. + /// + public bool UseMultipleLargeBuffer => !this.UseExponentialLargeBuffer; + + /// + /// Use exponential large buffer allocation strategy. It must be set at creation and cannot be changed. + /// + public bool UseExponentialLargeBuffer { get; } + + /// + /// Gets the maximum buffer size. + /// + /// Any buffer that is returned to the pool that is larger than this will be + /// discarded and garbage collected. + public int MaximumBufferSize { get; } + + /// + /// Number of bytes in small pool not currently in use. + /// + public long SmallPoolFreeSize => this.smallPoolFreeSize; + + /// + /// Number of bytes currently in use by stream from the small pool. + /// + public long SmallPoolInUseSize => this.smallPoolInUseSize; + + /// + /// Number of bytes in large pool not currently in use. + /// + public long LargePoolFreeSize + { + get + { + long sum = 0; + foreach (long freeSize in this.largeBufferFreeSize) + { + sum += freeSize; + } + + return sum; + } + } + + /// + /// Number of bytes currently in use by streams from the large pool. + /// + public long LargePoolInUseSize + { + get + { + long sum = 0; + foreach (long inUseSize in this.largeBufferInUseSize) + { + sum += inUseSize; + } + + return sum; + } + } + + /// + /// How many blocks are in the small pool. + /// + public long SmallBlocksFree => this.smallPool.Count; + + /// + /// How many buffers are in the large pool. + /// + public long LargeBuffersFree + { + get + { + long free = 0; + foreach (var pool in this.largePools) + { + free += pool.Count; + } + return free; + } + } + + /// + /// How many bytes of small free blocks to allow before we start dropping + /// those returned to us. + /// + /// The default value is 0, meaning the pool is unbounded. + public long MaximumFreeSmallPoolBytes { get; set; } + + /// + /// How many bytes of large free buffers to allow before we start dropping + /// those returned to us. + /// + /// The default value is 0, meaning the pool is unbounded. + public long MaximumFreeLargePoolBytes { get; set; } + + /// + /// Maximum stream capacity in bytes. Attempts to set a larger capacity will + /// result in an exception. + /// + /// A value of 0 indicates no limit. + public long MaximumStreamCapacity { get; set; } + + /// + /// Whether to save callstacks for stream allocations. This can help in debugging. + /// It should NEVER be turned on generally in production. + /// + public bool GenerateCallStacks { get; set; } + + /// + /// Whether dirty buffers can be immediately returned to the buffer pool. + /// + /// + /// + /// When is called on a stream and creates a single large buffer, if this setting is enabled, the other blocks will be returned + /// to the buffer pool immediately. + /// + /// + /// Note when enabling this setting that the user is responsible for ensuring that any buffer previously + /// retrieved from a stream which is subsequently modified is not used after modification (as it may no longer + /// be valid). + /// + /// + public bool AggressiveBufferReturn { get; set; } + + /// + /// Causes an exception to be thrown if is ever called. + /// + /// Calling defeats the purpose of a pooled buffer. Use this property to discover code that is calling . If this is + /// set and is called, a NotSupportedException will be thrown. + public bool ThrowExceptionOnToArray { get; set; } + + /// + /// Removes and returns a single block from the pool. + /// + /// A byte[] array. + internal byte[] GetBlock() + { + Interlocked.Add(ref this.smallPoolInUseSize, this.BlockSize); + + if (!this.smallPool.TryPop(out byte[] block)) + { + // We'll add this back to the pool when the stream is disposed + // (unless our free pool is too large) +#if NET5_0_OR_GREATER + block = GC.AllocateUninitializedArray(this.BlockSize); +#else + block = new byte[this.BlockSize]; +#endif + ReportBlockCreated(); + } + else + { + Interlocked.Add(ref this.smallPoolFreeSize, -this.BlockSize); + } + + return block; + } + + /// + /// Returns a buffer of arbitrary size from the large buffer pool. This buffer + /// will be at least the requiredSize and always be a multiple/exponential of largeBufferMultiple. + /// + /// The minimum length of the buffer. + /// Unique ID for the stream. + /// The tag of the stream returning this buffer, for logging if necessary. + /// A buffer of at least the required size. + /// Requested array size is larger than the maximum allowed. + internal byte[] GetLargeBuffer(long requiredSize, Guid id, string tag) + { + if (requiredSize > MaxArrayLength) + { + throw new OutOfMemoryException($"Requested size exceeds maximum array length of {MaxArrayLength}."); + } + + requiredSize = this.RoundToLargeBufferSize(requiredSize); + + var poolIndex = this.GetPoolIndex(requiredSize); + + bool createdNew = false; + bool pooled = true; + string callStack = null; + + byte[] buffer; + if (poolIndex < this.largePools.Length) + { + if (!this.largePools[poolIndex].TryPop(out buffer)) + { + buffer = AllocateArray(requiredSize); + createdNew = true; + } + else + { + Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], -buffer.Length); + } + } + else + { + // Buffer is too large to pool. They get a new buffer. + + // We still want to track the size, though, and we've reserved a slot + // in the end of the inuse array for nonpooled bytes in use. + poolIndex = this.largeBufferInUseSize.Length - 1; + + // We still want to round up to reduce heap fragmentation. + buffer = AllocateArray(requiredSize); + if (this.GenerateCallStacks) + { + // Grab the stack -- we want to know who requires such large buffers + callStack = Environment.StackTrace; + } + createdNew = true; + pooled = false; + } + + Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], buffer.Length); + if (createdNew) + { + ReportLargeBufferCreated(id, tag, requiredSize, pooled: pooled, callStack); + } + + return buffer; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static byte[] AllocateArray(long requiredSize) => +#if NET5_0_OR_GREATER + GC.AllocateUninitializedArray((int)requiredSize); +#else + new byte[requiredSize]; +#endif + } + + private long RoundToLargeBufferSize(long requiredSize) + { + if (this.UseExponentialLargeBuffer) + { + long pow = 1; + while (this.LargeBufferMultiple * pow < requiredSize) + { + pow <<= 1; + } + return this.LargeBufferMultiple * pow; + } + else + { + return ((requiredSize + this.LargeBufferMultiple - 1) / this.LargeBufferMultiple) * this.LargeBufferMultiple; + } + } + + private bool IsLargeBufferSize(int value) + { + return (value != 0) && (this.UseExponentialLargeBuffer + ? (value == RoundToLargeBufferSize(value)) + : (value % this.LargeBufferMultiple) == 0); + } + + private int GetPoolIndex(long length) + { + if (this.UseExponentialLargeBuffer) + { + int index = 0; + while ((this.LargeBufferMultiple << index) < length) + { + ++index; + } + return index; + } + else + { + return (int)(length / this.LargeBufferMultiple - 1); + } + } + + /// + /// Returns the buffer to the large pool. + /// + /// The buffer to return. + /// Unique stream ID. + /// The tag of the stream returning this buffer, for logging if necessary. + /// is null. + /// buffer.Length is not a multiple/exponential of (it did not originate from this pool). + internal void ReturnLargeBuffer(byte[] buffer, Guid id, string tag) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (!this.IsLargeBufferSize(buffer.Length)) + { + throw new ArgumentException($"{nameof(buffer)} did not originate from this memory manager. The size is not " + + $"{(this.UseExponentialLargeBuffer ? "an exponential" : "a multiple")} of {this.LargeBufferMultiple}."); + } + + var poolIndex = this.GetPoolIndex(buffer.Length); + + if (poolIndex < this.largePools.Length) + { + if ((this.largePools[poolIndex].Count + 1) * buffer.Length <= this.MaximumFreeLargePoolBytes || + this.MaximumFreeLargePoolBytes == 0) + { + this.largePools[poolIndex].Push(buffer); + Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], buffer.Length); + } + else + { + ReportBufferDiscarded(id, tag, Events.MemoryStreamBufferType.Large, Events.MemoryStreamDiscardReason.EnoughFree); + } + } + else + { + // This is a non-poolable buffer, but we still want to track its size for inuse + // analysis. We have space in the inuse array for this. + poolIndex = this.largeBufferInUseSize.Length - 1; + + ReportBufferDiscarded(id, tag, Events.MemoryStreamBufferType.Large, Events.MemoryStreamDiscardReason.TooLarge); + } + + Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], -buffer.Length); + } + + /// + /// Returns the blocks to the pool. + /// + /// Collection of blocks to return to the pool. + /// Unique Stream ID. + /// The tag of the stream returning these blocks, for logging if necessary. + /// is null. + /// contains buffers that are the wrong size (or null) for this memory manager. + internal void ReturnBlocks(List blocks, Guid id, string tag) + { + if (blocks == null) + { + throw new ArgumentNullException(nameof(blocks)); + } + + long bytesToReturn = (long)blocks.Count * (long)this.BlockSize; + Interlocked.Add(ref this.smallPoolInUseSize, -bytesToReturn); + + foreach (var block in blocks) + { + if (block == null || block.Length != this.BlockSize) + { + throw new ArgumentException($"{nameof(blocks)} contains buffers that are not {nameof(BlockSize)} in length.", nameof(blocks)); + } + } + + foreach (var block in blocks) + { + if (this.MaximumFreeSmallPoolBytes == 0 || this.SmallPoolFreeSize < this.MaximumFreeSmallPoolBytes) + { + Interlocked.Add(ref this.smallPoolFreeSize, this.BlockSize); + this.smallPool.Push(block); + } + else + { + ReportBufferDiscarded(id, tag, Events.MemoryStreamBufferType.Small, Events.MemoryStreamDiscardReason.EnoughFree); + break; + } + } + } + + /// + /// Returns a block to the pool. + /// + /// Block to return to the pool. + /// Unique Stream ID. + /// The tag of the stream returning this, for logging if necessary. + /// is null. + /// is the wrong size for this memory manager. + internal void ReturnBlock(byte[] block, Guid id, string tag) + { + var bytesToReturn = this.BlockSize; + Interlocked.Add(ref this.smallPoolInUseSize, -bytesToReturn); + + if (block == null) + { + throw new ArgumentNullException(nameof(block)); + } + + if (block.Length != this.BlockSize) + { + throw new ArgumentException($"{nameof(block)} is not not {nameof(BlockSize)} in length."); + } + + if (this.MaximumFreeSmallPoolBytes == 0 || this.SmallPoolFreeSize < this.MaximumFreeSmallPoolBytes) + { + Interlocked.Add(ref this.smallPoolFreeSize, this.BlockSize); + this.smallPool.Push(block); + } + else + { + ReportBufferDiscarded(id, tag, Events.MemoryStreamBufferType.Small, Events.MemoryStreamDiscardReason.EnoughFree); + } + } + + internal void ReportBlockCreated() + { + Events.Writer.MemoryStreamNewBlockCreated(this.smallPoolInUseSize); + this.BlockCreated?.Invoke(this, new BlockCreatedEventArgs(this.smallPoolInUseSize)); + } + + internal void ReportLargeBufferCreated(Guid id, string tag, long requiredSize, bool pooled, string callStack) + { + if (pooled) + { + Events.Writer.MemoryStreamNewLargeBufferCreated(requiredSize, this.LargePoolInUseSize); + } + else + { + Events.Writer.MemoryStreamNonPooledLargeBufferCreated(id, tag, requiredSize, callStack); + } + this.LargeBufferCreated?.Invoke(this, new LargeBufferCreatedEventArgs(id, tag, requiredSize, this.LargePoolInUseSize, pooled, callStack)); + } + + internal void ReportBufferDiscarded(Guid id, string tag, Events.MemoryStreamBufferType bufferType, Events.MemoryStreamDiscardReason reason) + { + Events.Writer.MemoryStreamDiscardBuffer(id, tag, bufferType, reason, + this.SmallBlocksFree, this.smallPoolFreeSize, this.smallPoolInUseSize, + this.LargeBuffersFree, this.LargePoolFreeSize, this.LargePoolInUseSize); + this.BufferDiscarded?.Invoke(this, new BufferDiscardedEventArgs(id, tag, bufferType, reason)); + } + + internal void ReportStreamCreated(Guid id, string tag, long requestedSize, long actualSize) + { + Events.Writer.MemoryStreamCreated(id, tag, requestedSize, actualSize); + this.StreamCreated?.Invoke(this, new StreamCreatedEventArgs(id, tag, requestedSize, actualSize)); + } + + internal void ReportStreamDisposed(Guid id, string tag, TimeSpan lifetime, string allocationStack, string disposeStack) + { + Events.Writer.MemoryStreamDisposed(id, tag, (long)lifetime.TotalMilliseconds, allocationStack, disposeStack); + this.StreamDisposed?.Invoke(this, new StreamDisposedEventArgs(id, tag, lifetime, allocationStack, disposeStack)); + } + + internal void ReportStreamDoubleDisposed(Guid id, string tag, string allocationStack, string disposeStack1, string disposeStack2) + { + Events.Writer.MemoryStreamDoubleDispose(id, tag, allocationStack, disposeStack1, disposeStack2); + this.StreamDoubleDisposed?.Invoke(this, new StreamDoubleDisposedEventArgs(id, tag, allocationStack,disposeStack1, disposeStack2)); + } + + internal void ReportStreamFinalized(Guid id, string tag, string allocationStack) + { + Events.Writer.MemoryStreamFinalized(id, tag, allocationStack); + this.StreamFinalized?.Invoke(this, new StreamFinalizedEventArgs(id, tag, allocationStack)); + } + + internal void ReportStreamLength(long bytes) + { + this.StreamLength?.Invoke(this, new StreamLengthEventArgs(bytes)); + } + + internal void ReportStreamToArray(Guid id, string tag, string stack, long length) + { + Events.Writer.MemoryStreamToArray(id, tag, stack, length); + this.StreamConvertedToArray?.Invoke(this, new StreamConvertedToArrayEventArgs(id, tag, stack, length)); + } + + internal void ReportStreamOverCapacity(Guid id, string tag, long requestedCapacity, string allocationStack) + { + Events.Writer.MemoryStreamOverCapacity(id, tag, requestedCapacity, this.MaximumStreamCapacity, allocationStack); + this.StreamOverCapacity?.Invoke(this, new StreamOverCapacityEventArgs(id, tag, requestedCapacity, this.MaximumStreamCapacity, allocationStack)); + } + + internal void ReportUsageReport() + { + this.UsageReport?.Invoke(this, new UsageReportEventArgs(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, this.LargePoolFreeSize)); + } + + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A MemoryStream. + public MemoryStream GetStream() + { + return new RecyclableMemoryStream(this); + } + + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id) + { + return new RecyclableMemoryStream(this, id); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. + /// + /// A tag which can be used to track the source of the stream. + /// A MemoryStream. + public MemoryStream GetStream(string tag) + { + return new RecyclableMemoryStream(this, tag); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag) + { + return new RecyclableMemoryStream(this, id, tag); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. + /// + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// A MemoryStream. + public MemoryStream GetStream(string tag, int requiredSize) + { + return new RecyclableMemoryStream(this, tag, requiredSize); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, int requiredSize) + { + return new RecyclableMemoryStream(this, id, tag, requiredSize); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, long requiredSize) + { + return new RecyclableMemoryStream(this, id, tag, requiredSize); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call + /// on the underlying stream. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, int requiredSize, bool asContiguousBuffer) + { + return this.GetStream(id, tag, (long)requiredSize, asContiguousBuffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call + /// on the underlying stream. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, long requiredSize, bool asContiguousBuffer) + { + if (!asContiguousBuffer || requiredSize <= this.BlockSize) + { + return this.GetStream(id, tag, requiredSize); + } + + return new RecyclableMemoryStream(this, id, tag, requiredSize, this.GetLargeBuffer(requiredSize, id, tag)); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call + /// on the underlying stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuffer) + { + return GetStream(tag, (long)requiredSize, asContiguousBuffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call + /// on the underlying stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(string tag, long requiredSize, bool asContiguousBuffer) + { + return GetStream(Guid.NewGuid(), tag, requiredSize, asContiguousBuffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// The offset from the start of the buffer to copy from. + /// The number of bytes to copy from the buffer. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, count); + stream.Write(buffer, offset, count); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(byte[] buffer) + { + return GetStream(null, buffer, 0, buffer.Length); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// The offset from the start of the buffer to copy from. + /// The number of bytes to copy from the buffer. + /// A MemoryStream. + public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count) + { + return GetStream(Guid.NewGuid(), tag, buffer, offset, count); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + [Obsolete("Use the ReadOnlySpan version of this method instead.")] + public MemoryStream GetStream(Guid id, string tag, Memory buffer) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, buffer.Length); + stream.Write(buffer.Span); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, ReadOnlySpan buffer) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, buffer.Length); + stream.Write(buffer); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + [Obsolete("Use the ReadOnlySpan version of this method instead.")] + public MemoryStream GetStream(Memory buffer) + { + return GetStream(null, buffer); + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(ReadOnlySpan buffer) + { + return GetStream(null, buffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + [Obsolete("Use the ReadOnlySpan version of this method instead.")] + public MemoryStream GetStream(string tag, Memory buffer) + { + return GetStream(Guid.NewGuid(), tag, buffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(string tag, ReadOnlySpan buffer) + { + return GetStream(Guid.NewGuid(), tag, buffer); + } + + /// + /// Triggered when a new block is created. + /// + public event EventHandler BlockCreated; + + /// + /// Triggered when a new large buffer is created. + /// + public event EventHandler LargeBufferCreated; + + /// + /// Triggered when a new stream is created. + /// + public event EventHandler StreamCreated; + + /// + /// Triggered when a stream is disposed. + /// + public event EventHandler StreamDisposed; + + /// + /// Triggered when a stream is disposed of twice (an error). + /// + public event EventHandler StreamDoubleDisposed; + + /// + /// Triggered when a stream is finalized. + /// + public event EventHandler StreamFinalized; + + /// + /// Triggered when a stream is disposed to report the stream's length. + /// + public event EventHandler StreamLength; + + /// + /// Triggered when a user converts a stream to array. + /// + public event EventHandler StreamConvertedToArray; + + /// + /// Triggered when a stream is requested to expand beyond the maximum length specified by the responsible RecyclableMemoryStreamManager. + /// + public event EventHandler StreamOverCapacity; + + /// + /// Triggered when a buffer of either type is discarded, along with the reason for the discard. + /// + public event EventHandler BufferDiscarded; + + /// + /// Periodically triggered to report usage statistics. + /// + public event EventHandler UsageReport; + } +} diff --git a/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs.meta b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs.meta new file mode 100644 index 00000000..2b866afc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e251d7371d835143ab1ba00fe110831 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Singleton.meta b/Assets/GameScripts/DotNet/Core/Singleton.meta new file mode 100644 index 00000000..0382e085 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ca744a370ba21f4aafc2a807adf97dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Interface.meta b/Assets/GameScripts/DotNet/Core/Singleton/Interface.meta new file mode 100644 index 00000000..a94cf9df --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 12455eea4412a9e468f0a3a4cc929a78 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs b/Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs new file mode 100644 index 00000000..9c325092 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace TEngine.Core +{ + public interface ISingleton : IDisposable + { + public bool IsDisposed { get; set; } + public Task Initialize(); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs.meta b/Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs.meta new file mode 100644 index 00000000..f2eb804f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Interface/ISingleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74fd4adc991154043a4cb8a74e974c55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs b/Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs new file mode 100644 index 00000000..f483262f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs @@ -0,0 +1,7 @@ +namespace TEngine.Core +{ + public interface IUpdateSingleton : ISingleton + { + public abstract void Update(); + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs.meta b/Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs.meta new file mode 100644 index 00000000..01e7e243 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Interface/IUpdateSingleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cacc43db095ef646b8e9894d0da431b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs b/Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs new file mode 100644 index 00000000..8f8b5c48 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +#pragma warning disable CS8601 +#pragma warning disable CS8618 + +namespace TEngine.Core +{ + public abstract class Singleton : ISingleton where T : ISingleton, new() + { + public bool IsDisposed { get; set; } + + public static T Instance { get; private set; } + + private void RegisterSingleton(ISingleton singleton) + { + Instance = (T) singleton; + AssemblyManager.OnLoadAssemblyEvent += OnLoad; + AssemblyManager.OnUnLoadAssemblyEvent += OnUnLoad; + } + + public virtual Task Initialize() + { + return Task.CompletedTask; + } + + protected virtual void OnLoad(int assemblyName) { } + + protected virtual void OnUnLoad(int assemblyName) { } + + public virtual void Dispose() + { + IsDisposed = true; + Instance = default; + AssemblyManager.OnLoadAssemblyEvent -= OnLoad; + AssemblyManager.OnUnLoadAssemblyEvent -= OnUnLoad; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs.meta b/Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs.meta new file mode 100644 index 00000000..377761db --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/Singleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b54004d9250cf744a5b58a655d7f4d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs b/Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs new file mode 100644 index 00000000..29021ae9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs @@ -0,0 +1,132 @@ +// ReSharper disable StaticMemberInGenericType + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using TEngine.DataStructure; +#pragma warning disable CS8601 +#pragma warning disable CS8604 +#pragma warning disable CS8600 + +namespace TEngine.Core +{ + public static class SingletonSystem + { + private static readonly Queue Updates = new Queue(); + private static readonly OneToManyQueue Singletons = new OneToManyQueue(); + + public static void Initialize() + { + AssemblyManager.OnLoadAssemblyEvent += Load; + AssemblyManager.OnUnLoadAssemblyEvent += UnLoad; + } + + private static void Load(int assemblyName) + { + var count = 0; + var task = new List(); + + UnLoad(assemblyName); + + foreach (var singletonType in AssemblyManager.ForEach(assemblyName, typeof(ISingleton))) + { + var instance = (ISingleton) Activator.CreateInstance(singletonType); + var registerMethodInfo = singletonType.BaseType?.GetMethod("RegisterSingleton", BindingFlags.Instance | BindingFlags.NonPublic); + var initializeMethodInfo = singletonType.GetMethod("Initialize", BindingFlags.Instance | BindingFlags.Public); + var onLoadMethodInfo = singletonType.GetMethod("OnLoad", BindingFlags.Instance | BindingFlags.NonPublic); + + if (initializeMethodInfo != null) + { + task.Add((Task) initializeMethodInfo.Invoke(instance, null)); + } + + registerMethodInfo?.Invoke(instance, new object[] {instance}); + onLoadMethodInfo?.Invoke(instance, new object[] {assemblyName}); + + count++; + + switch (instance) + { + case IUpdateSingleton iUpdateSingleton: + { + Updates.Enqueue(iUpdateSingleton); + break; + } + } + + Singletons.Enqueue(assemblyName, instance); + } + + Task.WaitAll(task.ToArray()); + Log.Info($"assembly:{assemblyName} load Singleton count:{count}"); + } + + private static void UnLoad(Queue singletons) + { + while (singletons.Count > 0) + { + try + { + singletons.Dequeue().Dispose(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + private static void UnLoad(int assemblyName) + { + if (!Singletons.TryGetValue(assemblyName, out var singletons)) + { + return; + } + + var count = singletons.Count; + UnLoad(singletons); + Singletons.RemoveKey(assemblyName); + // Log.Info($"assembly:{assemblyName} Unload Singleton count:{count}"); + } + + public static void Update() + { + var updatesCount = Updates.Count; + + while (updatesCount-- > 0) + { + var updateSingleton = Updates.Dequeue(); + + if (updateSingleton.IsDisposed) + { + continue; + } + + Updates.Enqueue(updateSingleton); + + try + { + updateSingleton.Update(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public static void Dispose() + { + foreach (var (_, singletons) in Singletons) + { + UnLoad(singletons); + } + + Updates.Clear(); + Singletons.Clear(); + AssemblyManager.OnLoadAssemblyEvent -= Load; + AssemblyManager.OnUnLoadAssemblyEvent -= UnLoad; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs.meta b/Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs.meta new file mode 100644 index 00000000..17dc8955 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Singleton/SingletonSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31f7155596debed429793d1d408935d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/SynchronizationContext.meta b/Assets/GameScripts/DotNet/Core/SynchronizationContext.meta new file mode 100644 index 00000000..30321b93 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/SynchronizationContext.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8ca2dbcc3024e6345808b8f3c07b3630 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs b/Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs new file mode 100644 index 00000000..daee88b6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using TEngine.Core.Network; +#pragma warning disable CS8765 +#pragma warning disable CS8601 +#pragma warning disable CS8618 + +namespace TEngine +{ + public sealed class ThreadSynchronizationContext : SynchronizationContext + { + public readonly int ThreadId; + private Action _actionHandler; + private readonly ConcurrentQueue _queue = new(); + public static ThreadSynchronizationContext Main { get; } = new(Environment.CurrentManagedThreadId); + + public ThreadSynchronizationContext(int threadId) + { + ThreadId = threadId; + } + + public void Update() + { + while (_queue.TryDequeue(out _actionHandler)) + { + try + { + _actionHandler(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public override void Post(SendOrPostCallback callback, object state) + { + Post(() => callback(state)); + } + + public void Post(Action action) + { + _queue.Enqueue(action); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs.meta b/Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs.meta new file mode 100644 index 00000000..ec80feb3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/SynchronizationContext/ThreadSynchronizationContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdf9a97b5cd42dd498c020b421ee8b23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task.meta b/Assets/GameScripts/DotNet/Core/Task.meta new file mode 100644 index 00000000..ca914f7d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7be5b356ee4841f4dbfce44b09b08e83 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Builder.meta b/Assets/GameScripts/DotNet/Core/Task/Builder.meta new file mode 100644 index 00000000..d333df8b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Builder.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 00f992eda26fe684e8b9d529843d2a25 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs similarity index 60% rename from Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs rename to Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs index 99d7b8aa..da4cabf9 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskCompletedMethodBuilder.cs +++ b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs @@ -1,40 +1,43 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; using System.Security; -namespace ET +namespace TEngine { - public struct AsyncETTaskCompletedMethodBuilder + [StructLayout(LayoutKind.Auto)] + public struct AsyncFTaskCompletedMethodBuilder { // 1. Static Create method. [DebuggerHidden] - public static AsyncETTaskCompletedMethodBuilder Create() + public static AsyncFTaskCompletedMethodBuilder Create() { - AsyncETTaskCompletedMethodBuilder builder = new AsyncETTaskCompletedMethodBuilder(); - return builder; + return new AsyncFTaskCompletedMethodBuilder(); } // 2. TaskLike Task property(void) - public ETTaskCompleted Task => default; + public FTaskCompleted Task => default; // 3. SetException [DebuggerHidden] - public void SetException(Exception e) + public void SetException(Exception exception) { - ETTask.ExceptionHandler.Invoke(e); + Log.Error(exception); + // ExceptionDispatchInfo.Capture(exception).Throw(); } // 4. SetResult [DebuggerHidden] public void SetResult() { - // do nothing } // 5. AwaitOnCompleted [DebuggerHidden] - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { awaiter.OnCompleted(stateMachine.MoveNext); } @@ -42,7 +45,9 @@ namespace ET // 6. AwaitUnsafeOnCompleted [DebuggerHidden] [SecuritySafeCritical] - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, + ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine { awaiter.UnsafeOnCompleted(stateMachine.MoveNext); } diff --git a/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs.meta b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs.meta new file mode 100644 index 00000000..839283f6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskCompletedMethodBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9e4e2f1fe1bb0d42be6152751f563ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs new file mode 100644 index 00000000..2be542c4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs @@ -0,0 +1,151 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace TEngine +{ + [StructLayout(LayoutKind.Auto)] + public readonly struct AsyncFTaskMethodBuilder + { + // 1. Static Create method. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFTaskMethodBuilder Create() + { + return new AsyncFTaskMethodBuilder(FTask.Create()); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private AsyncFTaskMethodBuilder(FTask fTask) + { + Task = fTask; + } + + // 4. Return to task + public FTask Task + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + // 2. Start + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + // 3. SetResult + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + Task.SetResult(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + Task.SetException(exception); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, + ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + } + + [StructLayout(LayoutKind.Auto)] + public readonly struct AsyncFTaskMethodBuilder + { + // 1. Static Create method. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFTaskMethodBuilder Create() + { + return new AsyncFTaskMethodBuilder(FTask.Create()); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private AsyncFTaskMethodBuilder(FTask fTask) + { + Task = fTask; + } + + // 4. Return to task + public FTask Task + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + // 2. Start + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + // 3. SetResult + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T value) + { + Task.SetResult(value); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + Task.SetException(exception); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, + ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs.meta b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs.meta new file mode 100644 index 00000000..d76038f5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFTaskMethodBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8973ac6e4512594b9787095beaa62c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs new file mode 100644 index 00000000..9e1527c4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs @@ -0,0 +1,72 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; + +namespace TEngine +{ + [StructLayout(LayoutKind.Auto)] + internal struct AsyncFVoidMethodBuilder + { + // 1. Static Create method. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFVoidMethodBuilder Create() + { + return default; + } + + // 2. Start + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + // 3. SetResult + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + } + + // 4. Return to task + public FVoid Task + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => default; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + ExceptionDispatchInfo.Capture(exception).Throw(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, + ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs.meta b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs.meta new file mode 100644 index 00000000..d49c5703 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Builder/AsyncFVoidMethodBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97a3c8247dd2a854485df3f751feb710 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs b/Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs new file mode 100644 index 00000000..2b4ca722 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +#pragma warning disable CS8625 +namespace TEngine +{ + public sealed class FCancellationToken + { + private HashSet _actions = new HashSet(); + public bool IsCancel => _actions == null; + + public void Add(Action action) + { + _actions.Add(action); + } + + public void Remove(Action action) + { + _actions.Remove(action); + } + + public void Cancel() + { + if (_actions == null) + { + return; + } + + var runActions = _actions; + _actions = null; + + foreach (var action in runActions) + { + try + { + action.Invoke(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + } +} + diff --git a/Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs.meta b/Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs.meta new file mode 100644 index 00000000..cae41dc1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/FCancellationToken.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 619ea7efe37fc984ea5b11c2e3400e9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/FTask.cs b/Assets/GameScripts/DotNet/Core/Task/FTask.cs new file mode 100644 index 00000000..d11674f4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/FTask.cs @@ -0,0 +1,299 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +#pragma warning disable CS8601 +#pragma warning disable CS8603 +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine +{ + public enum STaskStatus : byte + { + /// The operation has not yet completed. + Pending = 0, + + /// The operation completed successfully. + Succeeded = 1, + + /// The operation completed with an error. + Faulted = 2 + } + + [AsyncMethodBuilder(typeof(AsyncFTaskMethodBuilder))] + public sealed partial class FTask : ICriticalNotifyCompletion + { + private Action _callBack; + private ExceptionDispatchInfo _exception; + private bool _isFromPool; + private STaskStatus _status; + + public bool IsCompleted + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _status != STaskStatus.Pending; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action continuation) + { + UnsafeOnCompleted(continuation); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FTask GetAwaiter() + { + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [DebuggerHidden] + private async FVoid InnerCoroutine() + { + await this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [DebuggerHidden] + public void Coroutine() + { + InnerCoroutine().Coroutine(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() + { + switch (_status) + { + case STaskStatus.Succeeded: + { + Recycle(); + break; + } + case STaskStatus.Faulted: + { + Recycle(); + + if (_exception != null) + { + var exception = _exception; + _exception = null; + exception.Throw(); + } + + break; + } + default: + throw new NotSupportedException("Direct call to getResult is not allowed"); + } + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Recycle() + { + if (!_isFromPool) + { + return; + } + + _status = STaskStatus.Pending; + _callBack = null; + Pool.Return(this); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _status = STaskStatus.Succeeded; + + if (_callBack == null) + { + return; + } + + var callBack = _callBack; + _callBack = null; + callBack.Invoke(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnsafeOnCompleted(Action continuation) + { + if (_status != STaskStatus.Pending) + { + continuation?.Invoke(); + return; + } + + _callBack = continuation; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _status = STaskStatus.Faulted; + _exception = ExceptionDispatchInfo.Capture(exception); + _callBack?.Invoke(); + } + } + + [AsyncMethodBuilder(typeof(AsyncFTaskMethodBuilder<>))] + public sealed partial class FTask : ICriticalNotifyCompletion + { + private Action _callBack; + private ExceptionDispatchInfo _exception; + private bool _isFromPool; + private STaskStatus _status; + private T _value; + + public bool IsCompleted + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _status != STaskStatus.Pending; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action continuation) + { + UnsafeOnCompleted(continuation); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FTask GetAwaiter() + { + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [DebuggerHidden] + private async FVoid InnerCoroutine() + { + await this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [DebuggerHidden] + public void Coroutine() + { + InnerCoroutine().Coroutine(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T GetResult() + { + switch (_status) + { + case STaskStatus.Succeeded: + { + var value = _value; + Recycle(); + return value; + } + case STaskStatus.Faulted: + { + Recycle(); + + if (_exception == null) + { + return default; + } + + var exception = _exception; + _exception = null; + exception.Throw(); + return default; + } + default: + throw new NotSupportedException("Direct call to getResult is not allowed"); + } + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Recycle() + { + if (!_isFromPool) + { + return; + } + + _status = STaskStatus.Pending; + _callBack = null; + _value = default; + Pool>.Return(this); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T value) + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _value = value; + _status = STaskStatus.Succeeded; + + if (_callBack == null) + { + return; + } + + var callBack = _callBack; + _callBack = null; + callBack.Invoke(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnsafeOnCompleted(Action continuation) + { + if (_status != STaskStatus.Pending) + { + continuation?.Invoke(); + return; + } + + _callBack = continuation; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _status = STaskStatus.Faulted; + _exception = ExceptionDispatchInfo.Capture(exception); + _callBack?.Invoke(); + } + } +} + diff --git a/Assets/GameScripts/DotNet/Core/Task/FTask.cs.meta b/Assets/GameScripts/DotNet/Core/Task/FTask.cs.meta new file mode 100644 index 00000000..eea61879 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/FTask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4bfba30d0ddf4d4fb0da92631ce965b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs b/Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs similarity index 59% rename from Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs rename to Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs index 1a135346..43c26f26 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs +++ b/Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs @@ -1,20 +1,21 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace ET +namespace TEngine { - [AsyncMethodBuilder(typeof (AsyncETTaskCompletedMethodBuilder))] - public struct ETTaskCompleted: ICriticalNotifyCompletion + [AsyncMethodBuilder(typeof(AsyncFTaskCompletedMethodBuilder))] + [StructLayout(LayoutKind.Auto)] + public struct FTaskCompleted : INotifyCompletion { [DebuggerHidden] - public ETTaskCompleted GetAwaiter() + public FTaskCompleted GetAwaiter() { return this; } - [DebuggerHidden] - public bool IsCompleted => true; + [DebuggerHidden] public bool IsCompleted => true; [DebuggerHidden] public void GetResult() diff --git a/Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs.meta b/Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs.meta new file mode 100644 index 00000000..2010bf6e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/FTaskCompleted.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5bebfd7b883cd6b41892cf411a3532fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs b/Assets/GameScripts/DotNet/Core/Task/FVoid.cs similarity index 59% rename from Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs rename to Assets/GameScripts/DotNet/Core/Task/FVoid.cs index e0351892..e1243196 100644 --- a/Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs +++ b/Assets/GameScripts/DotNet/Core/Task/FVoid.cs @@ -1,28 +1,30 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace ET +namespace TEngine { - [AsyncMethodBuilder(typeof (AsyncETVoidMethodBuilder))] - internal struct ETVoid: ICriticalNotifyCompletion + [AsyncMethodBuilder(typeof(AsyncFVoidMethodBuilder))] + [StructLayout(LayoutKind.Auto)] + internal struct FVoid : ICriticalNotifyCompletion { [DebuggerHidden] public void Coroutine() { + } - - [DebuggerHidden] - public bool IsCompleted => true; - + [DebuggerHidden] public void OnCompleted(Action continuation) { + } - + [DebuggerHidden] public void UnsafeOnCompleted(Action continuation) { + } } } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Task/FVoid.cs.meta b/Assets/GameScripts/DotNet/Core/Task/FVoid.cs.meta new file mode 100644 index 00000000..2c704b69 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/FVoid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c43e35df403ff9a468b6805bd64181f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial.meta b/Assets/GameScripts/DotNet/Core/Task/Partial.meta new file mode 100644 index 00000000..f02cda2c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2cc519fac78a9574594b7ee2811a1c68 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs new file mode 100644 index 00000000..0732e6d1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs @@ -0,0 +1,26 @@ +using System; + +namespace TEngine +{ + public partial class FTask + { + public static FTaskCompleted CompletedTask => new(); + + public static FTask Run(Func factory) + { + return factory(); + } + + public static FTask Run(Func> factory) + { + return factory(); + } + + public static FTask FromResult(T value) + { + var sAwaiter = FTask.Create(); + sAwaiter.SetResult(value); + return sAwaiter; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs.meta b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs.meta new file mode 100644 index 00000000..cabd9082 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Factory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac04f3959fd37ad48b2bb6bb7ddb4d46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs new file mode 100644 index 00000000..3bafd6a5 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace TEngine +{ + public partial class FTask + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FTask Create(bool isFromPool = true) + { + var task = isFromPool ? Pool.Rent() : new FTask(); + task._isFromPool = isFromPool; + return task; + } + } + + public partial class FTask + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FTask Create(bool isFromPool = true) + { + var task = isFromPool ? Pool>.Rent() : new FTask(); + task._isFromPool = isFromPool; + return task; + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs.meta b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs.meta new file mode 100644 index 00000000..fd58eb21 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.Pool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfe1e7044d17abb41a98dd6e2ad51896 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs new file mode 100644 index 00000000..de23bd83 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; + +namespace TEngine +{ + public partial class FTask + { + public static async FTask WhenAll(List tasks) + { + if (tasks.Count <= 0) + { + return; + } + + var count = tasks.Count; + var sTaskCompletionSource = Create(); + + foreach (var task in tasks) + { + RunSTask(sTaskCompletionSource, task).Coroutine(); + } + + await sTaskCompletionSource; + + async FVoid RunSTask(FTask tcs, FTask task) + { + await task; + count--; + + if (count <= 0) + { + tcs.SetResult(); + } + } + } + + public static async FTask Any(params FTask[] tasks) + { + if (tasks == null || tasks.Length <= 0) + { + return; + } + + var tcs = FTask.Create(); + + int count = 1; + + foreach (FTask task in tasks) + { + RunSTask(task).Coroutine(); + } + + await tcs; + + async FVoid RunSTask(FTask task) + { + await task; + + count--; + + if (count == 0) + { + tcs.SetResult(); + } + } + } + } + + public partial class FTask + { + public static async FTask WhenAll(List> tasks) + { + if (tasks.Count <= 0) + { + return; + } + + var count = tasks.Count; + var sTaskCompletionSource = FTask.Create(); + + foreach (var task in tasks) + { + RunSTask(sTaskCompletionSource, task).Coroutine(); + } + + await sTaskCompletionSource; + + async FVoid RunSTask(FTask tcs, FTask task) + { + await task; + count--; + if (count == 0) + { + tcs.SetResult(); + } + } + } + + public static async FTask WhenAll(params FTask[] tasks) + { + if (tasks == null || tasks.Length <= 0) + { + return; + } + + var count = tasks.Length; + var tcs = FTask.Create(); + + foreach (var task in tasks) + { + RunSTask(task).Coroutine(); + } + + await tcs; + + async FVoid RunSTask(FTask task) + { + await task; + count--; + if (count == 0) + { + tcs.SetResult(); + } + } + } + + public static async FTask WaitAny(params FTask[] tasks) + { + if (tasks == null || tasks.Length <= 0) + { + return; + } + + var tcs = FTask.Create(); + + int count = 1; + + foreach (FTask task in tasks) + { + RunSTask(task).Coroutine(); + } + + await tcs; + + async FVoid RunSTask(FTask task) + { + await task; + + count--; + + if (count == 0) + { + tcs.SetResult(); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs.meta b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs.meta new file mode 100644 index 00000000..22cbc049 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Task/Partial/FTask.WhenAll.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef20b979a90164a45ad1e3ee2e5b1b09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler.meta new file mode 100644 index 00000000..ffc15c33 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 391584e0a2fa16849bec5654b30a8cdf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface.meta new file mode 100644 index 00000000..c0063ec7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18fd7fd9904bb6a448e89c953dbfa432 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs b/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs new file mode 100644 index 00000000..ac9fb217 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs @@ -0,0 +1,4 @@ +namespace TEngine +{ + public abstract class TimerHandler : EventSystem { } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs.meta new file mode 100644 index 00000000..e2a9af3b --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/Interface/TimerHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b512d458640f63d43983b335c0c71f12 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs new file mode 100644 index 00000000..8de42860 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs @@ -0,0 +1,31 @@ +using System; +using TEngine.Core; +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace TEngine +{ + public sealed class TimerAction : IDisposable + { + public long Id; + public long Time; + public object Callback; + public TimerType TimerType; + + public static TimerAction Create() + { + var timerAction = Pool.Rent(); + timerAction.Id = IdFactory.NextRunTimeId(); + return timerAction; + } + + public void Dispose() + { + Id = 0; + Time = 0; + Callback = null; + TimerType = TimerType.None; + Pool.Return(this); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs.meta new file mode 100644 index 00000000..780593c0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c079aa22ce14d364689992037fcb7184 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs new file mode 100644 index 00000000..0d133329 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs @@ -0,0 +1,21 @@ +using TEngine.Core; +#if TENGINE_UNITY +using UnityEngine; +#endif +namespace TEngine +{ + public sealed class TimerScheduler : Singleton, IUpdateSingleton + { + public readonly TimerSchedulerCore Core = new TimerSchedulerCore(() => TimeHelper.Now); +#if TENGINE_UNITY + public readonly TimerSchedulerCore Unity = new TimerSchedulerCore(() => (long) (Time.time * 1000)); +#endif + public void Update() + { + Core.Update(); +#if TENGINE_UNITY + Unity.Update(); +#endif + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs.meta new file mode 100644 index 00000000..f32177ae --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3565301ee18ba443b84827cd7a38d5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs new file mode 100644 index 00000000..4e3fe27d --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using TEngine.DataStructure; +using TEngine.Core; +#pragma warning disable CS8625 + +namespace TEngine +{ + public class TimerSchedulerCore + { + private long _minTime; + private readonly Func _now; + private readonly Queue _timeOutTime = new(); + private readonly Queue _timeOutTimerIds = new(); + private readonly Dictionary _timers = new(); + private readonly SortedOneToManyList _timeId = new(); + + public TimerSchedulerCore(Func now) + { + _now = now; + } + + public void Update() + { + try + { + var currentTime = _now(); + + if (_timeId.Count == 0) + { + return; + } + + if (currentTime < _minTime) + { + return; + } + + _timeOutTime.Clear(); + _timeOutTimerIds.Clear(); + + foreach (var (key, _) in _timeId) + { + if (key > currentTime) + { + _minTime = key; + break; + } + + _timeOutTime.Enqueue(key); + } + + while (_timeOutTime.TryDequeue(out var time)) + { + foreach (var timerId in _timeId[time]) + { + _timeOutTimerIds.Enqueue(timerId); + } + + _timeId.RemoveKey(time); + } + + while (_timeOutTimerIds.TryDequeue(out var timerId)) + { + if (!_timers.TryGetValue(timerId, out var timer)) + { + continue; + } + + _timers.Remove(timer.Id); + + switch (timer.TimerType) + { + case TimerType.OnceWaitTimer: + { + var tcs = (FTask) timer.Callback; + timer.Dispose(); + tcs.SetResult(true); + break; + } + case TimerType.OnceTimer: + { + var action = (Action) timer.Callback; + timer.Dispose(); + + if (action == null) + { + Log.Error($"timer {timer.ToJson()}"); + break; + } + + action(); + break; + } + case TimerType.RepeatedTimer: + { + var action = (Action) timer.Callback; + AddTimer(_now() + timer.Time, timer); + + if (action == null) + { + Log.Error($"timer {timer.ToJson()}"); + break; + } + + action(); + break; + } + } + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private void AddTimer(long tillTime, TimerAction timer) + { + _timers.Add(timer.Id, timer); + _timeId.Add(tillTime, timer.Id); + + if (tillTime < _minTime) + { + _minTime = tillTime; + } + } + + public async FTask WaitFrameAsync() + { + return await WaitAsync(1); + } + + public async FTask WaitAsync(long time, FCancellationToken cancellationToken = null) + { + return await WaitTillAsync(_now() + time, cancellationToken); + } + + public async FTask WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null) + { + if (_now() > tillTime) + { + return true; + } + + var tcs = FTask.Create(); + var timerAction = TimerAction.Create(); + var timerId = timerAction.Id; + timerAction.Callback = tcs; + timerAction.TimerType = TimerType.OnceWaitTimer; + + void CancelActionVoid() + { + if (!_timers.ContainsKey(timerId)) + { + return; + } + + Remove(timerId); + tcs.SetResult(false); + } + + bool b; + try + { + cancellationToken?.Add(CancelActionVoid); + AddTimer(tillTime, timerAction); + b = await tcs; + } + finally + { + cancellationToken?.Remove(CancelActionVoid); + } + + return b; + } + + public long NewFrameTimer(Action action) + { + return RepeatedTimer(100, action); + } + + public long RepeatedTimer(long time, Action action) + { + if (time <= 0) + { + throw new Exception("repeated time <= 0"); + } + + var tillTime = _now() + time; + var timer = TimerAction.Create(); + timer.TimerType = TimerType.RepeatedTimer; + timer.Time = time; + timer.Callback = action; + AddTimer(tillTime, timer); + return timer.Id; + } + + public long RepeatedTimer(long time, T timerHandlerType) where T : struct + { + void RepeatedTimerVoid() + { + EventSystem.Instance.Publish(timerHandlerType); + } + + return RepeatedTimer(time, RepeatedTimerVoid); + } + + public long OnceTimer(long time, Action action) + { + return OnceTillTimer(_now() + time, action); + } + + public long OnceTimer(long time, T timerHandlerType) where T : struct + { + void OnceTimerVoid() + { + EventSystem.Instance.Publish(timerHandlerType); + } + + return OnceTimer(time, OnceTimerVoid); + } + + public long OnceTillTimer(long tillTime, Action action) + { + if (tillTime < _now()) + { + Log.Error($"new once time too small tillTime:{tillTime} Now:{_now()}"); + } + + var timer = TimerAction.Create(); + timer.TimerType = TimerType.OnceTimer; + timer.Callback = action; + AddTimer(tillTime, timer); + return timer.Id; + } + + public long OnceTillTimer(long tillTime, T timerHandlerType) where T : struct + { + void OnceTillTimerVoid() + { + EventSystem.Instance.Publish(timerHandlerType); + } + + return OnceTillTimer(tillTime, OnceTillTimerVoid); + } + + public void RemoveByRef(ref long id) + { + Remove(id); + id = 0; + } + + public void Remove(long id) + { + if (id == 0 || !_timers.Remove(id, out var timer)) + { + return; + } + + timer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs.meta new file mode 100644 index 00000000..7a04957f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerSchedulerCore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7936ad23c011d3419f3254b935c96a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs new file mode 100644 index 00000000..ac8a321e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs @@ -0,0 +1,10 @@ +namespace TEngine +{ + public enum TimerType + { + None, + OnceWaitTimer, + OnceTimer, + RepeatedTimer + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs.meta b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs.meta new file mode 100644 index 00000000..f330e16c --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/TimerScheduler/TimerType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6919ca6608e0e8840aaedf1f8d09f53f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Unity.meta b/Assets/GameScripts/DotNet/Core/Unity.meta new file mode 100644 index 00000000..752640e4 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f7ad3b14e2e19c43b6112100cce6c42 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs b/Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs new file mode 100644 index 00000000..e28c1b73 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs @@ -0,0 +1,36 @@ +#if TENGINE_UNITY +using System.Threading; +using TEngine.Core; + +namespace TEngine +{ + public struct OnAppStart + { + public Scene ClientScene; + } + + public struct OnAppClosed { } + + public static class ApplicationContext + { + public static void Initialize() + { + // è®¾ç½®é»˜è®¤çš„çº¿ç¨‹çš„åŒæ­¥ä¸Šä¸‹æ–‡ + SynchronizationContext.SetSynchronizationContext(ThreadSynchronizationContext.Main); + // åˆå§‹åŒ–SingletonSystemCenterè¿™ä¸ªä¸€å®šè¦æ”¾åˆ°æœ€å‰é¢ + // 因为SingletonSystem会注册AssemblyManagerçš„OnLoadAssemblyEventå’ŒOnUnLoadAssemblyEvent的事件 + // 如果ä¸è¿™æ ·ã€ä¼šæ— æ³•把程åºé›†çš„å•例注册到SingletonManager中 + SingletonSystem.Initialize(); + // 加载核心程åºé›† + AssemblyManager.Initialize(); + } + + public static void Close() + { + SingletonSystem.Dispose(); + AssemblyManager.Dispose(); + Scene.DisposeAllScene(); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs.meta b/Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs.meta new file mode 100644 index 00000000..65e2a5ef --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity/ApplicationContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7db9c21fafd32054580caf1503ba2f17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs b/Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs new file mode 100644 index 00000000..d50efded --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs @@ -0,0 +1,16 @@ +public static class AssemblyName +{ + // 这个对应的是NetCore工程 + public const int AssemblyDotNet = 1; + + public const int GameBase = 10; + + public const int GameLogic = 20; + + public const int GameProto = 30; + + // ä½ å¯ä»¥æ·»åŠ å¤šä¸ªå·¥ç¨‹ã€å¦‚果有新添加的å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ ã€ + // 并在AssemblyLoadHelper里添加对应的加载逻辑 + // å‚考LoadModelDll这个方法 + // 这样添加是为了方便åŽé¢çƒ­é‡è½½ä½¿ç”¨ +} diff --git a/Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs.meta b/Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs.meta new file mode 100644 index 00000000..dd72fd39 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity/AssemblyName.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44af1c6e3b8a24b40a198c068993596d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs b/Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs new file mode 100644 index 00000000..1d67a8af --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs @@ -0,0 +1,40 @@ +#if TENGINE_UNITY +using TEngine.Core; +using UnityEngine; + +namespace TEngine +{ + public class GameAppEntry : MonoBehaviour + { + /// + /// åˆå§‹åŒ–框架 + /// + public static Scene Initialize() + { + // åˆå§‹åŒ–框架 + ApplicationContext.Initialize(); + new GameObject("[TEngine.Unity]").AddComponent(); + // 框架需è¦ä¸€ä¸ªSceneæ¥é©±åŠ¨ã€æ‰€ä»¥è¦åˆ›å»ºä¸€ä¸ªSceneã€åŽé¢æ‰€æœ‰çš„æ¡†æž¶éƒ½ä¼šåœ¨è¿™ä¸ªScene下 + // 也就是把这个Sceneç»™å¸è½½æŽ‰ã€æ¡†æž¶çš„东西都会清除掉 + return Scene.Create("Unity"); + } + + public void Awake() + { + DontDestroyOnLoad(gameObject); + } + + private void Update() + { + ThreadSynchronizationContext.Main.Update(); + SingletonSystem.Update(); + } + + private void OnApplicationQuit() + { + EventSystem.Instance?.Publish(new OnAppClosed()); + ApplicationContext.Close(); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs.meta b/Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs.meta new file mode 100644 index 00000000..f32a97cc --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Unity/GameAppEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fcb9cd4fe550bbe4083cc7c54ae9c0ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/DotNet.asmdef b/Assets/GameScripts/DotNet/DotNet.asmdef index 71ede0e1..524dfd86 100644 --- a/Assets/GameScripts/DotNet/DotNet.asmdef +++ b/Assets/GameScripts/DotNet/DotNet.asmdef @@ -4,7 +4,8 @@ "references": [ "GUID:d020df1f2b63b444e8ca93c0d88597e2", "GUID:756335c0388f7114790e504ed368ae1d", - "GUID:d8b63aba1907145bea998dd612889d6b" + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:aa06d4cc755c979489c256c1bcca1dfb" ], "includePlatforms": [], "excludePlatforms": [], @@ -13,8 +14,7 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [ - "!ENABLE_DLL", - "UNITY" + "!ENABLE_DLL" ], "versionDefines": [], "noEngineReferences": false diff --git a/Assets/GameScripts/DotNet/Logic.meta b/Assets/GameScripts/DotNet/Logic.meta new file mode 100644 index 00000000..2ee902d6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af6fed2b79cb79e4593f921943c3357d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/CustomExport.meta b/Assets/GameScripts/DotNet/Logic/CustomExport.meta new file mode 100644 index 00000000..bd541f64 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/CustomExport.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9d91af67c28edce489bc064458254a30 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs b/Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs new file mode 100644 index 00000000..f5b035a8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs @@ -0,0 +1,49 @@ +#if TENGINE_NET +using System.Text; +using TEngine.Core; + +namespace TEngine.Model; + +public sealed class SceneTypeConfigToEnum : ACustomExport +{ + public override void Run() + { + var serverSceneType = new HashSet(); + var instanceList = SceneConfigData.Instance.List; + + foreach (var sceneConfig in instanceList) + { + serverSceneType.Add(sceneConfig.SceneType); + } + + if (serverSceneType.Count > 0) + { + Write(CustomExportType.Server, serverSceneType); + } + } + + private void Write(CustomExportType customExportType, HashSet sceneTypes) + { + var index = 0; + var strBuilder = new StringBuilder(); + var dicBuilder = new StringBuilder(); + + strBuilder.AppendLine("namespace TEngine\n{"); + strBuilder.AppendLine("\t// 生æˆå™¨è‡ªåŠ¨ç”Ÿæˆï¼Œè¯·ä¸è¦æ‰‹åŠ¨ç¼–è¾‘ã€‚"); + strBuilder.AppendLine("\tpublic class SceneType\n\t{"); + dicBuilder.AppendLine("\n\t\tpublic static readonly Dictionary SceneDic = new Dictionary()\n\t\t{"); + + foreach (var str in sceneTypes) + { + index++; + dicBuilder.AppendLine($"\t\t\t{{ \"{str}\", {index} }},"); + strBuilder.AppendLine($"\t\tpublic const int {str} = {index};"); + } + + dicBuilder.AppendLine("\t\t};"); + strBuilder.Append(dicBuilder); + strBuilder.AppendLine("\t}\n}"); + Write("SceneType.cs", strBuilder.ToString(), customExportType); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs.meta b/Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs.meta new file mode 100644 index 00000000..5e2f39a8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/CustomExport/SceneTypeConfigToEnum.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e12aa9c235cdfe84ca74924639cd6d22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Entry.cs b/Assets/GameScripts/DotNet/Logic/Entry.cs new file mode 100644 index 00000000..c4c4d4f9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Entry.cs @@ -0,0 +1,11 @@ +#if TENGINE_NET +namespace TEngine.Logic; + +public static class Entry +{ + public static async FTask Start() + { + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Entry.cs.meta b/Assets/GameScripts/DotNet/Logic/Entry.cs.meta new file mode 100644 index 00000000..df83fc52 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Entry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fcece90443154e49a5a4bd57bbe7e82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable.meta b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable.meta new file mode 100644 index 00000000..fdedc34f --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1b8404cea481db34c91f2ba7567620db +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity.meta b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity.meta new file mode 100644 index 00000000..f7fb3419 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc782e6aee9c7654a85ff3e8178e1f53 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs new file mode 100644 index 00000000..9d3e975b --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs @@ -0,0 +1,88 @@ +using System; +using ProtoBuf; +using TEngine.Core; +using System.Linq; +using System.Collections.Generic; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace TEngine +{ + [ProtoContract] + public sealed partial class MachineConfigData : AProto, IConfigTable, IDisposable + { + [ProtoMember(1)] + public List List { get; set; } = new List(); + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); + private static MachineConfigData _instance; + + public static MachineConfigData Instance + { + get { return _instance ??= ConfigTableManage.Load(); } + private set => _instance = value; + } + + public MachineConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"MachineConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out MachineConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + for (var i = 0; i < List.Count; i++) + { + MachineConfig config = List[i]; + _configs.Add(config.Id, config); + config.AfterDeserialization(); + } + + base.AfterDeserialization(); + } + + public void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class MachineConfig : AProto + { + [ProtoMember(1, IsRequired = true)] + public uint Id { get; set; } // Id + [ProtoMember(2, IsRequired = true)] + public string OuterIP { get; set; } // 外网IP + [ProtoMember(3, IsRequired = true)] + public string OuterBindIP { get; set; } // 外网绑定IP + [ProtoMember(4, IsRequired = true)] + public string InnerBindIP { get; set; } // 内网绑定IP + [ProtoMember(5, IsRequired = true)] + public int ManagementPort { get; set; } // 管ç†ç«¯å£ + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs.meta new file mode 100644 index 00000000..acb7c8b0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/MachineConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9853ff3c8cde9a34f89c6e77f01c2b0f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs new file mode 100644 index 00000000..4e3ec6df --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs @@ -0,0 +1,94 @@ +using System; +using ProtoBuf; +using TEngine.Core; +using System.Linq; +using System.Collections.Generic; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace TEngine +{ + [ProtoContract] + public sealed partial class SceneConfigData : AProto, IConfigTable, IDisposable + { + [ProtoMember(1)] + public List List { get; set; } = new List(); + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); + private static SceneConfigData _instance; + + public static SceneConfigData Instance + { + get { return _instance ??= ConfigTableManage.Load(); } + private set => _instance = value; + } + + public SceneConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"SceneConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out SceneConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + for (var i = 0; i < List.Count; i++) + { + SceneConfig config = List[i]; + _configs.Add(config.Id, config); + config.AfterDeserialization(); + } + + base.AfterDeserialization(); + } + + public void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class SceneConfig : AProto + { + [ProtoMember(1, IsRequired = true)] + public uint Id { get; set; } // ID + [ProtoMember(2, IsRequired = true)] + public long EntityId { get; set; } // 实体Id + [ProtoMember(3, IsRequired = true)] + public uint RouteId { get; set; } // 路由Id + [ProtoMember(4, IsRequired = true)] + public uint WorldId { get; set; } // 世界Id + [ProtoMember(5, IsRequired = true)] + public string SceneType { get; set; } // Scene类型 + [ProtoMember(6, IsRequired = true)] + public string Name { get; set; } // åç§° + [ProtoMember(7, IsRequired = true)] + public string NetworkProtocol { get; set; } // å议类型 + [ProtoMember(8, IsRequired = true)] + public int OuterPort { get; set; } // å¤–ç½‘ç«¯å£ + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs.meta new file mode 100644 index 00000000..8562497b --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/SceneConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e4c00f667c811947b6c191342eab7ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs new file mode 100644 index 00000000..40d1c128 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs @@ -0,0 +1,86 @@ +using System; +using ProtoBuf; +using TEngine.Core; +using System.Linq; +using System.Collections.Generic; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace TEngine +{ + [ProtoContract] + public sealed partial class ServerConfigData : AProto, IConfigTable, IDisposable + { + [ProtoMember(1)] + public List List { get; set; } = new List(); + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); + private static ServerConfigData _instance; + + public static ServerConfigData Instance + { + get { return _instance ??= ConfigTableManage.Load(); } + private set => _instance = value; + } + + public ServerConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"ServerConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out ServerConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + for (var i = 0; i < List.Count; i++) + { + ServerConfig config = List[i]; + _configs.Add(config.Id, config); + config.AfterDeserialization(); + } + + base.AfterDeserialization(); + } + + public void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class ServerConfig : AProto + { + [ProtoMember(1, IsRequired = true)] + public uint Id { get; set; } // 路由Id + [ProtoMember(2, IsRequired = true)] + public uint MachineId { get; set; } // 机器ID + [ProtoMember(3, IsRequired = true)] + public int InnerPort { get; set; } // å†…ç½‘ç«¯å£ + [ProtoMember(4, IsRequired = true)] + public bool ReleaseMode { get; set; } // Release下è¿è¡Œ + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs.meta new file mode 100644 index 00000000..0309c0ad --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/ServerConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 86a653380b833dc4abd9816ea3cc27d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs new file mode 100644 index 00000000..2ccb8545 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs @@ -0,0 +1,90 @@ +using System; +using ProtoBuf; +using TEngine.Core; +using System.Linq; +using System.Collections.Generic; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace TEngine +{ + [ProtoContract] + public sealed partial class WorldConfigData : AProto, IConfigTable, IDisposable + { + [ProtoMember(1)] + public List List { get; set; } = new List(); + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); + private static WorldConfigData _instance; + + public static WorldConfigData Instance + { + get { return _instance ??= ConfigTableManage.Load(); } + private set => _instance = value; + } + + public WorldConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"WorldConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out WorldConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + for (var i = 0; i < List.Count; i++) + { + WorldConfig config = List[i]; + _configs.Add(config.Id, config); + config.AfterDeserialization(); + } + + base.AfterDeserialization(); + } + + public void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class WorldConfig : AProto + { + [ProtoMember(1, IsRequired = true)] + public uint Id { get; set; } // Id + [ProtoMember(2, IsRequired = true)] + public string WorldName { get; set; } // åç§° + [ProtoMember(3, IsRequired = true)] + public string DbConnection { get; set; } // 连接字符串 + [ProtoMember(4, IsRequired = true)] + public string DbName { get; set; } // æ•°æ®åº“åç§° + [ProtoMember(5, IsRequired = true)] + public string DbType { get; set; } // æ•°æ®åº“类型 + [ProtoMember(6, IsRequired = true)] + public bool IsGameWorld { get; set; } // æ˜¯å¦æ¸¸æˆæœ + } +} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs.meta new file mode 100644 index 00000000..840db25d --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/ConfigTable/Entity/WorldConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0effffaeff49200459ca1ea5dce76982 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport.meta b/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport.meta new file mode 100644 index 00000000..ad69c826 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 34d6b1a4a6ca2eb4390f6ce02c611461 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs b/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs new file mode 100644 index 00000000..7f60d994 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace TEngine +{ + // 生æˆå™¨è‡ªåŠ¨ç”Ÿæˆï¼Œè¯·ä¸è¦æ‰‹åŠ¨ç¼–è¾‘ã€‚ + public class SceneType + { + public const int Gate = 1; + public const int Addressable = 2; + public const int Map = 3; + public const int Chat = 4; + + public static readonly Dictionary SceneDic = new Dictionary() + { + { "Gate", 1 }, + { "Addressable", 2 }, + { "Map", 3 }, + { "Chat", 4 }, + }; + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs.meta new file mode 100644 index 00000000..a6b2d9a7 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/CustomExport/SceneType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e77592eb467992947a57a22ed5249c0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol.meta new file mode 100644 index 00000000..60b881b1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fb7989dfa4f501845a696744157d57b2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs new file mode 100644 index 00000000..56dfd4cf --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs @@ -0,0 +1,9 @@ +using ProtoBuf; +using Unity.Mathematics; +using System.Collections.Generic; +using TEngine.Core.Network; +#pragma warning disable CS8618 + +namespace TEngine +{ +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs.meta new file mode 100644 index 00000000..f93061d9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c2857a2685547246874c19eb182188a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs new file mode 100644 index 00000000..bdae94a6 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs @@ -0,0 +1,6 @@ +namespace TEngine +{ + public static partial class InnerBsonOpcode + { + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs.meta new file mode 100644 index 00000000..34cf4cc1 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerBsonOpcode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec1d3cc361a3cdf498afd9bef149ded6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs new file mode 100644 index 00000000..0f446035 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs @@ -0,0 +1,31 @@ +using ProtoBuf; +using Unity.Mathematics; +using System.Collections.Generic; +using TEngine.Core.Network; +#pragma warning disable CS8618 + +namespace TEngine +{ + /// + /// Gateè·ŸMapæœåŠ¡å™¨è¿›è¡Œé€šè®¯ã€æ³¨å†ŒAddressåè®® + /// + [ProtoContract] + public partial class I_G2M_LoginAddressRequest : AProto, IRouteRequest + { + [ProtoIgnore] + public I_M2G_LoginAddressResponse ResponseType { get; set; } + public uint OpCode() { return InnerOpcode.I_G2M_LoginAddressRequest; } + public long RouteTypeOpCode() { return CoreRouteType.Route; } + [ProtoMember(1)] + public long AddressId { get; set; } + [ProtoMember(2)] + public long GateRouteId { get; set; } + } + [ProtoContract] + public partial class I_M2G_LoginAddressResponse : AProto, IRouteResponse + { + public uint OpCode() { return InnerOpcode.I_M2G_LoginAddressResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs.meta new file mode 100644 index 00000000..e97dc740 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a087e0d601b77a34c91d2a27a3caaa59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs new file mode 100644 index 00000000..770230a0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs @@ -0,0 +1,8 @@ +namespace TEngine +{ + public static partial class InnerOpcode + { + public const int I_G2M_LoginAddressRequest = 220001001; + public const int I_M2G_LoginAddressResponse = 260001001; + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs.meta new file mode 100644 index 00000000..fe1eb946 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/InnerOpcode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b495abbd9ecfd7547a2c986416e701fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs new file mode 100644 index 00000000..1e8234f9 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs @@ -0,0 +1,157 @@ +using ProtoBuf; +using Unity.Mathematics; +using System.Collections.Generic; +using TEngine.Core.Network; +#pragma warning disable CS8618 + +namespace TEngine +{ + /// + /// å‘é€ä¸€ä¸ªæ¶ˆæ¯åˆ°GateæœåС噍 + /// + [ProtoContract] + public partial class H_C2G_Message : AProto, IMessage + { + public uint OpCode() { return OuterOpcode.H_C2G_Message; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// å‘é€ä¸€ä¸ªRPC消æ¯åˆ°GateæœåС噍 + /// + [ProtoContract] + public partial class H_C2G_MessageRequest : AProto, IRequest + { + [ProtoIgnore] + public H_G2C_MessageResponse ResponseType { get; set; } + public uint OpCode() { return OuterOpcode.H_C2G_MessageRequest; } + [ProtoMember(1)] + public string Message { get; set; } + } + [ProtoContract] + public partial class H_G2C_MessageResponse : AProto, IResponse + { + public uint OpCode() { return OuterOpcode.H_G2C_MessageResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// å‘é€ä¸€ä¸ªæ¶ˆæ¯é€šçŸ¥æœåŠ¡å™¨ç»™å®¢æˆ·ç«¯æŽ¨é€ä¸€ä¸ªæ¶ˆæ¯ + /// + [ProtoContract] + public partial class H_C2G_PushMessageToClient : AProto, IMessage + { + public uint OpCode() { return OuterOpcode.H_C2G_PushMessageToClient; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// 客户端接收æœåŠ¡å™¨æŽ¨é€çš„ä¸€æ¡æ¶ˆæ¯ + /// + [ProtoContract] + public partial class H_G2C_ReceiveMessageToServer : AProto, IMessage + { + public uint OpCode() { return OuterOpcode.H_G2C_ReceiveMessageToServer; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// 注册Addressæ¶ˆæ¯ + /// + [ProtoContract] + public partial class H_C2G_LoginAddressRequest : AProto, IRequest + { + [ProtoIgnore] + public H_G2C_LoginAddressResponse ResponseType { get; set; } + public uint OpCode() { return OuterOpcode.H_C2G_LoginAddressRequest; } + [ProtoMember(1)] + public string Message { get; set; } + } + [ProtoContract] + public partial class H_G2C_LoginAddressResponse : AProto, IResponse + { + public uint OpCode() { return OuterOpcode.H_G2C_LoginAddressResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + } + /// + /// å‘é€ä¸€ä¸ªAddress消æ¯ç»™Map + /// + [ProtoContract] + public partial class H_C2M_Message : AProto, IAddressableRouteMessage + { + public uint OpCode() { return OuterOpcode.H_C2M_Message; } + public long RouteTypeOpCode() { return CoreRouteType.Addressable; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// å‘é€ä¸€ä¸ªAddressRPC消æ¯ç»™Map + /// + [ProtoContract] + public partial class H_C2M_MessageRequest : AProto, IAddressableRouteRequest + { + [ProtoIgnore] + public H_M2C_MessageResponse ResponseType { get; set; } + public uint OpCode() { return OuterOpcode.H_C2M_MessageRequest; } + public long RouteTypeOpCode() { return CoreRouteType.Addressable; } + [ProtoMember(1)] + public string Message { get; set; } + } + [ProtoContract] + public partial class H_M2C_MessageResponse : AProto, IAddressableRouteResponse + { + public uint OpCode() { return OuterOpcode.H_M2C_MessageResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// å‘é€ä¸€ä¸ªæ¶ˆæ¯é€šçŸ¥æœåŠ¡å™¨ç»™å®¢æˆ·ç«¯æŽ¨é€ä¸€ä¸ªAddressæ¶ˆæ¯ + /// + [ProtoContract] + public partial class H_C2M_PushAddressMessageToClient : AProto, IAddressableRouteMessage + { + public uint OpCode() { return OuterOpcode.H_C2M_PushAddressMessageToClient; } + public long RouteTypeOpCode() { return CoreRouteType.Addressable; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// 客户端接收æœåŠ¡å™¨æŽ¨é€çš„一æ¡Addressæ¶ˆæ¯ + /// + [ProtoContract] + public partial class H_M2C_ReceiveAddressMessageToServer : AProto, IAddressableRouteMessage + { + public uint OpCode() { return OuterOpcode.H_M2C_ReceiveAddressMessageToServer; } + public long RouteTypeOpCode() { return CoreRouteType.Addressable; } + [ProtoMember(1)] + public string Message { get; set; } + } + /// + /// 客户端å‘逿¶ˆæ¯è¯·æ±‚登录æœåС噍 + /// + [ProtoContract] + public partial class H_C2G_LoginRequest : AProto, IRequest + { + [ProtoIgnore] + public H_G2C_LoginResponse ResponseType { get; set; } + public uint OpCode() { return OuterOpcode.H_C2G_LoginRequest; } + [ProtoMember(1)] + public string UserName { get; set; } + [ProtoMember(2)] + public string Password { get; set; } + } + [ProtoContract] + public partial class H_G2C_LoginResponse : AProto, IResponse + { + public uint OpCode() { return OuterOpcode.H_G2C_LoginResponse; } + [ProtoMember(91, IsRequired = true)] + public int ErrorCode { get; set; } + [ProtoMember(1)] + public string Text { get; set; } + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs.meta new file mode 100644 index 00000000..8c357eb3 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fed81665632c0eb40a9704d33e8a3b22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs new file mode 100644 index 00000000..0aaa766c --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs @@ -0,0 +1,20 @@ +namespace TEngine +{ + public static partial class OuterOpcode + { + public const int H_C2G_Message = 100000001; + public const int H_C2G_MessageRequest = 110000001; + public const int H_G2C_MessageResponse = 160000001; + public const int H_C2G_PushMessageToClient = 100000002; + public const int H_G2C_ReceiveMessageToServer = 100000003; + public const int H_C2G_LoginAddressRequest = 110000002; + public const int H_G2C_LoginAddressResponse = 160000002; + public const int H_C2M_Message = 190000001; + public const int H_C2M_MessageRequest = 200000001; + public const int H_M2C_MessageResponse = 250000001; + public const int H_C2M_PushAddressMessageToClient = 190000002; + public const int H_M2C_ReceiveAddressMessageToServer = 190000003; + public const int H_C2G_LoginRequest = 110000003; + public const int H_G2C_LoginResponse = 160000003; + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs.meta new file mode 100644 index 00000000..d2444b39 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/OuterOpcode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47365b17af2f7c744aa93ff7e9dfec0e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs new file mode 100644 index 00000000..1892c3db --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs @@ -0,0 +1,8 @@ +namespace TEngine.Core.Network +{ + // Routeå议定义(需è¦å®šä¹‰1000以上ã€å› ä¸º1000以内的框架预留) + public enum RouteType : long + { + ChatRoute = 1001, // èŠå¤©æœåè®® + } +} diff --git a/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs.meta b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs.meta new file mode 100644 index 00000000..8e668043 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/RouteType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b6880642b7d6ca4b995a988465c77a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler.meta b/Assets/GameScripts/DotNet/Logic/Handler.meta new file mode 100644 index 00000000..43fb0067 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b1677506e5cd7b347b38dd75ee4d0706 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address.meta b/Assets/GameScripts/DotNet/Logic/Handler/Address.meta new file mode 100644 index 00000000..ca28bdce --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f4621a08083c52d4a8b1a1b81e8cbef7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs new file mode 100644 index 00000000..5d3d98be --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs @@ -0,0 +1,15 @@ +#if TENGINE_NET +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic; + +public class H_C2M_MessageHandler : Addressable +{ + protected override async FTask Run(Unit unit, H_C2M_Message message) + { + Log.Debug($"接收到一个Addressæ¶ˆæ¯ Unit:{unit.Id} message:{message.ToJson()}"); + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs.meta new file mode 100644 index 00000000..9adb5c8f --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7292412e46412f341afe261729a0261c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs new file mode 100644 index 00000000..08eb7b8a --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs @@ -0,0 +1,16 @@ +#if TENGINE_NET +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic; + +public class H_C2M_MessageRequestHandler : AddressableRPC +{ + protected override async FTask Run(Unit unit, H_C2M_MessageRequest request, H_M2C_MessageResponse response, Action reply) + { + Log.Debug($"接收到一个AddressRPCæ¶ˆæ¯ Unit:{unit.Id} message:{request.ToJson()}"); + response.Message = "æ¥è‡ªMAPæœåС噍å‘é€çš„æ¶ˆæ¯"; + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs.meta new file mode 100644 index 00000000..a6d55662 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_MessageRequestHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cb9669623912ce48bdcad365cc1c26c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs new file mode 100644 index 00000000..1543fe8c --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs @@ -0,0 +1,21 @@ +#if TENGINE_NET +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic; + +public class H_C2M_PushAddressMessageToClientHandler : Addressable +{ + protected override async FTask Run(Unit unit, H_C2M_PushAddressMessageToClient message) + { + Log.Debug($"接收到一个Addressæ¶ˆæ¯ Unit:{unit.Id} message:{message.ToJson()}"); + // å‘过主动推é€ç»™å®¢æˆ·ç«¯ã€é¦–å…ˆè¦åªè¦Unit在Gateçš„RouteIdã€ç„¶åŽæ‰å¯ä»¥å‘é€ + // 这个Route在I_G2M_LoginAddressRequestã€ä¹Ÿå°±æ˜¯æ³¨å†ŒAddress的时候已ç»è®°å½•了 + MessageHelper.SendInnerRoute(unit.Scene, unit.GateRouteId, new H_M2C_ReceiveAddressMessageToServer() + { + Message = message.Message + }); + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs.meta new file mode 100644 index 00000000..9244e3bc --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/Address/H_C2M_PushAddressMessageToClientHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2030d3d4928a1524d8858ba1c485f562 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs new file mode 100644 index 00000000..b0962f8a --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs @@ -0,0 +1,55 @@ +#if TENGINE_NET +using TEngine.Core.Network; + +namespace TEngine.Logic; + +public class H_C2G_LoginAddressRequestHandler : MessageRPC +{ + protected override async FTask Run(Session session, H_C2G_LoginAddressRequest request, H_G2C_LoginAddressResponse response, Action reply) + { + // 什么是å¯å¯»å€æ¶ˆæ¯ + // æ­¤æœåŠ¡å™¨æ˜¯ä¸€ä¸ªåˆ†å¸ƒå¼æ¡†æž¶ã€æ‰€ä»¥è‚¯å®šä¼šæœ‰å¤šä¸ªæœåŠ¡å™¨ç›¸äº’é€šä¿¡ + // 游æˆé‡Œä¸€ä¸ªçŽ©å®¶è¿™é‡Œç»Ÿç§°Unitã€éšç€æ¸¸æˆåœºæ™¯çš„è¶Šæ¥è¶Šå¤šã€ä¸ºäº†è®©æ¸¸æˆæœåŠ¡å™¨èƒ½æ‰¿è½½æ›´å¤šäºº + // 所以大家都会把æœåŠ¡å™¨ç»™åˆ†æˆå¤šä¸ªã€æ¯”如一个地图一个æœåС噍ã€ä»Žè€Œèƒ½æå‡æœåŠ¡å™¨çš„è´Ÿè½½èƒ½åŠ›ã€ä¹Ÿæ›´æ–¹ä¾¿å¼€å‘ã€ç±»ä¼¼å¾®æœåŠ¡ + // 但这样就会有几个问题出现了: + // 1ã€å®¢æˆ·ç«¯æ˜¯ç›´æŽ¥è¿žæŽ¥åˆ°æœåС噍ã€å¦‚æžœåˆ‡æ¢æœåŠ¡å™¨çš„æ—¶å€™å®¢æˆ·ç«¯å°±ä¼šæŽ‰çº¿ã€æ€Žä¹ˆè§£å†³ä¸è®©ç”¨æˆ·æŽ‰çº¿ + // 2ã€ä»ŽAæœåŠ¡å™¨åˆ°BæœåС噍åŽã€å¦‚果能正常å‘é€åˆ°BæœåŠ¡å™¨è€Œä¸æ˜¯å‘é€åˆ°AæœåС噍䏭 + // 为了解决上é¢çš„问题ã€å¤§å¤šæœåŠ¡å™¨æž¶æž„éƒ½é‡‡ç”¨äº†ä¸€ä¸ªä¸­ä¸“æœåŠ¡å™¨ï¼ˆGateï¼‰æ¥æ”¶å‘æ¶ˆæ¯ + // 比如客户端一直连接的一个æœåŠ¡å™¨è¿™é‡Œç»Ÿç§°Gate + // é‚£ç½‘ç»œé€šè®¯çš„ç®¡é“æ¨¡åž‹æ˜¯å®¢æˆ·ç«¯->Gate->å…¶ä»–æœåС噍ã€å®¢æˆ·ç«¯æŽ¥æ”¶æ¶ˆæ¯æ˜¯å…¶ä»–æœåС噍->Gate->客户端 + // 这样的好处是无论玩家在什么æœåС噍åªéœ€è¦æ”¹å˜Gate到其他æœåŠ¡å™¨çš„è¿žæŽ¥å°±å¯ä»¥äº†ã€ä¸­é—´å®¢æˆ·ç«¯æ˜¯ä¸€ç›´è¿žæŽ¥åˆ°Gate的所以客户端ä¸ä¼šæŽ‰çº¿ + // 这个问题解决了ã€ä½†è¿˜æœ‰ä¸€ä¸ªé—®é¢˜å°±æ˜¯Gateè·Ÿå…¶ä»–æœåŠ¡å™¨çš„è¿žæŽ¥ä¼šéšç€çŽ©å®¶çš„é€»è¾‘å˜åЍ + // 所以框架æä¾›çš„å¯å¯»å€æ¶ˆæ¯ï¼ˆAddress)消æ¯ã€ä½¿ç”¨Address消æ¯åŽä¼šè‡ªåŠ¨å¯»æ‰¾åˆ°Unit的正确ä½ç½®å¹¶å‘é€åˆ°ã€ä¸éœ€è¦å¼€å‘者å†å¤„ç†è¿™ä¸ªé€»è¾‘了 + // 下é¢å°±æ˜¯ä¸€ä¸ªä¾‹å­ + // 1ã€é¦–选分é…一个å¯ç”¨ã€è´Ÿè½½æ¯”较低的æœåŠ¡å™¨ç»™è¿™ä¸ªUnitã€æˆ‘这里就在ServerConfig.xsl表里拿一个MAP了ã€ä½†å®žé™…å¼€å‘过程å¯èƒ½æ¯”这个è¦å¤æ‚ + // 我这里就简å•些一个åšä¸ºæ¼”示ã€å…¶å®žè¿™äº›é€»è¾‘å¼€å‘者完全å¯ä»¥è‡ªå·±å°è£…ä¸€ä¸ªæŽ¥å£æ¥åšã€‚ + // 在ServerConfig.xsl里找到MAP的进程ã€çœ‹åˆ°ID是3072通过这个Id在SceneConfig.xsl里找到对应的Sceneçš„EntityId + var sceneEntityId = 0L; + foreach (var sceneConfig in SceneConfigData.Instance.List) + { + if (sceneConfig.RouteId == 3072) + { + sceneEntityId = sceneConfig.EntityId; + break; + } + + continue; + } + // 2ã€åœ¨InnerMessage里定义一个åè®®ã€ç”¨äºŽGateè·ŸMap通讯的åè®®I_G2M_LoginAddress + var loginAddressResponse = (I_M2G_LoginAddressResponse)await MessageHelper.CallInnerRoute(session.Scene, + sceneEntityId, + new I_G2M_LoginAddressRequest() + { + AddressId = session.Id, + GateRouteId = session.RuntimeId, + }); + if (loginAddressResponse.ErrorCode != 0) + { + Log.Error($"注册到Mapçš„Addresså‘生错误 ErrorCode:{loginAddressResponse.ErrorCode}"); + return; + } + // 3ã€å¯å¯»å€æ¶ˆæ¯ç»„ä»¶ã€æŒ‚载了这个组件å¯ä»¥æŽ¥æ”¶å’Œå‘é€Addressableæ¶ˆæ¯ + session.AddComponent(); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs.meta new file mode 100644 index 00000000..0c25cd1e --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginAddressRequestHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a889d7a63c3f4754486afb58e40418a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs new file mode 100644 index 00000000..47903125 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs @@ -0,0 +1,18 @@ +#if TENGINE_NET +using System; +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic +{ + public class H_C2G_LoginRequestHandler : MessageRPC + { + protected override async FTask Run(Session session, H_C2G_LoginRequest request, H_G2C_LoginResponse response, Action reply) + { + Log.Debug($"æ”¶åˆ°è¯·æ±‚ç™»å½•çš„æ¶ˆæ¯ request:{request.ToJson()}"); + response.Text = "登录æˆåŠŸ"; + await FTask.CompletedTask; + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs.meta new file mode 100644 index 00000000..bd822e64 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_LoginRequestHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43f82d10a26d78443801db581ed7251e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs new file mode 100644 index 00000000..5168d1e0 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs @@ -0,0 +1,16 @@ +#if TENGINE_NET +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic +{ + public class H_C2G_MessageHandler : Message + { + protected override async FTask Run(Session session, H_C2G_Message message) + { + Log.Debug($"æŽ¥æ”¶åˆ°æ¶ˆæ¯ H_C2G_Message:{message.ToJson()}"); + await FTask.CompletedTask; + } + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs.meta new file mode 100644 index 00000000..574505f8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dec8374fe05a89344a64674e05b06239 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs new file mode 100644 index 00000000..6169600a --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs @@ -0,0 +1,18 @@ +#if TENGINE_NET +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic; + +public class H_C2G_MessageRequestHandler : MessageRPC +{ + protected override async FTask Run(Session session, H_C2G_MessageRequest request, H_G2C_MessageResponse response, Action reply) + { + // 这里是接收到客户端å‘é€çš„æ¶ˆæ¯ + Log.Debug($"接收到RPCæ¶ˆæ¯ H_C2G_MessageRequest:{request.ToJson()}"); + // response是è¦ç»™å®¢æˆ·ç«¯è¿”回的消æ¯ã€æ•°æ®ç»“构是在proto文件里定义的 + response.Message = "Hello worldï¼Œæ‚¨çŽ°åœ¨æ”¶åˆ°çš„æ¶ˆæ¯æ˜¯ä¸€ä¸ªRPC消æ¯"; + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs.meta new file mode 100644 index 00000000..af058d73 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_MessageRequestHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4283804f211765a42b5a81aba74e2666 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs new file mode 100644 index 00000000..b09384f2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs @@ -0,0 +1,20 @@ +#if TENGINE_NET +using TEngine.Core.Network; +using TEngine.Core; + +namespace TEngine.Logic; + +public class H_C2G_PushMessageToClientHandler : Message +{ + protected override async FTask Run(Session session, H_C2G_PushMessageToClient message) + { + Log.Debug($"接收到客户端å‘é€ç»™æœåŠ¡å™¨çš„è¯·æ±‚æŽ¨é€æ¶ˆæ¯:{message.ToJson()}"); + // æœåŠ¡å™¨ä¸»åŠ¨æŽ¨é€ç»™å®¢æˆ·ç«¯æ¶ˆæ¯ + session.Send(new H_G2C_ReceiveMessageToServer() + { + Message = "这个是æœåŠ¡å™¨æŽ¨é€ç»™å®¢æˆ·ç«¯çš„æ¶ˆæ¯" + }); + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs.meta new file mode 100644 index 00000000..dd82fe42 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/H_C2G_PushMessageToClientHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 958e9874d43c5834cb0739b90d644725 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs b/Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs new file mode 100644 index 00000000..1fe91d43 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs @@ -0,0 +1,46 @@ +#if TENGINE_NET +using System; +using System.Collections.Generic; +using TEngine.Core.Network; + +namespace TEngine.Logic +{ + public class Unit : Entity + { + public long GateRouteId; + } + + public static class AddressManage + { + public static readonly Dictionary Units = new Dictionary(); + + public static Unit Add(Scene scene, long addressId, long gateRouteId) + { + var unit = Entity.Create(scene, addressId); + unit.GateRouteId = gateRouteId; + Units.Add(unit.Id, unit); + return unit; + } + + public static Unit? Get(long addressId) + { + return Units.TryGetValue(addressId, out var unit) ? unit : null; + } + } + + public class I_G2M_LoginAddressRequestHandler : RouteRPC + { + protected override async FTask Run(Scene scene, I_G2M_LoginAddressRequest request, I_M2G_LoginAddressResponse response, Action reply) + { + // 现在这里是MAPæœåŠ¡å™¨äº†ã€çŽ©å®¶è¿›å…¥è¿™é‡Œå¦‚æžœæ˜¯é¦–æ¬¡è¿›å…¥ä¼šæœ‰çŽ©å®¶çš„æ‰€æœ‰ä¿¡æ¯ + // ä¸€èˆ¬è¿™ä¸ªä¿¡æ¯æ˜¯æ•°æ®åº“里拿到或者其他æœåŠ¡å™¨ç»™ä¼ é€’è¿‡æ¥äº†ã€è¿™é‡Œä¸»è¦æ¼”示怎么注册Addressã€æ‰€ä»¥è¿™äº›æ­¥éª¤è¿™é‡Œå°±ä¸åšäº† + // 这里我就模拟一个å‡çš„Unitæ•°æ®ä½¿ç”¨ + // 1ã€é¦–先创建一个Unit + var unit = AddressManage.Add(scene, request.AddressId, request.GateRouteId); + // 2ã€æŒ‚在AddressableMessageComponent组件ã€è®©è¿™ä¸ªUnit支æŒAddressã€å¹¶ä¸”会自动注册到网格中 + await unit.AddComponent().Register(); + await FTask.CompletedTask; + } + } +} +#endif diff --git a/Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs.meta b/Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs.meta new file mode 100644 index 00000000..4c1335ee --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Handler/I_G2M_LoginAddressRequestHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d7564e2d1386c64c98261758acd0109 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Helper.meta b/Assets/GameScripts/DotNet/Logic/Helper.meta new file mode 100644 index 00000000..fb937b16 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Helper.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9b231e7229fc3744cbe27ad320c6fb4f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs b/Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs new file mode 100644 index 00000000..6cac8384 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs @@ -0,0 +1,27 @@ +#if TENGINE_NET +using TEngine.Core; +#pragma warning disable CS8603 + +namespace TEngine; + +/// +/// 整个框架使用的程åºé›†ã€æœ‰å‡ ä¸ªç¨‹åºé›†å°±å®šä¹‰é›†ã€‚这里定义是为了åŽé¢æ–¹é¢ä½¿ç”¨ +/// +public static class AssemblyName +{ + public const int Hotfix = 1; +} + +public static class AssemblySystem +{ + public static void Init() + { + LoadHotfix(); + } + + public static void LoadHotfix() + { + AssemblyManager.Load(AssemblyName.Hotfix, typeof(AssemblySystem).Assembly); + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs.meta b/Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs.meta new file mode 100644 index 00000000..e7d3b7b2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Helper/AssemblySystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a0e634d3c2c44d40b6cbaeeeb56c780 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs b/Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs new file mode 100644 index 00000000..f149060a --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs @@ -0,0 +1,139 @@ +#if TENGINE_NET +using TEngine.Core; +using TEngine.Core.DataBase; +#pragma warning disable CS8603 + +namespace TEngine.Logic; + +public static class ConfigTableSystem +{ + public static void Bind() + { + // 框架需è¦ä¸€äº›çš„é…置文件æ¥å¯åЍæœåŠ¡å™¨å’Œåˆ›å»ºç½‘ç»œæœåŠ¡æ‰€ä»¥éœ€è¦ServerConfig.xlsxå’ŒMachineConfig.xlsxçš„é…ç½® + // 由于é…ç½®è¡¨çš„ä»£ç æ˜¯ç”Ÿæˆåœ¨æ¡†æž¶å¤–é¢çš„ã€æ¡†æž¶æ²¡åŠžæ³•ç›´æŽ¥èŽ·å–到é…置文件 + // 考虑到这两个é…置文件开å‘者å¯èƒ½ä¼šä¿®æ”¹ç»“æž„ã€æ‰€ä»¥æä¾›äº†ä¸€ä¸ªå§”托æ¥è®©å¼€å‘è€…å¼€è‡ªå·±å®šä¹‰å¦‚ä½•èŽ·å–æ¡†æž¶éœ€è¦çš„东西 + // æœ¬æ¥æƒ³æä¾›ä¸€ä¸ªæŽ¥å£è®©çŽ©å®¶æŠŠServerConfigå’ŒMachineConfig添加到框架中ã€ä½†è¿™æ ·å°±ä¸æ”¯æŒçƒ­æ›´ + // æä¾›å§”托方å¼å°±å¯ä»¥æ”¯æŒé…置表热更。因为é…ç½®è¡¨è¯»å–æœ¬å°±æ˜¯æ”¯æŒçƒ­æ›´çš„ + // 虽然这å—ç¨å¾®éº»çƒ¦ç‚¹ã€ä½†å¥½åœ¨é…置一次以åŽåŸºæœ¬ä¸ä¼šæ”¹åЍã€åŽé¢æœ‰æ›´å¥½çš„办法我会把这个给去掉 + ConfigTableManage.ServerConfig = serverId => + { + if (!ServerConfigData.Instance.TryGet(serverId, out var serverConfig)) + { + return null; + } + + return new ServerConfigInfo() + { + Id = serverConfig.Id, + InnerPort = serverConfig.InnerPort, + MachineId = serverConfig.MachineId + }; + }; + ConfigTableManage.MachineConfig = machineId => + { + if (!MachineConfigData.Instance.TryGet(machineId, out var machineConfig)) + { + return null; + } + + return new MachineConfigInfo() + { + Id = machineConfig.Id, + OuterIP = machineConfig.OuterIP, + OuterBindIP = machineConfig.OuterBindIP, + InnerBindIP = machineConfig.InnerBindIP, + ManagementPort = machineConfig.ManagementPort + }; + }; + ConfigTableManage.WorldConfigInfo = worldId => + { + if (!WorldConfigData.Instance.TryGet(worldId, out var worldConfig)) + { + return null; + } + + return new WorldConfigInfo() + { + Id = worldConfig.Id, + WorldName = worldConfig.WorldName, + DbConnection = worldConfig.DbConnection, + DbName = worldConfig.DbName, + DbType = worldConfig.DbType + }; + }; + ConfigTableManage.SceneConfig = sceneId => + { + if (!SceneConfigData.Instance.TryGet(sceneId, out var sceneConfig)) + { + return null; + } + + return new SceneConfigInfo() + { + Id = sceneConfig.Id, + SceneType = sceneConfig.SceneType, + Name = sceneConfig.Name, + NetworkProtocol = sceneConfig.NetworkProtocol, + RouteId = sceneConfig.RouteId, + WorldId = sceneConfig.WorldId, + OuterPort = sceneConfig.OuterPort + }; + }; + ConfigTableManage.AllServerConfig = () => + { + var list = new List(); + + foreach (var serverConfig in ServerConfigData.Instance.List) + { + list.Add(new ServerConfigInfo() + { + Id = serverConfig.Id, + InnerPort = serverConfig.InnerPort, + MachineId = serverConfig.MachineId + }); + } + + return list; + }; + ConfigTableManage.AllMachineConfig = () => + { + var list = new List(); + + foreach (var machineConfig in MachineConfigData.Instance.List) + { + list.Add(new MachineConfigInfo() + { + Id = machineConfig.Id, + OuterIP = machineConfig.OuterIP, + OuterBindIP = machineConfig.OuterBindIP, + InnerBindIP = machineConfig.InnerBindIP, + ManagementPort = machineConfig.ManagementPort + }); + } + + return list; + }; + ConfigTableManage.AllSceneConfig = () => + { + var list = new List(); + + foreach (var sceneConfig in SceneConfigData.Instance.List) + { + list.Add(new SceneConfigInfo() + { + Id = sceneConfig.Id, + EntityId = sceneConfig.EntityId, + SceneType = sceneConfig.SceneType, + Name = sceneConfig.Name, + NetworkProtocol = sceneConfig.NetworkProtocol, + RouteId = sceneConfig.RouteId, + WorldId = sceneConfig.WorldId, + OuterPort = sceneConfig.OuterPort + }); + } + + return list; + }; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs.meta b/Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs.meta new file mode 100644 index 00000000..5c8970af --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/Helper/ConfigTableSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b319159facf1dfa4ab8d203a2c68a55a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Logic/OnCreateScene.cs b/Assets/GameScripts/DotNet/Logic/OnCreateScene.cs new file mode 100644 index 00000000..1e774d9c --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/OnCreateScene.cs @@ -0,0 +1,32 @@ +#if TENGINE_NET +using TEngine.Core.Network; + +namespace TEngine.Logic; + +/// +/// 当Scene创建时需è¦å¹²ä»€ä¹ˆ +/// +public class OnCreateScene : AsyncEventSystem +{ + public override async FTask Handler(TEngine.OnCreateScene self) + { + // æœåŠ¡å™¨æ˜¯ä»¥Scene为å•ä½çš„ã€æ‰€ä»¥Scene下有什么组件都å¯ä»¥è‡ªå·±æ·»åŠ å®šä¹‰ + // OnCreateScene这个事件就是给开å‘者使用的 + // 比如Addressåè®®è¿™é‡Œã€æˆ‘就是åšäº†ä¸€ä¸ªç®¡ç†Address地å€çš„一个组件挂在到Address这个Scene下é¢äº† + // 比如Map下你需è¦ä¸€äº›è‡ªå®šä¹‰ç»„ä»¶ã€ä½ ä¹Ÿå¯ä»¥åœ¨è¿™é‡Œæ“作 + var sceneConfigInfo = self.SceneInfo; + + switch (sceneConfigInfo.SceneType) + { + case "Addressable": + { + sceneConfigInfo.Scene.AddComponent(); + break; + } + } + Log.Info($"scene create: {self.SceneInfo.SceneType} {self.SceneInfo.Name} SceneId:{self.SceneInfo.Id} ServerId:{self.SceneInfo.RouteId} WorldId:{self.SceneInfo.WorldId}"); + + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Logic/OnCreateScene.cs.meta b/Assets/GameScripts/DotNet/Logic/OnCreateScene.cs.meta new file mode 100644 index 00000000..a0125aea --- /dev/null +++ b/Assets/GameScripts/DotNet/Logic/OnCreateScene.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf9edee1a93725b4f818c55132e2d70f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/csc.rsp b/Assets/GameScripts/DotNet/csc.rsp new file mode 100644 index 00000000..36fdced9 --- /dev/null +++ b/Assets/GameScripts/DotNet/csc.rsp @@ -0,0 +1 @@ +-define:TENGINE_UNITY \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/csc.rsp.meta b/Assets/GameScripts/DotNet/csc.rsp.meta new file mode 100644 index 00000000..dc06f14c --- /dev/null +++ b/Assets/GameScripts/DotNet/csc.rsp.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8444a333021244b3aa1559b112073e61 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/HotFix/GameLogic/GameApp.cs b/Assets/GameScripts/HotFix/GameLogic/GameApp.cs index ab3c9551..0f92999e 100644 --- a/Assets/GameScripts/HotFix/GameLogic/GameApp.cs +++ b/Assets/GameScripts/HotFix/GameLogic/GameApp.cs @@ -1,14 +1,19 @@ +using System.Collections.Generic; +using System.Reflection; using GameBase; using TEngine; public partial class GameApp:Singleton { + private static List _hotfixAssembly; + /// /// 热更域App主入å£ã€‚ /// /// public static void Entrance(object[] objects) { + _hotfixAssembly = (List)objects[0]; Log.Warning("======= çœ‹åˆ°æ­¤æ¡æ—¥å¿—代表你æˆåŠŸè¿è¡Œäº†çƒ­æ›´æ–°ä»£ç  ======="); Log.Warning("======= Entrance GameApp ======="); Instance.Init(); diff --git a/Assets/GameScripts/HotFix/GameLogic/GameApp_RegisterSystem.cs b/Assets/GameScripts/HotFix/GameLogic/GameApp_RegisterSystem.cs index 1c82538e..770a2b01 100644 --- a/Assets/GameScripts/HotFix/GameLogic/GameApp_RegisterSystem.cs +++ b/Assets/GameScripts/HotFix/GameLogic/GameApp_RegisterSystem.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using TEngine; +using TEngine.Core; public partial class GameApp { @@ -25,6 +26,13 @@ public partial class GameApp /// private void RegisterAllSystem() { + if (_hotfixAssembly != null) + { + AssemblyManager.Load(AssemblyName.GameBase, _hotfixAssembly.Find(t=>t.FullName.Contains("GameBase"))); + AssemblyManager.Load(AssemblyName.GameProto, _hotfixAssembly.Find(t=>t.FullName.Contains("GameProto"))); + AssemblyManager.Load(AssemblyName.GameLogic, GetType().Assembly); + } + //带生命周期的å•例系统。 AddLogicSys(BehaviourSingleSystem.Instance); } diff --git a/Assets/GameScripts/HotFix/GameLogic/GameLogic.asmdef b/Assets/GameScripts/HotFix/GameLogic/GameLogic.asmdef index 73fe4ced..b1c0e3aa 100644 --- a/Assets/GameScripts/HotFix/GameLogic/GameLogic.asmdef +++ b/Assets/GameScripts/HotFix/GameLogic/GameLogic.asmdef @@ -7,7 +7,8 @@ "GUID:aa06d4cc755c979489c256c1bcca1dfb", "GUID:641632c4f8079b94f963b5284d859a12", "GUID:6055be8ebefd69e48b49212b09b47b2f", - "GUID:15fc0a57446b3144c949da3e2b9737a9" + "GUID:15fc0a57446b3144c949da3e2b9737a9", + "GUID:ecba4a58c7f7a4842b72ce2c77aecf9b" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs b/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs deleted file mode 100644 index 7f2032d2..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/AsyncETTaskMethodBuilder.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Security; - -namespace ET -{ - public struct ETAsyncTaskMethodBuilder - { - private ETTask tcs; - - // 1. Static Create method. - [DebuggerHidden] - public static ETAsyncTaskMethodBuilder Create() - { - ETAsyncTaskMethodBuilder builder = new ETAsyncTaskMethodBuilder() { tcs = ETTask.Create(true) }; - return builder; - } - - // 2. TaskLike Task property. - [DebuggerHidden] - public ETTask Task => this.tcs; - - // 3. SetException - [DebuggerHidden] - public void SetException(Exception exception) - { - this.tcs.SetException(exception); - } - - // 4. SetResult - [DebuggerHidden] - public void SetResult() - { - this.tcs.SetResult(); - } - - // 5. AwaitOnCompleted - [DebuggerHidden] - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine - { - awaiter.OnCompleted(stateMachine.MoveNext); - } - - // 6. AwaitUnsafeOnCompleted - [DebuggerHidden] - [SecuritySafeCritical] - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine - { - awaiter.OnCompleted(stateMachine.MoveNext); - } - - // 7. Start - [DebuggerHidden] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine - { - stateMachine.MoveNext(); - } - - // 8. SetStateMachine - [DebuggerHidden] - public void SetStateMachine(IAsyncStateMachine stateMachine) - { - } - } - - public struct ETAsyncTaskMethodBuilder - { - private ETTask tcs; - - // 1. Static Create method. - [DebuggerHidden] - public static ETAsyncTaskMethodBuilder Create() - { - ETAsyncTaskMethodBuilder builder = new ETAsyncTaskMethodBuilder() { tcs = ETTask.Create(true) }; - return builder; - } - - // 2. TaskLike Task property. - [DebuggerHidden] - public ETTask Task => this.tcs; - - // 3. SetException - [DebuggerHidden] - public void SetException(Exception exception) - { - this.tcs.SetException(exception); - } - - // 4. SetResult - [DebuggerHidden] - public void SetResult(T ret) - { - this.tcs.SetResult(ret); - } - - // 5. AwaitOnCompleted - [DebuggerHidden] - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine - { - awaiter.OnCompleted(stateMachine.MoveNext); - } - - // 6. AwaitUnsafeOnCompleted - [DebuggerHidden] - [SecuritySafeCritical] - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine - { - awaiter.OnCompleted(stateMachine.MoveNext); - } - - // 7. Start - [DebuggerHidden] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine - { - stateMachine.MoveNext(); - } - - // 8. SetStateMachine - [DebuggerHidden] - public void SetStateMachine(IAsyncStateMachine stateMachine) - { - } - } -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs b/Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs deleted file mode 100644 index 5c2c12a9..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/AsyncETVoidMethodBuilder.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Security; - -namespace ET -{ - internal struct AsyncETVoidMethodBuilder - { - // 1. Static Create method. - [DebuggerHidden] - public static AsyncETVoidMethodBuilder Create() - { - AsyncETVoidMethodBuilder builder = new AsyncETVoidMethodBuilder(); - return builder; - } - - // 2. TaskLike Task property(void) - [DebuggerHidden] - public ETVoid Task => default; - - // 3. SetException - [DebuggerHidden] - public void SetException(Exception e) - { - ETTask.ExceptionHandler.Invoke(e); - } - - // 4. SetResult - [DebuggerHidden] - public void SetResult() - { - // do nothing - } - - // 5. AwaitOnCompleted - [DebuggerHidden] - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine - { - awaiter.OnCompleted(stateMachine.MoveNext); - } - - // 6. AwaitUnsafeOnCompleted - [DebuggerHidden] - [SecuritySafeCritical] - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine - { - awaiter.UnsafeOnCompleted(stateMachine.MoveNext); - } - - // 7. Start - [DebuggerHidden] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine - { - stateMachine.MoveNext(); - } - - // 8. SetStateMachine - [DebuggerHidden] - public void SetStateMachine(IAsyncStateMachine stateMachine) - { - } - } -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs b/Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs deleted file mode 100644 index fc55e3d2..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETCancellationToken.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace ET -{ - public class ETCancellationToken - { - private HashSet actions = new HashSet(); - - public void Add(Action callback) - { - // 如果action是null,ç»å¯¹ä¸èƒ½æ·»åŠ ,è¦æŠ›å¼‚å¸¸ï¼Œè¯´æ˜Žæœ‰åç¨‹æ³„æ¼ - this.actions.Add(callback); - } - - public void Remove(Action callback) - { - this.actions?.Remove(callback); - } - - public bool IsDispose() - { - return this.actions == null; - } - - public void Cancel() - { - if (this.actions == null) - { - return; - } - - this.Invoke(); - } - - private void Invoke() - { - HashSet runActions = this.actions; - this.actions = null; - try - { - foreach (Action action in runActions) - { - action.Invoke(); - } - } - catch (Exception e) - { - ETTask.ExceptionHandler.Invoke(e); - } - } - } -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETTask.cs b/Assets/GameScripts/ThirdParty/ETTask/ETTask.cs deleted file mode 100644 index a40acf0d..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETTask.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; - -namespace ET -{ - [AsyncMethodBuilder(typeof (ETAsyncTaskMethodBuilder))] - public class ETTask: ICriticalNotifyCompletion - { - public static Action ExceptionHandler; - - public static ETTaskCompleted CompletedTask - { - get - { - return new ETTaskCompleted(); - } - } - - private static readonly ConcurrentQueue queue = new(); - - /// - /// 请ä¸è¦éšä¾¿ä½¿ç”¨ETTask的对象池,除éžä½ å®Œå…¨æžæ‡‚了ETTask!!! - /// å‡å¦‚å¼€å¯äº†æ± ,await之åŽä¸èƒ½å†æ“作ETTask,å¦åˆ™å¯èƒ½æ“ä½œåˆ°å†æ¬¡ä»Žæ± ä¸­åˆ†é…出æ¥çš„ETTask,产生ç¾é𾿀§çš„åŽæžœ - /// SetResult的时候请现将tcs置空,é¿å…多次对åŒä¸€ä¸ªETTask SetResult - /// - public static ETTask Create(bool fromPool = false) - { - if (!fromPool) - { - return new ETTask(); - } - if (!queue.TryDequeue(out ETTask task)) - { - return new ETTask() {fromPool = true}; - } - return task; - } - - private void Recycle() - { - if (!this.fromPool) - { - return; - } - - this.state = AwaiterStatus.Pending; - this.callback = null; - // 太多了 - if (queue.Count > 1000) - { - return; - } - queue.Enqueue(this); - } - - private bool fromPool; - private AwaiterStatus state; - private object callback; // Action or ExceptionDispatchInfo - - private ETTask() - { - } - - [DebuggerHidden] - private async ETVoid InnerCoroutine() - { - await this; - } - - [DebuggerHidden] - public void Coroutine() - { - InnerCoroutine().Coroutine(); - } - - [DebuggerHidden] - public ETTask GetAwaiter() - { - return this; - } - - - public bool IsCompleted - { - [DebuggerHidden] - get - { - return this.state != AwaiterStatus.Pending; - } - } - - [DebuggerHidden] - public void UnsafeOnCompleted(Action action) - { - if (this.state != AwaiterStatus.Pending) - { - action?.Invoke(); - return; - } - - this.callback = action; - } - - [DebuggerHidden] - public void OnCompleted(Action action) - { - this.UnsafeOnCompleted(action); - } - - [DebuggerHidden] - public void GetResult() - { - switch (this.state) - { - case AwaiterStatus.Succeeded: - this.Recycle(); - break; - case AwaiterStatus.Faulted: - ExceptionDispatchInfo c = this.callback as ExceptionDispatchInfo; - this.callback = null; - this.Recycle(); - c?.Throw(); - break; - default: - throw new NotSupportedException("ETTask does not allow call GetResult directly when task not completed. Please use 'await'."); - } - } - - [DebuggerHidden] - public void SetResult() - { - if (this.state != AwaiterStatus.Pending) - { - throw new InvalidOperationException("TaskT_TransitionToFinal_AlreadyCompleted"); - } - - this.state = AwaiterStatus.Succeeded; - - Action c = this.callback as Action; - this.callback = null; - c?.Invoke(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [DebuggerHidden] - public void SetException(Exception e) - { - if (this.state != AwaiterStatus.Pending) - { - throw new InvalidOperationException("TaskT_TransitionToFinal_AlreadyCompleted"); - } - - this.state = AwaiterStatus.Faulted; - - Action c = this.callback as Action; - this.callback = ExceptionDispatchInfo.Capture(e); - c?.Invoke(); - } - } - - [AsyncMethodBuilder(typeof (ETAsyncTaskMethodBuilder<>))] - public class ETTask: ICriticalNotifyCompletion - { - private static readonly ConcurrentQueue> queue = new(); - - /// - /// 请ä¸è¦éšä¾¿ä½¿ç”¨ETTask的对象池,除éžä½ å®Œå…¨æžæ‡‚了ETTask!!! - /// å‡å¦‚å¼€å¯äº†æ± ,await之åŽä¸èƒ½å†æ“作ETTask,å¦åˆ™å¯èƒ½æ“ä½œåˆ°å†æ¬¡ä»Žæ± ä¸­åˆ†é…出æ¥çš„ETTask,产生ç¾é𾿀§çš„åŽæžœ - /// SetResult的时候请现将tcs置空,é¿å…多次对åŒä¸€ä¸ªETTask SetResult - /// - public static ETTask Create(bool fromPool = false) - { - if (!fromPool) - { - return new ETTask(); - } - - if (!queue.TryDequeue(out ETTask task)) - { - return new ETTask() {fromPool = true}; - } - return task; - } - - private void Recycle() - { - if (!this.fromPool) - { - return; - } - this.callback = null; - this.value = default; - this.state = AwaiterStatus.Pending; - // 太多了 - if (queue.Count > 1000) - { - return; - } - queue.Enqueue(this); - } - - private bool fromPool; - private AwaiterStatus state; - private T value; - private object callback; // Action or ExceptionDispatchInfo - - private ETTask() - { - } - - [DebuggerHidden] - private async ETVoid InnerCoroutine() - { - await this; - } - - [DebuggerHidden] - public void Coroutine() - { - InnerCoroutine().Coroutine(); - } - - [DebuggerHidden] - public ETTask GetAwaiter() - { - return this; - } - - [DebuggerHidden] - public T GetResult() - { - switch (this.state) - { - case AwaiterStatus.Succeeded: - T v = this.value; - this.Recycle(); - return v; - case AwaiterStatus.Faulted: - ExceptionDispatchInfo c = this.callback as ExceptionDispatchInfo; - this.callback = null; - this.Recycle(); - c?.Throw(); - return default; - default: - throw new NotSupportedException("ETask does not allow call GetResult directly when task not completed. Please use 'await'."); - } - } - - - public bool IsCompleted - { - [DebuggerHidden] - get - { - return state != AwaiterStatus.Pending; - } - } - - [DebuggerHidden] - public void UnsafeOnCompleted(Action action) - { - if (this.state != AwaiterStatus.Pending) - { - action?.Invoke(); - return; - } - - this.callback = action; - } - - [DebuggerHidden] - public void OnCompleted(Action action) - { - this.UnsafeOnCompleted(action); - } - - [DebuggerHidden] - public void SetResult(T result) - { - if (this.state != AwaiterStatus.Pending) - { - throw new InvalidOperationException("TaskT_TransitionToFinal_AlreadyCompleted"); - } - - this.state = AwaiterStatus.Succeeded; - - this.value = result; - - Action c = this.callback as Action; - this.callback = null; - c?.Invoke(); - } - - [DebuggerHidden] - public void SetException(Exception e) - { - if (this.state != AwaiterStatus.Pending) - { - throw new InvalidOperationException("TaskT_TransitionToFinal_AlreadyCompleted"); - } - - this.state = AwaiterStatus.Faulted; - - Action c = this.callback as Action; - this.callback = ExceptionDispatchInfo.Capture(e); - c?.Invoke(); - } - } -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETTask.cs.meta b/Assets/GameScripts/ThirdParty/ETTask/ETTask.cs.meta deleted file mode 100644 index 43915411..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETTask.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 624636d415540b94b934cf1931b5c281 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs.meta b/Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs.meta deleted file mode 100644 index 17f3a301..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETTaskCompleted.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c27123994d9644acf9b27e884c5fdf1e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs b/Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs deleted file mode 100644 index f621e01b..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ET -{ - public static class ETTaskHelper - { - public static bool IsCancel(this ETCancellationToken self) - { - if (self == null) - { - return false; - } - return self.IsDispose(); - } - - private class CoroutineBlocker - { - private int count; - - private ETTask tcs; - - public CoroutineBlocker(int count) - { - this.count = count; - } - - public async ETTask RunSubCoroutineAsync(ETTask task) - { - try - { - await task; - } - finally - { - --this.count; - - if (this.count <= 0 && this.tcs != null) - { - ETTask t = this.tcs; - this.tcs = null; - t.SetResult(); - } - } - } - - public async ETTask WaitAsync() - { - if (this.count <= 0) - { - return; - } - this.tcs = ETTask.Create(true); - await tcs; - } - } - - public static async ETTask WaitAny(List tasks) - { - if (tasks.Count == 0) - { - return; - } - - CoroutineBlocker coroutineBlocker = new CoroutineBlocker(1); - - foreach (ETTask task in tasks) - { - coroutineBlocker.RunSubCoroutineAsync(task).Coroutine(); - } - - await coroutineBlocker.WaitAsync(); - } - - public static async ETTask WaitAny(ETTask[] tasks) - { - if (tasks.Length == 0) - { - return; - } - - CoroutineBlocker coroutineBlocker = new CoroutineBlocker(1); - - foreach (ETTask task in tasks) - { - coroutineBlocker.RunSubCoroutineAsync(task).Coroutine(); - } - - await coroutineBlocker.WaitAsync(); - } - - public static async ETTask WaitAll(ETTask[] tasks) - { - if (tasks.Length == 0) - { - return; - } - - CoroutineBlocker coroutineBlocker = new CoroutineBlocker(tasks.Length); - - foreach (ETTask task in tasks) - { - coroutineBlocker.RunSubCoroutineAsync(task).Coroutine(); - } - - await coroutineBlocker.WaitAsync(); - } - - public static async ETTask WaitAll(List tasks) - { - if (tasks.Count == 0) - { - return; - } - - CoroutineBlocker coroutineBlocker = new CoroutineBlocker(tasks.Count); - - foreach (ETTask task in tasks) - { - coroutineBlocker.RunSubCoroutineAsync(task).Coroutine(); - } - - await coroutineBlocker.WaitAsync(); - } - } -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs.meta b/Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs.meta deleted file mode 100644 index 18083752..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETTaskHelper.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 918113fec35224afa958d442f09ba720 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs.meta b/Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs.meta deleted file mode 100644 index e3aaa410..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/ETVoid.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c1427531a30db254e8587be525470b6c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs b/Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs deleted file mode 100644 index 317a5fcb..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ET -{ - public enum AwaiterStatus: byte - { - /// The operation has not yet completed. - Pending = 0, - - /// The operation completed successfully. - Succeeded = 1, - - /// The operation completed with an error. - Faulted = 2, - } -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs.meta b/Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs.meta deleted file mode 100644 index b460d502..00000000 --- a/Assets/GameScripts/ThirdParty/ETTask/IAwaiter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d48c67734d0468148be856dbd3051d19 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins.meta new file mode 100644 index 00000000..c9ecbff7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6ec9cf984eeccbb4ba7470e5beeac5a1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta new file mode 100644 index 00000000..e8af9edb --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 479fdc912cd3148078b6596aa438b98d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta new file mode 100644 index 00000000..100e24cf --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8626627ffb7484407ad050b63b7a6676 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so new file mode 100644 index 0000000000000000000000000000000000000000..966dd3c08b1c2badd3a2d9824fa6d544a6dc4c03 GIT binary patch literal 32096 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 PxABL#hH1SQrT>2bq&_k3 literal 0 HcmV?d00001 diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta new file mode 100644 index 00000000..e9cbfd0e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 95cef76c7e8fc924594a2c535022c037 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARM64 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta new file mode 100644 index 00000000..aee16a23 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1e40c3d1ddee94950b16c1495dfcd31b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so new file mode 100644 index 0000000000000000000000000000000000000000..b12f7e3a8e93f9998abafc4895b64e11becddaa0 GIT binary patch literal 68152 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 literal 0 HcmV?d00001 diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta new file mode 100644 index 00000000..5d585f6e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 618bbca818d51974ea5aaf2c99cbeb25 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: x86 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta new file mode 100644 index 00000000..4ae2d49c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b5ee1b6d2f25b4a889ae7286f23a7f93 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a b/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a new file mode 100644 index 0000000000000000000000000000000000000000..abd42fd1ef70893d241c77dd72854b28d2d7c745 GIT binary patch literal 43328 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 literal 0 HcmV?d00001 diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta new file mode 100644 index 00000000..a8c588e4 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 72dcdf060ecf34d279acdee20e77b200 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta new file mode 100644 index 00000000..2504b328 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd47571478aff4eb6a9fa70ca39031cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll new file mode 100644 index 0000000000000000000000000000000000000000..ce18e81f1515abc702361ee7e25fec5c5d3281f3 GIT binary patch literal 146432 zcmeFa3wTsTwm;gPPA8p^u$xCP$g7b?qXCTsHKBv_PC8__>4qSns3_oFuwBi|^qw|t+=FE7`%p9H3fq=@S6Uc*SK$(GY&WJkdj;&)94FQFHf2;QH?(p!8 z_uTva|M%YS=KIq1u3EKf)v8siR@DyPxy53$SS-o-*L91f2`>G4`R{-K8g7lbS=8~p@&w;uJ0{ztqMKly6+*EZ{}>*gDVy< zxi&L1-ECHV|BY2+uDyL!Aof3{Xn1fu!c&Sy2Xncb9sCvCCtcaWJnjw;y0|+!cok0{ z8Jr0B+S^A49)bI|Yk2TF?v66u_X0+E3=iLW-=byI)`dN*D79D?KbC5lIPmA+i>38g zu5b-V8aUc=4?6mZ5$no_>*W3##2Wr2izS6eC0xrEL=sZ*XR|CsR-;hNxj1aZ)C^9t z>_%?i&?HL^6<(QSIYAA1lPn{LQiSEO`W^ z3k_$$9e6$f$k%-BN+TohBl>C?hJ2S_l+U~1R-ZUWv{=4GvSlICDNEkP`L116wQ3QP zIPQQ$o^R5n@-11u0vQRHmKvlx;7-0MUom3;Klvwx?Sm#HSz7m-f0CMcHP9}pS=Yd6 z-JjoF|5P7(E~iU{wKU;{XPV=gQASz2(h^|Deat^cY|?doqkK1O5pyGU=T#QV)G;$a z$wJv)(Q|lhX(0U6i>S$$yVuXs{hqz@XkmZGvyU|+HS~eV zN-9b{ky0Vh;q!EeLUF{_(#l%;x4wVBq|SFcBvp1hZYGDTkl`)L!1hs|p?X(- zv&81R^CTv_3nXQ?YeB14f-)CJ8#%&C168FeTexG9!pgHo#MXEOp%^({jz=^Z5zS@< zYn9Y>^YOn}U6&!LuXXVF*Sff~E~mXDk7C+OCNGAgt_!r6RFF?psoZpUC;cBeYp`&d6Be8p?;PMB@rJ;W~EQ{xpRE#Z`=*kG8(9XD2N}rVb(OW3O{>M028u8!-_r8@>Wj*xp=;td+=1I1t$$ zD(G_;aUbROav$aPN$8MNDU7K@+ti=-(%cp??9KL+BySkzN7vXs?Xf|GLgYhNfe?u) zG)O)M@Y&=Jw!9-*(jy4D8_JU46f~6CgzZM-4P~jk`G&GIa-9uj>EvcNlno$&9Svof zs|67$-i8$pZ^N1lU&9J# zdl?P6MN-L z4Kq{v4P-R6!Jr^?JV~f~2bG-P@eyXny9ZK=(OYz!clcX7njp)R*bw~l{{A=p{PzBd zk$+@IKF!9j2nW6OHLS5wN9R~Tx&uJE0!VjAzaINol{dfFS6Ee`R!Kf#dQ|~&33uTu zJ?MLAAQF5O>*KMkWN2=QQ}nb6>Q;`LfIBB(Flaw>20Sf79pPPK=@Ki(3MD!umL#!T z;I&HXBuQO`2$M}CJ*>P)xm9E_Bb|ErGQDIWvq|drUq+f*>})R{I*Zy;ZyD)RA8`Q$ zwb-qf3}q$RYB3nuBYCVOPc5E|$_tPv=C*lCoI=BLOgyF#Xw7q`>;;suo*>|ki*)i7ojY8Td75z zJ~^D>?EWEv=Gl-}{k^bgRi_u}CG%Lxa;$qUHhr?lf)-Dt-RE{uDWRkzJk+Y*Hn}*Q zF(tZG=|i^{s=L89bYE^1Di1!V6;y!K$RBJc1WF($37EtKlZ5`Tt0R6NJ886oNzGPHe-V5 z86Z5d5&0yAuUnSTaIxtSg~M5vJFRdsuZE+|!}8p#(E4YUZ$e(;Ar^U}j~xhv?PHK* zCq)p?Zb=7p1}xxev~+=!y1?;W^`FXF0mh8wu9Y42v;F$_X(TXv*bFC&a0Xx{mv}>i z5b4ftm}wKXO&{7&mJA2fY!|jq9|Evlc*o+BgS{92m%p3}6D4}dv!_3Qw)3fOlAjM=sT$94Lc_ns8 zdvY8)Iemg;>tiMBnMqyKi&mw!mt+vlN$f_GxWFz5b1}BTc?OYL{ZG%*B7s?#`?|?K zf8%cE7=6{<0k>Y?@V!e6(cSa-5IqJ*`$JJ;h{nc;=rSKWgdtjw9PuFf8(Z=Odicj z9OB`B_&lGtm-vVvj7&bE_;a6Hhou&D2lb}(`4*O^&4Y0lB7C@{L z+1FD25v*mYXhk`bEo}Z9d=l8fR5mp}X zfz1%-qa1cDI9p7Ay3eLTb||9wG=JDXl&V1-g&qC;18qGNm5Is?Uq;+F*k?OIX@kVb z-%*tNoj!tEMAK#@(vy6)4mehd<=&W23X91S=I*nAiNwyyrJlpW=3ir{SX$pHG`s*$ zxb&qWoACG+cmv^As3Sgh#?JZU+3sA?jXlpaiD6Yx z%dikwB{mCQutEFpGm;>SLj1YIk=RL0oCL;Jz7Ug4d<~6Q{GM~6S4ozl8AQznSxV5; zCa09LdG2VkkKHxYr{3lC)||ansF(RnD-V?B9uOKfa~Nk&D6Kyd8X__X!sQd5BL>Sl z)RLTNIiy^X(GE_`-7EMbtUMSrB(<^_(59exH*Sz)3}8IWZ0w^KWCybf{`XzR`6 zX!ElaKOsj_n%z;XE$x`C1r`Qz(HU}LktraU**TDTUKG-BZadbJndfNNvC~EMft!EX zqwBPDEQGR*l?T8{cFwU{u-uBd2QbZYeC!}7#6?*fLOx)BmQddY9%8H*PWj3> zI~s`sKFlu{YI+tm?R1(AF_Z=fB}JZ+Y(Z^7Va7Ktdb3A)3weacUcv7lsiU6u)hRaJ z)3@pj$fhbC+ulP`QYfJTJJzPi(Cm2+*in@?$tM&yNR-Ja@sB7 zq4ktO#BL%Z3A+%hnsJR}Mw+gw$VCr1}~GZgeA@ zfvkM@qE2oA0veKa-aSuMw$K0p-NKf;C!@|^q@m752P7pTCF=B0ove@cCu*6KL_cB| zYFRdfI72Yn2*GHDOI_pksY`Rz6%(-34wW!PXbMZ=nce`~qjf_Sour&kCtUTKj z5!7B}@WIpYDdl9WnHPA4IJATbd}VBZ#%8%iPnS^lOC(SQ&|PdylbGLuT%q?Qb(*Bk zwclKB!FCV9S)mq6wyEOv+P#LrWyl5B{GJj@#j{fHO?O{(rEDHF3VaL@o9B1ER@UEM3O6l-r! z1eI3uMcFQEYPzT zewq!h(U_X>(AdTpR)B3b7;ElPwg-y~Ekx@pC?^)K?jM}ibu>3esJ{W_jpl0Paj~Z4 z;iY^ve}Y<~R;eZUEzzg@w-x;%pXl;co^{UZD@4zAqQR48i?^A<5&t2}SA!4}PS`to9Qp>D*`~}!T`e}O`4&VBzSB^Y5;+G zP$X4DaA<-grAH4pog-&2>E?7ZMQmyWTY+GE6*LKJQ`FiXg!20Kbd5f~xkR`UMxJY8%iyU!@^+Akgy&f8Tv4*g}97~%ircjF)s23&u z#QDQ(=uf%oH2-ahoi@AiM=Bc5N;P22As822Pf`EM84xzIo-VnVN!jT2K<1s$Prau{ z3TG}}73Y_>c@vcCY>Nz521eQv`V9I+SqVcF zU>}yINU? zghjXd)N#yle}~AXXXk&49cqfm9-0t!>&@WF!lu8V4L@rW#`rKRmnGo$dv=FDL-CdM zp9&3sKqgQ3hG7zBVl|&@m|cl>N*3pTN(Aqv4Ih}j1ZkoGv!#KcB*{V;Lja?lfkAf8 z&&~zd=RvGi!?7|=kixfn{p^4?4Tm5UWJ18E*;Y(2XvD$RWXt2@tZ;;n&2)q7{=OV^ z;y{(e3roMzbw3kI^L1b0>+TxFuE(khNt5d#4oM8F)9t1NB_@%;IW3@W>?d%(eibb{ zi|c#kw4mopp?(gr4{#T+2mO~p>9o}biTwQ*J`km7Tr0xz3>8QdS~FB2^4(j^^=^*2 z-UTkX-UZYt0rim!R=a?Dg}MA)xXc-qhkn7zi-;NK6_ziq8UzX;+!g}etCDGrl8{uu z_VDrPKQzXG5m)RV8=Ds1*~T#fpU|Vlrt6Gup7rx?j;7v%a_{P{xE9?ffib_A53Lm5 z@gkbXP)VNCa&bR-e~=;N(_-FF%HNLzDM`IvQXlSz^s*~sHH6xw@N0xfvqCW@e@N`o z^{u~DeW%99D&X}Ie!gpc6E0QXugcHHE{k^?^eT<#p@L|-zh2$)( zGQXli@za#6d2Un8G%`GPUIquxV1Jk*am1olL29a6tiIAk^{TJ%>eW{|cq0 zI>4r&pV}=bf1-%o6QbuEpAh&+wC$D_bpTD(=K#Wag7RSW!w~#K1H`5eP<{7rsSWlR z)#)d7jamw&V-W7$v1m!nk<|aB45esLYr&aAuwIu(ickdk*`LvZ*T3kmlI=i%wQ9dW zYQXk2rHQ%Uw3lY17<6p=)gba7^fR1XOb36V&030|-Da|yKb^H&g6fSyb#*_gp9QT^ zy)Haq1%zyd!*hD$0C5-mI0X7MdQJY3u&CSGPkruVJyUBCD}=u?(ui;S!Yi%O6yb?I zh!Vrob#U8-!Wge5KL6N54$oQOXbxHyW_%V{^no8@MZZ18M^_MDjAKEP@ule2j)uQb!2B7D>I!0qIEJYhpI{tfWHoqwkTzkI`tV zC?u&iGgVTv%frb<`t)UJL{fjs$3pP62UuUJ=O4o63h<=-<|w#MiXoppUdm}JXF6!> z9UO0PS&l@%98>fIG0_qVaS(I_5;h1f#>>c6XH7m&g+x^7q?Ogh{sKZ)&P)&+>q=M+BqrYOv4o;NM1+Qf%gzyFgEcTp+{Eaw-~?x1SFEtS_Fn@6sD;(d_Rr%0}ehmG^+PIXowl-@3qAp#`7 zIXI1GExD1YbO`-u=G2~Jy1qUI65UnN6p@nmma&npLrgghgOCC&)yo+Hlf+ zSjz-VTq}YB%AuOPvrAdF#Qq{G?}MLoioKTP06Q#08{>5Z7gZ#mIu@`U#?%gsJM3T7 zdJ7xsl&~{l5|CTs==E7`f;<2RELNLxqKDLS`Dmu|u5Qf8nDAa1r+>+lIlRYe2|1-m z#94VNqU#}Rs}$*UcK-kx3Y?z<*7_rIq4Kd+&Y_=S${DMio`6?-Bo*5#yH*ZI*w)PZ z;AdUrb_-}R2ubRh2oGih`w97&$jQZpI2S}@h&TD2+Q+D*4$27P!P>)6nl&D4z=q4a z-KtEz18mzO)YD9ExBC&GvD98irn+W%rnV0PyLQfwpHYYcpeE&ZRLHd_8@Yz*60fc5G4e}mi-dLhHaiJpn9{_3fMYG z4lHiO3m|OvurnAU=mdqrtOCHWF*U!ro7SOlu@1f7no>;cykgE3FwuPOJl^@Cde+Hl zP=g%<-^Ia_Q1^S30M-~(=?Ar~*nuXJ{`SoGHTkABWY=B;aRgX9dy{XtP(i|wqx(sK z&gVMuTI^r)JN2xWaAXF_M9=yuJbKoPJlL@_E!jfzisX>-Kc+Zn5+Rs;Qn2GP;7rfj zf;jX(m7hi&xEj*N^i!acP7HyBt!ja!+9zVjq%irFW~{aLH;@6{A4but{qbH3VWPp$ zkkW3y7ctaqyZtWsXe`_9OH5xAsKp{Z&`e^@-BhwY*-En@5zjLBM5*FHvMUw|(KfRw z7KwrxUO=eR@F}|sfJovqJE&p}lxaaEIHB3i9J8DM?%~~Z0MI*{`K&)&Bff)VF&48hzUcpV2qf^u@rXzSSZQt9qWK4z(h9C&)ny z8W88eNM^p=W|E{VhDw=K7FdyyAwWA# z=r2GX6uFQBIWTIWYW1ui@apD6djnFVZ7#c#HS&b0rQ8G?1SdBx>*JMrU4|^ujw6pMxGq z^0bCzNm-JOUInxDtZO(>r4SQJoqE<#gmCb8J^3w$KOA%!VXY8pr7jf1s&m36<-?y} z>Xh;$(hkz~ke#VC4uSkcQAbPyF_Bid~*~*Jj87zEL0X`PKXN>w{b&UhibwEtE~&3;cqOtENPmlF7;w@368fgCp2us4rWc<6{d@+H z@@$(2Vs+(vXfa8z0VVMP%?}9Cb%Odv3K#OQd@Zn? zUn#7xtKc(|(xIOeHrme;|4{8I$VWkP5ftFG*^eX5$}tWQb^sNSBk;rN0HQ&pDF~^e zve3Sx4MJ75v?%ivcp!NEJ2|$!wS{C;99qzX11e+_vqN(Nxh=wWai|oYl`Yx2;7yUj z88v}$#$NCsx8HME&Stk{1U!*d>985GMFO5?ScxbtInj6WkKo9bQ_4`~5x3=0qM3Bi zWeVIRPNvzd2EH4QYkvf4^N#>81M1!mrAm)yrBF9HDvdlfQ@;lDvos!;lk1LzGPGX; zMUW+4%P>jIz&YlV9lD`bWxO5bF|UZ`=1Sq;xV>Mai?yFL11apd$-Y0nfwS*qw(r2z zI{<)1+l{FKxWmC7v8f9KrCo>7rl6ihYJG|QL_;Oy~A!$ zR&^UQmFA~04zaQ$jIu*7C_DdK%C>-jsqF;YaOOAXcM31~SOf&fq=bBodeUBU}u?dN4n9tx?+?$QB=QBN{E4a7UJ(ai`|NJ{3u_p#E9 z@0q3xQ=6gIkaMOk#Aa3=J9Q;Fm)l=H{SD+v*BZC9gVtt3uO$3DQZ>|vGryCNu9Xg} zPqjY>Z!{HgTi|ITN!k~dzV`I2zmi}@>pn&jTXL*zVdwj4Rq3SzaGY(w8%5gfHvtf; z-KSMCvpo0!ayZtykMB}Rc&+v)8tuDDh>2S5%n-RnaBViYOO;8|r`Nkddd5(p! z<3mmE6GbONnM&sKNcF0to`FLnn?oA`(n%@1om?KsZ!WagK*Ybm+!~yp{u2AU+ixSS zJ^KPjSSNpR4=;LNy2K)ZaMrLEsDqV0L3lg^IH|;6OrKcBHg6(3`k-yEsK48~CBTwk zqvjK~-;x1SiCYH-R15YAl|EI1p=ZUwR-_{h8MfV$j0UfA?LxVpc#&*HLJRu3bOCEKzlv)BGN?6#AH z`o);_d(N#$+WoT)E0!mnpbWsg@Yrt92KM}Z<=ruUrQ7At?eym!4%#B|3_nK(vpV7N zo6XTmj2G%m$N#M^{M_lq&-WJYIlD0p|Fc~9xxWMbzJ?PH#@feIgycjhoXTQA#Pzj&h)Jaq}Nbofi!Xu+G>Qz zL8#dXac8;p#GZ2-EqnUbv{r5b1}?_eN5I!dgs+bXUmtxJe0^l%>mw6iADQ_2=zor{ z#h1j_qaR(2FN$*+7>VPH;x5D&#hr(*M?dP1FA62_MWO!q68^J~ql~yig5HP}v29xo z%1-_Vr|d?o{g^LFG+&k{^&mqHC`Nef3H%zAEr7C}{VAI=tbUlEr}3F4thm()&z&y( zd@rvOGYIpi`B8Mo<2$6%(%SMPCqBkQ%4nkcPm%6ad4+JbH9GXX@e!^Tox;^=yeh#N7<59a?It83ex-1=e<3AQP=eR5cOtR^ zkzR_-qevHG@(|;q7^mRO7!>+Xs?o2U*eK*gjCPad%MbwJ0;nAneSwB05+qW{sVasa8N+`2?EOclTM3J z_b-Mw!zy2fv81 zU4@%edqX!-7KoS`$R8R_hJfRvmxaqPbVo(COv1I2eHp*A25q}Vy-ROrLp*1MO~h7s zzXRd%`VKaBl_o;Beb2sUVAh(xOH4sAKtv9?OssC^pS z)bTy$I~e@<9=Os7q-uj8L!G||WdTGDXy+Fl&&pBHQGV^#FLD@ z`^-cX>G78~%z+#XBZJyPP6e}-wb{ubXzpDTUH0z}hgJF-(5lD}3U892me7c>%f{y0G z$^XF2xmbP!zYw+^@$ayje*xj8y^vX)!ed{c{Yt;^P^3~=*lcp)HO}q_C^}JpuRW-d z0{LGtH3K$qmk4_NZciZBoZPAM)PN^*^f5@9k1s?~a2R3$CS!$nEB;?Lcta5e1>a4R3 zv-DO9f2q#;279JirzisMNp;rOJIFhEeEbP&XtJ524Q3V<%g&M`YO9N7#kT-8T6^f+=IT7WzPK* zHY%reIiDunKCM?s7NSt%zAJGP5A|M5}kzxCP@S2X1h0Z>059VELdKz;E22*?@ z+j$!rZC|NxCV!9V#g(b=9mU2kM5hLHoTz8zUkPrRc>uAjPrH*Qud-K%u>^X%lo*3u zVm;BjS>~^iL)(OO#LeKgeBoPawP&DOVVO@N%{ZH-bD1vfE+ZSZ$gQ~41qmC61An7> zGFj##ELZ;2~pjPYfowjx(_6ozgE zW#5O2sIm?{>m4`3nYD<;sJ|0aUB$*Kt}E=f7Q5K#KzQn@S7>{9P!F>}Pd) z_4P@XEijuF>Y@e-2=#x5CkP|iy2J3W5Eic=pIS)Sh?WNUJ%1DGpF>LYfgpSRRP`nr z5I-!|n@EDxvuXjIF`A*P$mXtloe$OY}OF1>PBK7M2sTe+(=i@R^8!$d^7KA zG(GtEtK3IhaWd7wIr}`?lrN+S(Qk=M%rkJJZ*D*@J7Inmg$I531dhf)DpbFK*7$6K zx_Te=0u|9APZ`?B95ToAII1~-Vo-=cf)3kv7&YyrnncuuI#5?etnj9O6%FGR^{eL@ zst4pm_0$^mY@&Kj)4|yJ^-Sqk&#o)DEVt)Zp3sDlvg>csGR4XPH)mlQ;{RgUp zWhO=qkU8|Me*lgsG*%l>Pd`P~#e719#23ySLlvV1`#^X|e-X~gpe8V9E!qqM6(?%E z?g2j5re{5AR-YQL9>ap8E>eaOGrH)nAAg2SKs*}q;~Y2c^O5e3fq_J{;xnL_F6{w0 zAOku?!MZ+JY$EJw<0+OiRbKaZsqYJzETg^==a(dzB+>o_t^5&1`n5vq5#A$u;) zv7i~I1%}ywdI;ldm z$}U`};C=Lh{IRH=`U=>A?m3U_#c;@cR1D9+Y@0X6YI(yBu0iL6-c!eXuy!2LDUIXQ zjvqbiTI7H(dIW%22Oz-yfH<$l%(f2EVyPU!p#@@5E$O$tlHOA3E1X;r>XpL910G1%N5Bd@I^0FAiX$ySJ{uMf=T7~WQ9(XEM5e6=x zUygz&_(_>ie=9Nqt~V1Xkj)fY!;vy*QXDyVnkl2H8T3+bt}HE_Tm}$HjSmoo$3Er5 z@|C2wQPP@FFK%`F)|@je$tsn;`SLh_c;-1Qln*0x9d2NHdaED{cPIJvHetJ!vV=Ye zun&W|ANrMVu3SCSubdlPRVwbd0n`^{?ZI3af$$!@3TVs`4x?3onv&?mOF#;Za1~N! zhLe|e-wqE|h$@Wog7O~5%zyYFX4Sa|weOkz^BC1_PxO4DS>0HwCAZh8As9|xqfJMO z2}P<=tRYeCYO~m-_Xwl@r>~FqXsTJi)kt|PkuuIq`3TJb3zh^-X=cg+ zBjwgana{ZVA=J|?E0p<7qRde<PET49 z(z?IIvWaG(9^gi$TztX?dCsk1ZT$RK``zb|bt`V8IeP^GHXKP8W*<-4y>nrV0K*_0 z^f_Ti4tCD@C$k@d$q8rY^(Xj5JWVT`wBt+S3|L-q*R+RLSgfO8=vgmZP9t;-W|Uat zPM=+~3ahN1^)zC!u?{V!OZ2!fZ}&{JLTz}6)H?~5zfgM-gPD&Tchw$ZI1s@)bei>r zMPlew05N%FtVman$;!;MkARdHMD|9`Q}lZUT$GSj9B7I9m5g?3G=k!v0~bjR04a4 zuW_WR{YxJi0deD%AWOr2Mo1Vw=EPe!KHw{-djpCgJ#4>W0@*E){6$M3wwkdseUd_R zV5rF#D!zW6(K0ZGSncF|u=aFq*K8v+K0vxBoN-|GfKy%KTz@{GiRaLU(Q;?lgjG1n zCM=*zF2f21OUxN5yfT}Xv$wI;CPdNrkTDC1W)3YgAH7syv__UBB&DD<&R=QqgmeMD z5Y`FYi-3_MR(tJVQHQy-AEaYl7e?Fw)S=yunv_nuTPZ<1vAY24GadMIT<|(g(}v^x zj#0YyJXqxxNQc@%Xq4#o%>eCLL=j}v<(N!p(C{4bHmDpgqT|%|{&ZZL|93x5G5AfM z06QJTotA7Z2Us=MHtq8nfUraF29Jlt1e-G$r^(oMK2rgD;>)4-OKfn0?353;rVDZA zwb(t?*Ra?bj1G+DlPi4{LiVRets&QqR&jhdH+Zr(AKAb4ubS zoKwyR`P>bipdPX-`}F8v!(jAh}s zkL6#WPx)UvmKegVG@dVf*YW%a)-mRImR~TQkM$qVt1+I}(0GoIK1|~Y#fvTv$9Sfo zxhtdCo*Dx$FGbirJvO2N|92kIpS<}UNA#zBMA6LR4dn6_)&zgsI#?LkprhH)FSY=Y1ERb%ZnE@Dn zvs%uJ4r@V}1_x}-0ab+dy8zbA_EM;UuG6uis$zl2T2GyzXN5!GS?q)LUuRH>XPTrKniszun<;l5I}xPnrWz758}`RM8g<6&$RMM4)rD6(RXuORSs zJ>iR>g9cmT7zPG~AaEo_L%yC^AHcsIv@`4#W&RxyS9$U08>G!Y*8x)*$laW9yy*!7b9A z(Hp^?1cW4|+F_A#=W-Z!cL%k{t01hAHEE?M0)r_Vvqe*s0Xi0IQsk!S?VhviUo+$a zh*1zr$;7dri9U2YbcbVwDR`}SJ4@LhZzFBvVGAWO9A4qtUSY8_G_>XsTw=A0dP@}N zaC`Mqbx_nLhKsGDY9)g$-RmTW;YGAU9>yK2LAcVc)5%+Fspl9BZ;8mMbtk48Zx<^* z7x;{J(WtQN%y(#a?4{bRRC&9KCF#P3Up}v4v&op=zD*+3c>P$9J?G8(jcy3ClG*K zztE@t;bkOg?HukfWn;&*uf%vEUjce;2udzfP=eg}2N2OieJ)s@l0wl02tC2VUEryQ0C(gQcx z-Xj`^tsldOOmmIAgC>-|_zvAD;yIA~8NATOMI^nmC@(|_*n7b%e)?S0)y6%4gL&T zTJFv^@3^=mb_a%=w2yQqmZ43jMHVswUJ_xsT!W^Jv+M$ph@F{D+Z3-w%01`{Pfycs zgGIEcLgX!T(sG!lrSTQI%&8Pyf#W!cQC`VYmW>lTJVp|=c_;NM9!n?%_qmhQTq1Re zFWbv;s(=kj|GOmyy`Ohi>U=PtpPm3SpTNumm|_!YV`6aTVJp_1r2QAREaw5an2Mr% z1SD7)m-PT7_-QcTsI z0#r~33w1|h9=dl#3gV^oa|iw8(9baJpoMxs0W|K@{xAS4lvd=VfHM_=Z3xtyxrPo~ zX9F+$_?budR?4E3xWLFuTukbN03{t#%L}4;%Ax{GcfDB%cHOflM?3rHRLdvVcdkuS z%JY&>9Y+8Ia9d+b%^8=RQ#4KDo1?%~oxKFFuZ6K{L+Uqi=)He9Mvzf`F_Gk|_Kgk%Ul3k0(oo z4`+iQ-YxT->}n$Z1o8X2V|JE^cLN7($I z2*I$lq!{adb7k%xXlp?k{;;NvnC{ac#TnxNW_c7Yyo)-*{`~cCzJ@nI$ zG9IP&fl(VaAKin^KxGO7q;ao=Sly2{CSjfG{uI^qAMqG%bRR*2T2;{91=lEO(8r$s zxjB6Z>MseV7>#s4OC@}sJ#s#k8z)~yPA=@R;7pV=$;k_?M}9|iHI<64#IH4af7%0i z(IxOEMHk=-e4e+V$_p}4tBT;86DN{4*8#J*3- zKzE1cy1$>8W!Ldam`z=m^D?b7cM_oq+ws(Gd2uB=T<<4ZqhY&%*seT}*e)fEX@3yo z)UshJC%eP6zI|7^yCy+*4dxm!-$HdiMnR%F3tUcY@6*5%`E4~k^8G|`cV9qtA!H}2dyuFu-3-)WQg@#v zQc(Icq&J}A?EVnZ{mCd!bo-D%MAsQBXV6?*|I8-MZABKM9?qA#|Ah*8j>^}ewQ;f= zzqxWYekaNq_{|HgLsmyLLE%t^xa?8mxCF>`Hl#9vvMA-t6QcAA}U6q@?2x5w3Ur`Q~_g zTc;$fI_NAmAnfvskhj{zIk^9DT3>$|Z;G_V7EoS>RQs-_276wteX#yn?q?0>p>hZZ zCq>k~IngUcMFK!$C_w1+vl=5W%SBW=`l0)U+1yZVastG&g7n7hRtHY+K%4F21gw-GKU@owfuy1tp{sZz&hjbIfkf>cOh6OQ zFY-#eha**4TVx5nU*s3+=m@ANAk;li?i}Mqv7%-2@}m3Y`9;g+$wjj4E?R@d@e!f^ zzmTV0ECLg@bdw1R{G=M{-edR)nC>0;0s69Q3YLckqKA?yW;X#i++imjsReiRyPtuJ zvY1wuZWTX9{$+{$fY1^>%1r^H2a%I3u%h3Chq@HK1CKRkHzpP3hc2sm#5ySi5urE< ziCiW_bJ2pL8+p>?coM`5h+;?@HDO*6E*wZZt zLpkw={wTi>@FO{f^xKSNj`aJBkd>dzYWW2;lHF5~Y9Jj&fcD*J4L%^AtWeVInZ zmdVu!-!E6*9f+6&N+~f%rk9#Ik-R2uIO3jiK0&ynHAggoh8sVj2ozdlCT4523L~l8oU|t%sL)*fCu8ylAO&?@YE1rwS#OTX^EdwgWAcC3m>4<( z?DP0x3-kuu#yk(a3T#~bBax4HTebHO^Tcgp(c+D_33aa{N}K|_wl;W*O5}+}KH&+H z{fo+kCw@=vZ25NYls)p&qC4f0MfVDIKcUD)c$^q6%rSmUi!)t!&{9Bz-F1|@bi=x$ z`{cz%56O2Ft&ndjS}Bhxs*;^WA$g8>N?T|tf8R42)D{i`InQku3!Errl3{6MyOE49 z739&A%XYD@T}~BtcBiW4d9gRWqE78cC=|_B{CQ6Pd>zhswHnL^yw33;0Kw)v6*K)N zbh&1o*CN!z{Mf=~=jAt7LOoHDuQ&MIx3ZRqmQ2-OOTHA|5YC}0CPpt8>;5dKl0P>( zkhL7vD9u4(sO9v=a_CCE*X()mosd8!If!;Z9<`NDYHo~(*R;L(4wT8IfFcAX4~)4 z+(B*eCy*nYtBOfP~qNnGz){Ex}#YTRFIFC+T7m`|P85TA?sDs@)t$%SkgFXRo)5UHw z*kAh(Qz?bRJYo*Jsxh!2z3@XPeBli$$b9jdB|;r7UGR%S-G_#+L@q?wCp`9!5iS!R z+i&=0%PT4&@^3`)ow5tRpg@yJg9d561Pz`5ckA8?zt#)x$*&I}=ThOZ$BjzvlOIC( zA-p`S^sSJm;&-Jy2ESGEVElqAEAilEoGQOZSI+;ZRB6X))CE*|KV}H1^7~i{OscGk zQ)MMlWjUx4W15$*6Kzf=qEGgPSGZ_6CsBw~W_q}6BBkd=M__#A)TUf?F8V2b;DIux zK`pl&S2~zZBD%Qa9i4=AE~tz35zuLDD#htET>M9zd_=?@d8nu#wT?nff%SPL_NG9{ z6+`%Z##fc9ydLj5aC!x;f=DA7p<#h=IUf6Mlfqoo6C00ei#|asaT@hs)N^1gH zpcqnGhwRozEzyyeAZbpmZw&qpB>lP;Bk9{PUYw*y0+R;y%eduF(+3a{`ENm9PSFAE z>!C*c5=Zc{s~b3Kc)%OKwvl+}#L$gPBx5H%{ev4$k1c{i&RIZ-UBW{P#R62Mdl-xyge2Q+auu z$|JNLHK_a|ZX6c>D85btywDQ+sTJeNe<(5?B`uY)k`DQk&NclKPcG2rvmnmn`tQX58yZ;-0XNA>+QwxSi8hHiDaDzsc}|Km3iI1n1_(G35nU3i#(QnLJV9b> zaI(`YkMl9HNZ3BB80R0_o9|*2@r@H~>!+aGjymp@bZUm)8U38T5-a8m^e%_2pH5SjD(lIdjCtiY$;><=dPoM2S#~xoT zfok?~2Qok{FX1UYNiC7$8V>Bhsq7bm)7$q2ZXsb=hi#7?ua{9p-#04ynBvj`VKxu? zqv+)U#&|sLMHo%_4|fM_o&LibJ-#OFDi>i{O_LjFBCS6T{ovwjD+M&saRP+LDEM0|nrvz3SU^Jmd>iS2Uieg7iC9HEYbXpvMjHDZO{^5hwN;)vZ!|}TMpi0oiP*P(@Sq9$QcF94#vH8d;1 z2m^wz*{r?X)i|q9Y;fX}a(n`X%jXeqdCHi-M=*a6OX_UQ-`#of_oV7$98m(Z zFj+0nMzcz6-;lyK%Aoqtg@zHhOp zx-VJ+)*rq3;?z`0_#;x>!gl=P12q>^DkFUtQj&#j)qUnNj`rk%SVq>SNvGaXI=$G9 z5(j%Ut-XZvZ+s_h(d+`{-BU;aHO$klBvWXZ^Cg33-=XEh*La|mZr|Y=)Hz28T=&mf z3x+6~3QzcLI)4IdOhq%Jlywyrx~f$!)fDETRh^JrF118E zjK9=JMur>NY$$!em@t(|HXBK@*(j3D3=Wxh5r-U2V!{AONDp92QZfIcE|QzH5m0JJ zvty}uCsMWF-2`njr(M7mw`r#B)p_#TX_D92!v)GY7-z?1w0Q=Yl6nUiQV+>&V~*)M zIQ3XEUH}}U-cKet7D&<}nB(kgD+4H7z|5k7ScW5J1{iPG_imU}fvvv;YKjc-)+6v2tp>a%l%A zb27OePY~ccAXrka;u~gc5_q~~Y`Te~Pu6aRyao%ebrqo(qF3=_n6da6C^j#2h%{RY zg#9_V#ZKxaqi*`>FcAvb@nb{${)@y8Xw?LVUujB47wCEDZB%@YgpRj;1rOt6Bn*)H z3LmZzo~R|5(KV4??x#ftFO5No0P`W(N5xi`HItBOkp>g9nGjqF6k$8^U3}&mpflHD z=9#PZa^S-ARy=5*gE!^AGWz>ObUN=ZZik6bp*=+v`&kn2F`ES%kD1I3q*zoPvX@$| zQ5;j0;6Mm(%@L~0fez|FUxG>Fg;m!yOiAdv3|#;Il;((ov zp}<=Sg^af!213_kkGH597`PSExPbv&6amSbM`@_iJO&)k`}EJw0sXcf0|}eI)1be> zL4^GcjXas(V6d^~(rdfi22ZGaj_3H6LuNdQ>tED|=?BBa1kA?qpJ*+NmWoQ69-Su9 ziw1a;1g2%4c1bAi!G#-b)2s9eh7`#P1rL5#0`)01^eK#Fs|Us!o>O?dc7PsDvU1a# zJOU4!nF}ox$!4bEO z1ps1sc;t=rko((oTbiXx)@=L+IGCv>m^Z=T*V79y1wCr>Sz%d?r#}cztjt8{U~4h; z=q1-+!(t!LHquub7UFfv;Gz?_8bnBCV>p}AB4ddPkNMS9mOCSi!{%|8=z1UuTp}?` z(g}gw^7A%4EU!J~PfU-Hk6_2j;?P#3D%7JCToWoqfy69G6u%6`cT58^wP3$G@O|ji zs12Qzls3Hdg^yjV!xF(`F0kV2-+6S22+6n@KEKs#zN5NOVx@Vwr~$L+AnwGJf^p4- z42!2xAxvj!hh6dI?}sCy!|Twaw~)5rLp&&mY;!wM^jIJq>_8YN8gsi4J`o6);u;km zQB_MdDU7%sbdr;EWyDN$HiEccSkbm+T>fns>OH=mi$MXJLc(g>~TBS6qy`6x^U6TqxRx zm5$%uA`^e1j-E;5YZ~6Vt8GS6KWfS>BnwNW+3EK*hfexo&PZ8@AS+Zf(99r^f&d;< zv4=8o_X&@{d%x-aib}&YEaadoKOZ3^u?HMbmN=B^WSo2Nq!=Xvyg#)C-lzjQ8wY)UTL43gX8F3>g71^- zsx5dm3GSfUf1w=ayj%uV>BtJ0jLOqj*C;nR?12ryx>NA8>RJ0ky}5Bf*5sf#G9t;XN#t zu04x2Y741rhM=)6xPML8e?n{qxAkRc?>luR{z35axhPh0oI1 z*E~hfZ7A(wiOTJw`o?y^s3CnUjBkmj*KM_$$;T;mn&6(nh6m zP;7XrX~hJ4OA2Y>FX{Q1*ds_TRr?wa~80*#-xo-_^>` zR_M+4{N~!zM3zvY8h7)2SUIy{;OjIedj;&o!Fz6@7eI601$>yvQ+F88!a73v(juuy zvUQjb!MYOypU2DF%0+}(;gfqC5LRSg=vLNXcu@n?-f;A+uMq@JVzM>vut9lCny=%)?~JeM zDvh4ihD^Z07?QFI%y>Pk2C;-12i4O`c;d;>jp7pfU6jc2j`^NB>SwqInSTUdCxnS_ z_v7dl+5`=V;fkbgdhvzXzu1Z_-(kJskC_}ZEPnmURrt^tT@VH!N3hjZFZU~L4)3H- zYo=Wh61;VvdbeHS6`sb;^7iOWejQ87+j44SY6C3$TZLUWAsVV{_d?#g!55?>(OcIm zk3=n@m%TOTTyhG2(?Y+XO48a&bW0S~9D-4BV=8_8fXpO{Tyk5y@Og$X|v5o4*)R8^b=}co{mf*2il)nlqMGf854P5c~2Wtgh1k8K^@2QWtCtUX6-QBrP{l%KokL=>{qU|5abui3KuMwF*=If_!@)H)8aX!s;M{{ z0OR$a>Ioop9evI+h>zdVyLhL%{GQ0_k$zhz$BMQIOL#sm@E+5CjKecL3B*6eU{I2| z7p12h=%kjvaE`M^AHHzVDb&pdR&f~zGhy{GZQ5`I77SCfwX`fy`q$lLyrijjqRQ~7 zpZ*k&yEb7Xh5FybU9p3s$alj>zofm_sAh7|X}h=J<`6D_oWu;3B=u1*X0De@*`Qp6 zB|7gosQJN*0_;(*$hn}z=HlGqkaiM8ZCq8ut<&FUTP*YlGdc!|nfh?G#v+qZ38NCm}LIK)`Sh-Jo{8sd?2eWXv z#DWFdGSrP|O4a7W&(1-NWyp@J*m#nxa0=GWuor87L@F!@eW_-?{clt!uT9PR69U@! zepL+TesCV#!=;S`otopJjs7|TYyu0X&ILl{WKguEU3mxaJp3L4gH~}|0X?KguOd*f z2_$}D#0anFVZL zMSo5G_VIz&4j>L3gPlZQBk6^HB82XQ%XNoPNr2h)225|dN7yIjd${;v_iQ-K>aq#Y6c;zpk za$H$ML0&UJ*JA(Ng$m@Tz6M2K4L*U)8^>~@ThY4X^!(@oQASt>|k4GKSB6LQ| z?6(pr%+onW>N}(1#u&VkOaj3<&W{ zND*N06C2@p_Q@IA?O?lLpnpYttSVy;Wcmr_0!n6?rSMD2_#5C@G`A7gNmVK1ujX-1 zBkm0&?zeC(+BcA)kl{rNm|X@HSEj%8W;n z#8YjbfpCo{?!skVMQLl&yuz+_rMW9QE?o9}(ePOK-=p|U+8-0)&L~>j101LFYgR4<-J3D4}RIc)%WNQs;WB*Nzbi7*H9fC$@=NuuXq)kG1m z%4UX=J$vy0C7QHE2jIKKy|&N!uChOF26RWCL0BUB!{)8ii#F}El{1PUB zw|9>tG@%C9jBs<(Lw6s2ZkMu^KN2C429>r0RF7)K80Q&zt2%V*Z_$0Yt#{QW3*uTSCV){eUBq>$@!us|o3RtadcDk0F+&8d##HeVTNdN8 zQS6RlOj+i|b0i?$&q4pt&;$yv0Qvt2{Le(?;&hPzTA2KO3;B=4M>hg&n#ew(ugq4% znq&+nKoS`|qzc=!$q?HxC{+>Mr`@-Z)Bk&{ZO2(1o5O>!r2ZO{peI~%v=TOSuvLf^ z!x2o=hnOyO{dtEInRhNsLFlzSpj~Y0fghh}sEN;#dy~li$n&YMoLwK-k{; ze{lEa@ljRh#HkSA}V!5cj~HCPH`nnmVP{y z_IP*N&sSG4zpC`TU8&mg0Jv}Ep1;^Ctu2=*%n^MmdFNlBN)zsBbtzOpQZG)Wo+qho zFiVnJoJw_)3J6x`aL%YKh++xaZ_&tDe^MJpFrx}!->~_!S)wfqqAT8rFFdS+}b$yRR?V6x=?w151-LDEpdW;Pa zKMVDq5-dof-bQ!9#@H_lm3yDE0Py4jxA)Xp6-zzP&(w3KYeFs0>;0K6DM5kn#`LCza^19O+QfYF!wCbL}GL_aYc%a&w ze}998Ths_&nM`F(FlVXUO+C`D?UDYc9_dqMjx9W+ru%z*Iist@{3TYK+VVV=2A=Jn ze^V;WE;U$qwtN1isnpNGmIe#YcF&)fO5IH=cowtIuV#qN**2-(j;l6!^ivFQ_)F|0Ekxb-Tm5ci9FLpGooY z$Ecp*L7n>CBSm|SgOS_QMSXFa&qyN}5170N0VG;Z`{J=p@p0&E(wFsv&H69IDe>VP z`*Y0}j{V83Xn9UtJoa4)cW@ky0!(YyzaH^dxh;hTZ$2D_Mx+Hs5f9)3d;j2U4(q-| zYv58&<`?VM$KVIP7ps#0d}|V&*1w3o|0xOhg-IR!S=1Z@HF*uv>XAK8eQK%Hq~1JLTzFX4vs_#}}d>CXw3t_Q371851g-LusHdNEGWD3R|IO#eRiYTe21BG7ZC@)L~bYB2S@ZTK*6xhVykH@ zb&f$(bEIw2M5?nSNSbFi0388r>A@*-K>F(y>KPGFwv8sx?-gxN>F-M98^R+>IRW_>i!F^n>Hx?mIZ(NEFxFHak?7)AvDRk3<)DnD0HH zta;(Q=UMk^nbUu7rId3|!d9}I7my$!IOd5&Oh;zT)*!kr4xEYHrR(K<`;Sa8N3{B2 z-eyD@iQL;rmoA@|x&@5T8WKFWG<5@*2vulxjzl0Frz(~Bb0UMjK#Cf*nbP-gtyQ!2 zMyyZse;<|MDCZZYL0SXEVDviCzPQ6(_acT zvF;_qlHfOWVv}m$DqB5+?U9N^l6PGQ^h!lUFG+9=|a zqER6^sqffmXy#A(in!Mz8lbR>0ga49j>gKx6X8YnER|6wPxYK2~lJ)Lbedfj;w$A*i8yRk{ar!g--18G$ikxaSWY z>4RnWk<36D=H`J(p(9+05^=|oH7r_+eV{^YX9{OoD52}YgTFr8KIFcR+ z6eVK!@oc^|QkI0t{0H+yHc9y6I?@3em(sdtyM-u2V_D*j?~xpR&yJ2M-mheq9pw+| zPTds^9%OM-ey}NAcyeDmojsY!jx5gfPB^3n5I>nq=L(S8#9^-{jgSY{<|L8h#QX9< z1Yeq6nO{ZoX$zBXR0OTDr(nv5aF>CH+LBz!f~y}#@dEI z>&Gy^9E!EMRZh?UnRMipF&^ghD3V-_ zG$%6}>9V@J;O78I8X0cxf<-nea#(wWoHra6Q;29?g=qbN$2KTqikXqG{xW>a8 zjxX%Zlq#MxACxNmj6%w*^oZOV z?m_tO>K`abH2Q2?3HC7mV8s1dsAJHgJ`D&!($-c-J;INP942&Rpj(^TxYliWqV^Q- zT6TD-$_=0=xX=#?-P`~(YX>Iz;bL>Xe29#iPMXjfyB?Y4WUF38wd_4=M@}v-Sy<+j z9`%gusz;V`^1js`-w(rFt9G2$_ha~nlw8(ZlyDm{q_2c2<5DEIvCmVCp-3Rdjo;Vd zJ(b~%$gDT~kxMhmjVA5_h|JI6(i>yL;jcipqx~A#>o{`G+f&Ktk(sQmv^7<_!x?P0 zT${90E=@$Gv~T|KN^jU@&SF?f_Jq{R$?k@_Xe{G4cn)SiqJn+LfQ;;rH4VK65p7JHQ#@JcL(9kXyN?`x}|xQs_+HeBktD9db> z>?bpV_!f7>-Kid3YZo=tkrC1j+zPLbttC^E7NY_U9qCU7q~{ z1&)G;N+seIE)1O8Bf(zJV9@esdtiupc8~UsbV#_|9cn3X>IGD6H4+_fk0?Li5Z|5w z0woEiIVjGxQb!1$J*Wpv+AWxP!zaZ5;FB}XiN}Eq91h@%#m|)boPv%7(&9cuMBz~d zI&4@lP6dGb(}QiwxLVqyYOVH1V1TMv1II7Fy3-T6v_ff9cs;?q0qvjyaqKMerN-Yo zmSt(tUKMPQkax`B`rz>5tHuRvbyp!Fy$T-fDr}Iha>hh8&2$F~^^&Xdf+wf)m^NBi zEl3>Bth|)b%eYI&$jr;&2$(#0{MoT&Wt1sZrHT#7Wq|6bmy% zRoe>M{ZWZhXE>GC%wiePq*+&blq$YKiZf7rVbsO8^FVeO-}~7%*);b37qn3^zE!e` zNrd3H+zk$%g;|}K$ld&^;tq!-ay>j5t!6dL3i0I%U?SYp9K}Y`JdR16ufRICCtd!` zl7tGpt8Yp(Z+cevN%k#wgUFpcC8b%xTua0_t*4kYUSgJeRf8jnVQqTLP%W);d}x!!D0z`r_*PA&`U-MkWw+J6(BxA zri%&6kjFW_n-~{u)H1U}XoMzg6 z%6W3Awq}2B5!(D6eq)`7ZRi>@#0QxrIz2R6;~$l3tC~ZN$$jYo<4 z55K>Walud`XGi`IYYQ%2h5e@ZZ)f8haXe)+ zthhMPP%P7M57|cCc(Fc`SMwCGS>k|F>y%84aQgC_7s_O*Y32BcbW{+J%!&+Xa$fvWqN z3FHK8Gvfp&y&?x9M;H5wV#=v5d`S`sEXC#A$GKPPReUXf3MCRff%7@j$f7RPBloKF zf%zEN5&xqx(7c5H%Bi7B5uw-SD64KEH@#q=il;tlOff;q(Zd1Ap~E- z)!)jHrZjW|25Q)#hHh{IiSjlbyHpI+uuYY7z1XGB{E%n*?Cs!ao~IOB7_^1GADRzR zan;hCG{fuEyqK|asu41i(%pCYMu_to2%zSDI`bZwYPbsS#KAF#u`S^2P*j^(#!+CS z*?E#sUXS#4r}^Y%X=$#GK>zm8^dUL28V4Cuh7cbZv~aYC<_`I`Yo&C%GxU(Jumi%< zfW)yZ5X8KZKahS5|LcL2Bj+tjs8{Jgr z@b8<~MeNPY>lSlF*QCxPsn?_)5AK$&h!2c}lJwIrwGa`1K*I6b&y=b@enGM_si+jvLWp2JTu8LwOb)A7JMszgX?+4C^vuwl9IWLm ztc409z);cwWlo}OH)TDgj5p`#;qpfM6PW8h-1s6h9lXj-K#suWyuK>0Sw0wZeG=Q{ zU5lW}Vsl;I3Qni&vm1He%6r>!?6dPMk(9UHyjo`d1amjrM3VLX{pTsx8)I!K4JKO1 zVWey0gb9U?^mji8=gh<-bJ@|>J=gy(@1H;D&MSD)o7bx-`b_R+u*|bzA8U#M9t^Bj za~2P!*v-RLVjee+x61HoF(<#ALNfW`-m9e0mYCYT$hX$?lR^(kED@$hc*+;glU`7Wg{$dv zu~0#gJ-M{__Vhq8)x<-n$5kh+3X)~rkxM{bhNmhq^el4$^#GgN>7ar+f=v!Cwk7R! znu|>$Ne_=7gP_gAwmGu#-ehTcvAk8jB^aTIy{e+SFzn2*+>N0KC|gVSGz3qO6Cz%g z>jN2M2EjYRNU{U&4cKrk(W|Kn?6C=yx0$E@L1xEm((&pe zYI@GpUYJ=7VnShsTC?1)lIfw|jIa&2I_>9hXlbUKYGJfpdZt&h{+HTCrON=!G@S%~BRm^k1QeVAH%aOuu8Wk0Pbs^Q3(D@eT_w*c+l z2k0d-`vxb!?oiukA7S7R?=By?-9L7#&xrb5+i|7Y?z3(1yEr$)i@*vIZUwqMgDS^fV-$AZ`SIn}C8 zX;*L~eOr67U&AZ4+qqRXkY1_nYGfHnr|_a^4SuXTl}V=p&SZzah!552Db~7-pLAt0 zO4ctJJXX7F4af685_oP0j0&W$3n&jYaz6p(u!Pyhu(TR62`E}$S<+vtL2m&g*_443 zK=AWe9dDucMynZMl*DdO1KsN%yW6)dUhdjau9dx2w0?TTJ`2H0U?5d+1W6V@=quQ& zZ#(L9?a*qyRD@=y0=c)Z0J7WGrrVl)ks+mibc&;7CSHpMgFUCh$5ik!_yLty#QRse zOZo-+k(j|?gYbrg%t*}zvcMjLqLcJRtC6852NmTp)vM2AU~JE+`@aq<7+^4JOa*J1 zLB`ac98)}3O`%&0cFiFj&ymr5q((DPMx)h;KtDC2&tj*^nqX3_{s(eY-@gM*%!6Ur zj>}KNELXv9z;*~bsT&4-mQ-whM78-@X9x!k!1e1v=Unhg#f!d|ENido-bpz-#Ac~Vi{d?8n4CO*@3oWfK#9-C-i zm~-_tv_fLkrNyI4wA-5`jyP^Gnj)8av*%bR#TA7P76MVUDg4Fx{sTT-k%>VNgAc32i6pjbP7!&>cAl z56q5ovW0(VmD_7w$UIBZdg7Di%ai>yWkm&hy4mL($ss26fl)^GgQOE4u0$nnG;fo+ zV}kCGh(B_$InG%tk$cb69HFwWq^Pl5DjjV;S0t4x`*q}xm;x;*BFuMuo{?Q7oyG*r z+({PN{CG_(U{f5GxgUj~kv&LqsItKvy7E`@!N(qkkBzU901%I=g2BN?_Qx1;k>g&~ z*p@gVh%M$u4k=`B6}HznEFMD#{!CvE?-M2b*yaGMS{Vy;vG?Y97?R`}j`35a3}K}C zxl$75j^-M6G+>@~`(>~8pw@0@cJxdRxTk^Roq-LOAb)ptDDzd$b4 z`KwePdcl4HQgS2vA<5AV`w8Tu^LOq`!G4S?D4oBBoWMRuHI{_^48*Qv_me#d`{18a z=$D!R;c}A)E71!?guMP-hm4As$OvKLZEKa&Puk z)@)^&9hB$ZY~E8=;3*lEjvQ8GSFEX$jKEMsT_IBIcoB++*YB-r%^;yl;&#eb!f^sR zsAJ3AW0lGJ0fXvcU!m;duf{I^mK-)j{ zHd0Y4HxK~8XT&sL2TZ;B*neY{HEn=Cw^er?Sd?qN&#vH(EIS!>+n>RR*gp0lpP{Wu z^cAp1^{9D{KDLQF(Y4xY>Z37}9|HNo59@H;Bw&+G)*W&mNh$t9!YGTTv5{m^;>taU zoP0c-eDE7Zdc%?=)^kxTz}uP|RN;;0N57*IIq6+ZYCV^Fl3sNnz41LqRkWgx{PS$F zQa&||mjdXRGL=6VU9GQCnTcwceongkr37o8OEyd^f+Ci-dIKf+*M3*@BL`MC>YtVt zYKGBo4d!|32H>FWV~oN!f9S6Mu#+0%wr1PeMSJ~`V7dg0sb5QM0uEoZbfd0=y4VTo z)9SpOO^0cB|AGXs_Ci!&Bh7Hw!^U^nSybw-Zv}Qz^$xSrZ4+b0l7+TPTeG{aPhty~ zy6VwS>_o_K{N|2LmAGjxzQ|+TtKchcSKnX(bwoZ3~F61I{a@TLeV8z&H{%{%lgYAo=y~VTBe46)j zcjG@D?y7Yap1RxEJ0;SJuz=m|6NZ=Dcd%YG#^tCf4xTimZnE~6zspGKySFQ()wpL zmvQ`3hbUDQrTI*6-Mn-bgIsNMlkDP%v62Tuu`Qctw4hla6v8dV++YJH_lVec?D~T? zR2WYysirz9*W_0*@#3i>5-H=TCo+>udz&IN(WP$nX=P1#w6vO=XUH-|A=?^Sl3z-w zlbtM+rzq&gf0qR^4yRH%hSz?k?()=r7u5^4$pL}DE4t>{>9e)!wk@8BH{IOtQ(K#( zxo=t=dz_m56;wk^LJvzv?aru|t%b<7^$227;}FY4+92<*$*a~Q?u4MSc;g-;9XOu8 zLRg0i{DS80235WFkQfv!N3&K51fJ;l?2! z8YNw^#5WFZZ?1Ytb)G)`UBNbRTG95<c|U-#kN%~R6HBc!{2$;gJ$wed^bht23whd}_$(YCE=mt}y&9^Cgr9sS85=&Leh zbY-yJ(sYOnp0p|U&`bGMG?~H8UL}PHijYaVmGO|dVIH7blCP)r7EmFX5#x~W*hGEt zmZe)Yjvqp#R7|)Qi~n6j-(7ATaI;G+eo(bAFU>diVtZf&uBgP~JwY`5G8>J$ZEa%Z)7n>n>j~+h>6%C^sHvOlYLP^U?zSA!QPOHa$LC{vsZ; zX496OP0?jc%^9n{L}pY@o&eBKJ$=mMI?BgEnW@4RA9n#7&!gc(-aA zKau988Mv1oBIrY+rE20?I8*9#aaEnhyl=w&(Of`9{<+O-*yq8(P3Bn47mlN=6pUsa z$dX|@P!5kv5Us&xOJuv(xyl${w$xsIXEK1HEK!+FpZL%9vk1jeAquR4I?}Z(8YTQ= zsre@~T^?%nKqwTIfLbJ%H+@VI3W$QD6z&8A5TQE=>x-O-7_vrHCx|P&UaMD;iA=~u z>;akqOrAt*sN3))y z8u6+L;1z0kK`vd8t3m=OqDt+G;sRg#J?nT*xxrrFq4+;#>yl?W_I8V8H|-R19D9vk zhzRlnv9o;F55FPXg;;iM<5|ctVRr-B5%-Iz!52Eb6ZYa7_BU2)a;G}fn5or%KsEx^ zykaqlvd6R8}twxz{y@Nurg=WLKgaY*vHj-ro9Y{4OE z2lD>Jiwxjkx@|kP*)YYjV{_d!h5OndFzj8fquQ?;JrO&s!Y;op$`YAD7V|kg@`S{T z#DR539M{+*JR)UsSG75XOAEc99@>!MCTQ%Tv478(DGtSmlk66#T5-il3vL?*i&lUS%nh3ttUq=s%psnh8z*ygdJ zo!^iE6uwB-a0taNS?3kzqwXZBu%*S|#nZYvD__;hg}tCUS^a$6Xg-axQ=EOHYfLmG zLz29usd0Vpm=0No)&EoB&(Tx~$2JFkEeC!LYmfy_EJDT!&xYx#5}5Ze3O>jcz}C4o zC*{i~`g7*P@w@4q!Nh(B{>Uf;36E+o9Eh4(Xi|rV%5pljy8Zy3D-0`9?S(QjVniS2 zb&Gkr{6@xKc(JJ3&o!@Ar^>*&@`+f;95FisLMzP-$v1a5k9zd$K1zSxZ9nlYOhzkv zobhITSxIN>#B}ZMUn&aEe3JRr$hXInAd(R_fx@O3*_sn<^Ke=WDN&GoW2&b+W;19H|rQTx3}CVSFqU zj>FLC(6pS+JK&gi8JkV>DpIl$pem5`&Cp&zy@mo#%ybH`vIzQ3LcoNUvnPs&hMc82 zwBaZ?A2O8GGP<@aEo0oqB`jnWo9EEYIq6#6<|_rp8FfhNGqhD=45Ay^U-^_SE3`gc zFUhVzfFm()QfKKst08M?=0Dz{dDrg1sg|(k57sLocR*J*^8|9ckLW!IemVYIR|hG9 zladuDWn+6xR8<5rMC4DMR!!F=Yr;fz(%Z;X;zO+lJ<0T+k^VaA3CAD!i!%O?ji{D` zbK9$@4_Pi0m(vxkmaI<8p_`itdR2rWPhYc!r{uR$@RldGOv#af5WO)I|Js%*PI-fY zI#Cac;u>!r8(E|hzR6GgeQU=6IYp6--}O0`_)T#6t*F@#MCN|!4di9~z#+#z>; z3()5}5z@>R+vBnP{~eiIS8xLkLt^ZHF(I00TnuS?{f$5zIX6??}({*Seq|CSFB#+#Ik;a+*0o2N(S*mP!~$Jt4js z()HuhGqHjAo;}}LbV$zSb2o#3*Q31Uc zc2Xsp4dZrdt6Cwe`^I718|*6$lyV&;Hoj-bb7uS$m87@O4^AhbyW?k90wPKxc(M0s zDb17QM6*N`3U%7F+VLt^Z+|PeAVC}_vw=TFdlBAkxkfGPEc1Qv+uTHr ze@nNr{F5y8NeO2U`&z8YB9qRRju=@kk%9L&UDTn6@?HE{o2gfSE!H9P_Xy$^Dybmr z%W7N7=1M?#cm*use#`$GEYrD$Q3@3XB^*ORoy6ME*8}J8VEu}GF`tgGSk-dV=VI`d zRmB2&1$x0_E7oX#dr0}USZx@i-e6COLlqpq6~aQW57iK`|1J3v+5g}(?s@yHt|2w= zuJh*ZI&bV#-5I(gGi^zZbsYa_L|PiVL}AeJqR=2?<2~8a zD4a5_1}9Un_)aw?S7t6DlBd{YR8R|iNFlaOs>xLG)EGMeB7?YGweg*bRDb$gHT{8_ zWS0?K9VLHG5SFU%i$XEtSua9ih%0BcL04B$Z|}C0A{n#j^?`vX?zpd&o6OO0&!6Jc zp4qKP5yEh|A@Ntcd90d6_HS&roa!0Nq3p|;QvB7Ftz{zj(Feg6ka$}>+kAA3Y-+;i zf)HKw)U{!Oic3NoI`)SNLGr!pxbPSwXJx?5etS`l`6J@eSf07CK!&>gTTY^LY3eVHrxgMr3Uv4r-! z4lJA^meBNPMvnO;d^LSX%YUA^hv)Bf$ESHV_wJ8Yd#hUhmI*b|FzE;6RJd2wkEJKj z*SW1CDi;6q9_G_YkE_yN=O!Y?{tLKcTy8}%NE(;-fP1X@yKq05Wg}MRmmdrGm#oYd zv#_6+?~L^LfZvt`JeKH>$(DK?OLUH@YO^)l2Ft)rCSZ6XcM(EoCgAU{GXbL!P#Jl# zT2&YKLc3xM%ev=aOmZ9uG=6xzj)C)=j-7%Z_m~3Jtt2iKT2}0;OU#E^uDYb=;wQc5 zVwNTOt}!8!^=MF*Gac*$E&rC)X7KpCgYR#_%rv z3+*mZKrh9Cp)v476rL58E#8XLFJyBnty_AjIgD*w!e{`k98rrxsZk|Gs3M+)inL8P zwwiq*8WL|Th&2WT->}7>_Zv5u1VC$xbpT9x@Ecns&1dZL$rik{!dKA3@y&0v`=XsT z5#dL41m`hZx$7HUo3=#{ZAPKAK5&Y<#~q&^!ggU7OxUg{#ub@?`x5J> z6i*z7Agf?2&$?}9MLBB)#CZ5>AzNB)NFjxH7OLv4*kbLzC~QC5CnvyWrocLo1Ig#B}ZJ;HRRkSVNlF z**%iKLYgGZs})2 z_C+U=cY5TE^EeWg8!hGvSbV|ZlY!YRRntbZg5OuvUbEix@N6{4GFW`{ww)t|sy~ut z@{4thIaU>tCWrge@5+pCR5KCio{62WrOw7;{bxCovK%8%=Wm6R!qKFVRRYhpa3Xw) zax{@MNx8uaI-tLpp2@(SWN>W@oLrxA##s}`FY3=N4cv6|#M=36J)6y$Y9%sB&B1p) z^%?G}jthb(>5-{8wQmjNmKzc~Q%q?#oBL@n_CsoSSD9zwcvsaieo>Y$S=qDc^9gp; zq8FgoSh_-_`r)Nd9m2L`>9dMK;Gv;>!#+UX@?&IRTJP%~gX@;Di+g&H4voKIrb9htd825^!xH7-wdzBtb zkZ3WTSY|NaYIcNNWrN5Z3i5rZBr+jt=mJ?52Z`QLuu~8m3Ez_(OWev{-yD1yI;o_rh$nJVy5Dl*NZOEb#L9z5Jd4Pkves{xeQCB^1+eoPtptZ%rsbq* zgVMDeJx3el%i+c+r#8s#l(~=Y8CcEd9Jz`sx4wrfWo$SQQ^7 z-4eOB`QdeRD+GB-ihKNY%d(4SuN`EWsbz!+b~iGW8gZ1m=oa^SkfWb@XUC9S%j%bI&LSKIq9ISLqEsh^0zZ$c$x|Bmy-)&ip z(5t5nQ`g>x49v;R6=2DMmu23-R3xfUsTZ$hv)~}PEK3hhwX1v0^zhw+tJ({Vm0$ds zgX*{ofJ;GVy?wN@deB3kr3Vk{k)O+Ku8}d++e=Wd3zLDX=0OV%h~SEa|0=a0B77G| z2#KK~5>8ErV zZ@pKf`MPvev5BawdHz@Ro_zSR%kzJTpm?4$?!*?OEFBR+d;mdlvYI_v^PGvO+ts?T zU93&(Gf+CcZA`I?P3HsXbng)XoDAvXu(I`J;=NyX*5y5xLSbrR17R z;{gE`;b=w68}ZI#goToQ91l*j9!98Fk>qVry-U?%@Fqg|9tAIcJS2TMl#w17 zQ033d%c=J}Zja|4%E$-|ly_(Sy^h-{j+S$M-Mx8vJSsa{AQJjKJ=hYTQnXzG2~{;Q zqkl(0KqnD{79oonke3rFbH)dw4^Fmq~DL^ z%giA#N(#S~D`XFwRdLhG>$2xBEv+XYiefb34XK!u?b{G%*up|_ZdYcoZda*Yg&1gH zO{^Zrn9Y35Wh|alt3i?gVt5PJ$dMc5B?^eFMuxQH|3*JBXAsinvo$K}CYZabcTGfo zgkf^y^gjj}?hbPheKI;^^E|~o4RT^p7F5o3$HXTf={qzBgfA7gQvykI$1B$K(sA^| z(%~KkvYhI>k`c5t$s)ko(P4gy+}rX1Py_XHKG(ylw3IOFo?^$S&3+4N6|4d0KpQW6 zQ@Qw*74zI7rkP6eKGPJtQ4S#3)S7=~$N7|y4_g0 zN&tjck5v>^2qaa}5Ub|Lsafh_4wOAdPpV>G@?&Wh`F_=; zazB_A^kZ-*t{RN&G!kg=r~al!`TjgOcQMYdwM^f{YK;S>4f-W(>Ra6 z&5i`B8M!w}Gy_t<`SF|5PsFLYdqitPdhB%seKJ?0ghyETkKLhy0 zgTnfZ@Y>*!KQjL05{W~@#S81OML+;tgEA-0WBZD|ybvv~V}86kVSWo4t{Kk=$WZv&yvp#kT%x-yOQP68OVfmc!yGO>3U~WH#e_J5 z(TUtAl6AdCCfTGe&P#6Sf11Tk1>=|zUY4p?t3}*IQGfoa#nmW>Th7>eKewH2?GOo&hexBX-tzPhE@Jm#v8HkCnk)AqYbtmPkGBb7CI=x_1@GIkD zdv$CjXdE~b1twR2#=+;qeR(+gq+poZq2d`v zJp5mou>s6m$td4Nh|O!iq9rgccKFzFZNCD>#Aq%jz>+^Q=|RxJ{fZD1f8@kcU-Sce zxosmP%3VA>FuC}u^gsz)2l}5f?s{rml<$pHq`Rw+%Fel9n7h6&dQ5kwXF{IG_GU8A z!ZxA1#Up}vutYg(f9o&!D)?7mNC&I;tFfLI+HVhx4DHVjWV^l%GR#p^8H)^a)VcD& zFh}Lf1H&A30uTOzcat-#M}C;4=5u;vVuAL8XIO`dmT_r@fOCIrxL_jF+y@?+oe@!` zy3}~|g_3MZ_k}9;W;0Xg-=t4zDZcw+y15M#9E)dZ$I?gmjzYBCr8P@3h6hJ*YNeNK zZx9cHOI$LxIVp#CsNL!~Jc+F23Sk7qj=+j3KohG4@+xprY@tPaSYwIYp;a=sj{nh- zIp+?tULNPA+b*{H$hlzbB4?!f4HtMye8w#In*Ra!2{O|(-w`@;dho2G?eT)@Bfs7W zdO53){PEqiw1p$9kNi!3Kg42?H5GU}=>}#Cy^o&nl)v63!fz!Tx*}4Y$BEWU1h0;C z_-(iLtEQqvve^@RHrtSFwsC$D&BjknHp#DQGB4TW&}5SXttJC~v^59&g1H>}(ZYLc z@chAs1_I>Qye<6CJ>J3=XM~Go7uAvZ$`PQ{D|;S1)OB>Xuj=mi%5zH-xt}dl3aHfX zxe{pONQyqH5I0no2cjWf&Y2oKq({z~Di{0kdS38DF{3Tk3%-zFc-iI*eH-_8t%WO+ zySs2VlMhag_FDzxL>?HJF4jtSJTIuCY_iW(bx=apL1C`EqF{=PdYFQgf}K9>BdEU3 zCzmd5F*}eySuc=!j2Jmboy{1M*aM%XBzel{sh7QznzP=1oE(%)v22!z9oqvO3AzRD z3pDd~*<6#-^0-@^?)sY#Tq-SffXY}pKJyMMN%s5IDpkq&h9YTNFi1xz70Du~22tb8 z&}A}WIwpJFG0bl2?AD`SJ@yBuhs$##=ROYAQg?75^ZR7Fac}3=3o_V;a_b9Zd)Wf5 zYVsM|^`dPk^6Fc4!jrjnVW2EDoM8dKrkGq4TJCyHs}Um)BM3wG$e}`Nk?nFIv3<0f zr}+?joImBp-w(AvBqr@h``|(|<=~+$k#UO)KyXGP_xE=~IXkWhmPKYgOC}H7Z6r7U z=P+VtJ*(#IG%INvlu1N}!>!a4ln8}{MDDB9Kz+aw+vRhk{eckr74(!XzI$p^&*F%f z)}~$r&8?|kCEuYtv zGCV7ae8IMQg1Bu3R=%RG)9-mufiymzdd?+dyn%-^cmT4ir4WEE0Imr+}bDfhT3XDS)9w3;f`a~ z^0OYb45XHn&QMKVxzyvZKvQDX^E0W(``vmb^r&am;_iJyov3klh-7C{0Xt_TcLr6c zU7A70S#{1S+f;i(EPv$=*hfbt6V| zugX&-w~?RKDBjY{Gi4kM;Zm{0TS1D%Tr@0;J!7W|%Aw54gzq3(8A7mW74j@xy(rQo zy^3ecnO}l5?g0q#36N*@k4b!yAQc;_UrJD$9HNb#V&PRqf;xf|PbUeuIYM%8l3igQ z!^5N8+Ph^=2nrs_9;L>DQE~1GG6;NsWRWI*&scuUdK6@1IQ@-X&=cu~!<;6LGK!ch za2;n~>5fN917A{=1+4ewDU$mEKdXn+j+rhEQ2P%UjzC2Q!EirGPD-hCU6-y`&~=Pa z%zJ0{&_^gcprXCz6?mjY?o4;J1POGJeH*nifNQO`uH%ObD$IvDRY*&xl4Guw<8mv< zOqFA}8MJa}tM+Q%eHbEi50E{|T33{vv4XyfNdrx|cxE^g`7zH=);~3odxq-0@)pAp zJ^NfK(cdaD#wu~LlyF!jxU~aD9;cPAHT~&A2uPCZi6}V}xzfA~+36yVJ4*TCUP!}n zUp`M5U>@EUdhg^fbWSd0Ox~3tdOLv)yrkns$!DFeO6g4N@92!^B%JII$GJ+61gXknY?`uAplD6vg{I;PGr507D*RGGo-LI1@oHq!Lu-F+JbOP=?T}v zRf|_1q~VqTjVsG2w1N?bf3g5)cW`%?r3HMd;+0Phs$Rk_7X!!c`!G%!$15B&HW>o5 zZkl49=jGzT%SF$LY?FR-_uwT|gbZEukOaqkH~3Hf5L(r>4;#YJ+73N*CjkuL`c?i+ z2_ZXLnhtSZA&4k57koQu&!(`eo8)w_NyUyGk)}r=O;w;)741}mr6Sk9JyO zkPo0RO`OuXKBp^=^i6$=38z$g!-nH z_*RRzNp1(-xFM?%p)8R%kjDy)4M$%_sn(ab9U0JE)DBybBDI?Pkhb8lP_$RsjxU0- z^KP{cf_KF4LczP$Xo+@Yx|Q%$?6NRiEjdwNnp0Qa=tcgB#wc1hyD8qEheoflp>B2* ze##{BnY8S;4qC&1Vm@z8x{o)PP8>S?9!KMK2D8dcljzDDbp`zoMBsd3dBYhi^759=$D>4PxWxwtt&> zg!h)Te#}5Y{rC10d=uQ!D=;pTog~&tK zb~R-F;17rtRM1Ze9c9tap0YhGmiMqqWIZxZ?~O{Wl41JXgXQAY5i7Tu{;+qS-}uNMzU>)b^c#SAPUA&J&3pG3Wu#qXc-qHN=7mJ=bpT6_tEgMU z-v2S@T_-^F=g;yM?e&CbKkPR4hG+lQXC#bESRWf@;fH7bx!m=w7JA<%U267WNg?nE zdq1w%+^^ElGy;=(JfN>$eN?p2no>3(02@p9+{TV_9|drwn|T}yXL@TDQimo{_yNq8E*>$@K@b(WS!aK4kher ziZL}yG;-O6$LKI^1LzNHQLEX-VBAvDxa}EzE{e^=v5!b1{*oLq3wB+kz4G^B8B^rq zbTMnh`}n>O0VScVX>5x$5?;ECx@mHYI04SwMH{L?E?fL9T#>XBSU|-d?n|tvZZ`72 zechS3$aqkVqpaS4aMGbD!ayK#aB|gqU)OoLO|z_yWUA|1=go|dHeZI9BHmNc_R5kW z`ciz{ntiUXw9xYk`*vA6RtuA#StX*dMDXOQrT3-93^HK8p*{&Xe_?$k$~GU2^8W#9 znjFv(E(rxdZM6*ND8v|EV@nG(SB^zsA>`L-#y*AcV5OwhY*)>ezaZ<$FXN?zlg?_1 z+`L;w*u=uuYMvmM2d=Hu*jF%npWC(%gvAEhuPr}7t%wIb{ewQ;D1X5nnUbxls-zu{ zH!>yNZ&i3XK4Q7ew{+Dg%j!I!(cC8NFqgC$vw!Qh?T-6J%2vC5 zSsA#ozuZ_`j?-J{pBMUY3cfgU;%79IR&IO8U2iXTC!%2GF12)&Srygone0>OvgBQ$ zVS9@XdWvV?C#+!HS5N#x$1_K|aXGP;@2U?hEDI~D{OyzM#WU}Vzn;h)ax-+(`>+(2 zNsVX9lT>GUU8bxn81>lWOuM`QGY$-j^`V0!28vYcrYJuld;m>ea7g2hqtrr$9Ln83lpMWs)(yVlOvsCxcy z;~cej+{6G8q;sygS9MyzGH{&53drza1)R9=;AMc@j=IR>craLUT?z&Vhy6Pk%t3PZ z?_r>YmJ7P3;BdC~LLVi6#Va?M6PlKpu{xXYm0_eRYjOM$M+V|z*W^!50)^Pu*TbPG zpy)}c=-a+ljGqmnDT2F1ZVkgf4ruBGGz~W%2Q*)t`maEfI!oM|pQm(P+#1_3j}DpF zT$_T=_Mu(ynZp-_BPe~FyvSiK94Qsq8#q#K!@RIVc?+h{ zqsiAj1;|`R0!*^z*)Vkg_cjsrpr9o@1u14{>>MP2iCkYL;Kq=I^TWOQ0vf@@no}%r z3lPCXu<#ae+H(3=HyA(-EnTc|Mm^PwPHcw#1Sy=mM+`~w%w@la_&d|gU-4_PSjxi! zo@s$=)FzhYf?HJEU+B?J7?1I5#=$zZUFeVGH~I|ES7~L3JZ*4IQ&D0+a|X0_3tM(q zGCot2yXrMhu$qc%USTzMQNqnU5d?Ol`3Oa^^crdIUtx8pZ+Ee+k63IK%&j5lW#ZIx!cQdPzvJCUK z$s$t*>u0x_XP+W7tknz}FZwH)8n_@C5Rrib@ zSxdB<4GO6pB`Oso$;iz1iwysfaf#gjy1{DVDRL+seYHGvZswlsbnTuqsqf-Q{`vG~ zt6#RfZ?sSjX-4o3;-Pa(P%8(gb~B0)O?#odoxp^_^HAM{gKwx6Gg1*0^Km{~q=oN8 z6U}_aYo2_V;cB5q8jL^3xq7bb3tMB)^6o_sBEhaeY2yv^bF9wTYSN5ZnX!lY3Mg(r zRX~v?pg2;z6l+%c9+)8h5ySD12;R}Vf=7{y#b557_9TME_#b|@{*ZzU9M&5} z92J}OF~YW^GgHyNI9IHspPV%MAy@mc>aj6laJ}G2 z^QVuc7Ku4kJvtaktO=gMAWY;3kD?|22QQ56fm4#xe8|<pDmxT{wccPiLz_BJ(%< z@W{H0)kIU36bIqE8n>XoOYRWTC? zG>)fBMT44>aUeEG*5#Ne?RD4}jWE#dt9uG1%sR+0ZJTILY?Wc`Hn=Y#b*6!?K*xL0 z6y3Oh>swOlsDl#2M?#%`E<=%906_oW^Xw@;E0~F$0FUue9%6>d8%r)NuGLS=I7A2= ziT~&$5IJU;772y{z1}6YY*k!wP((O4!pu}uGtFpZONbtjzHnM~6onLqgK4|L>nJp@ zB5L;66PqgC0}u(tk=6L`}KR~w~Rof#LX<99(r37~4zVKw(4v{~O z^cmk0TC^Y@Bh6e0iLe5cDg~%TjBZtuxgNyGXT0;j8>F!dankY&)D$EFzmegEWHz|) z9A8#j2Y=&#v1E24Dj4VkXA}U{B7&UR>juR?Iowo5@744Bhnv!nNCpSEl|zU7`Nnt& zNkAu<7g?SL2Qx`L-xum`YJjU^vS<4 ztIAzxAK(kc(}SP6`*T}}JH2jD`Ix-;3DP5T)^B@3CnWNy_5@cN8`W*huMEqk>~gBx zd%v2!U)9+-l{w-rb~msuLo0HPL9hoU740It$q@}E)!`Pn*i>;uDv+b3k{X>pxgTsJ zA>y21TI>$~bn_p*43w%nq;hfEXbju|(cdgQ2$$R3rW@9wfb<`Q2!Vc$ZWva_xf?iC zk`^3f-u-~E$cEHl{&loeV_0M9%5i;<9KUv|9Bq=@-y^+LJ%>`mo+gbEm%_-T(|UER zQ}Ul^C#Y@sbPL`H>+B{>}C{pt(;7oirXGUR+tg!Go+ zwwJ|btdgAIEk!XXa@d#95u}kh3U`U|SdE)sTB30+qRPZw?BV1qRL;L--i0DiW`+F2 z43!Z=oNeYgC3&NnrTU=6wW{0*ima3(=}+zhhR$AD$4uOqV7IJsWQ!5@khBI;d0(+I)E+2IGJ=~ zxApM}J_;4by}76Q#=OEsp`?sT>5L?BHEW2F#OniYB9!Q^w?qxo5JC$P6cMt=+AQEihdkUJ z@Z)Xf7`LguAc^#ftAd*GSe2M0Gmyzx5u5Or{Z%qzQ=AI}4+UQK8F!%cxH6sCUh&-e zzGx*JxccBLU#QN8?nEn9-1QNWH?+DcpoC!HCit|ogPBzqo~u3D7@uYSZJ1&VP9x#2 z%a&YAjybHLX)w5PC~{+cX4R3xz#$Yc=haV3P#EeQA0L(sj3No6 zNeGUJ517zMR|ZR1HZE@1SZ|wH68N&SvCihMy725^A9rG^J${l8%*DNGO4X8L3^Tvt z(MKpK@eKI&4=k%JIr;X~{i$t7K^I|T@}F`|yUY?ga>zp3D29C=V~}60F%NiAO4NMD zUT9oUizw>@p4^EIk!dP`$ooLVWBk}&^qD*KO?vQD<4R@N?7%wQoZkDEB1??fC z-};TOyslk=aU6=u49~CZSeZmWa(ZpMd;+-0hwM_`?mv1=C$S zmiVOWdc^)HkVq>x-t}`n#)dAE^$R22U1A>)9OUNi@B~Nb7-3y@ERDs7xs9m1ppjd4 zklh&myY220|F7%|Z0@h^f9uj`XV zeano?|7yp`>`f7Lh9ArqX{;Ju#M|jHuDKh_PCqv4m*<~hJg7R#8a+p3%?JS@Gq9tC z6EZdsd&oba(Y@mXcj#N&{5-zn2J8LS>ZE7D?zqD*dBv!x5tn9<(Zv0eK3B`aVY=EU z1U-Bb2?vLrXV&^{jpeSEMW@rGfQ{+Xo*`m&D|hrceFHYKaW5R`V}1&n{I<2^Tu7K8 zn9x4~T0aqSz z7q)d%wlmFcq;8&{+dW)+rYyILjT!|SpQ~v>na6mO1~Bik8t~(pM;p{R!JTLl;Gy`q z+ilp&X^^4#ZBb9S%uXmKr^mHx{w{j%jm%8=q8~cliA~J6-`3{F244%`46XGOK1b%t z9l12!*x(wvU~r{2+5-@xiD(#fat)mSWq2sFFfg&9#Qc@9Z7>+3pXu9&LXoGQ(=`My=3r~I%&YxBI(4mvK zAJgQF3G~8~f0LyyAc*_K(FC%?9|ERcWjFog@YG3oEVhK=S7j*}E#=s%a9zdT$c$`F zk1885ci5gUB^H|(oh^M>>$U=U5}?VqT2KLbb{YXGgT=nuJ%Rp*J^72Nib&^f#4L7E z%lCgk$~{!U5Fi8Q!^^D5NN&8xVtx6;JO$)Fkcm{z{j=O}%>E|VC)_c#M`$O`x5+D& zuqs;1Ik4#PR`=MJP-})4!Lc3rj8^kV4$zc`dr}Yc<$;@zYG(7$?HefrFVx5GMjs+e zj58*Reh!%`9H;1)j3yv42Q)b66=sNjxBA9ng{Fc@HITqR?qnIc*Ee<>)kv_v!+SwCE5E%&k4#?6Dp6+S)N@U$E#FcVuzG8~I^^wGz;= zz+*K=^aSg!ugCD$u8iB*6Np*R$GAMQl1jI`$G%}~-tmcNEHrVWd&hh30o%&Lmp`FKC&{+ZQFulPw)$=>5wNJT;Vl7_Jm)a$KYFPxsWjpSf#~= z1Cy@NZu_%4q0m(-p(qP@H}HS0jRpu^Sa)_&RS zMybnO%S3B;*IFp>%6Y(BromnCr5=e0MjD+(+oSs(#y&mzK?YW|=7k()Lm!Q15tr6) z-q9AW870{~ksH&!#%sQ4TZSk6q#!XrGL1?MF^h*6g@>V1nN`1Qt#*%uM$jV9;eW`&H;_(Nl3NM{*Q_ zhW}^;quH+d9n=3ill_0iGF;w(px^unF~O}%*3Aj`2y_&H+22bWHo=Lq?bhv~+lSvn zDI3W>{BCC=YT6({6-Fz6Ic{q;tpMBIa4NISvV)1zrb!f8f1=gsAi(MJroUjQvg+^4 z*Ye9zkQv8#9Li!Jn8b!~txT=yqC1@2AEXIA`VsNGYF0N=M34O9*_HC6Fh?Rl*a$*1 zWVt#0MCtBBQWjQHyXU{@fT2hlXH1P7?32h6J1V#@^!xx1-r5hf8dDy)MAp~}ttm6^ zRrteH!#!*uOfPmlWnn8(Hg&IUi`VsfaF=2O<)VdC>-t!`=P%O5M+I5YaSK{|jrTp_hmt5AC~fG4&W($JkcG1P1~KdUDGBs-o_-m#p2tR8tqZqQ={GSzZq>zoS?lfymlUv4!a4BWle#u zO~LR&0ou)#BEnG2(tg>Yi8gz5y&~nAyld#wuMzHj`FnoD=3E zt1&=Ut33i-%EtUXGK37?$dA&Kd1LR9jt13hMAdQU6<*xGGkwKVsLX zhIt2qYMr_^2luH(`b+YMmabT!g>K-<@UHNLg;z;mx+1nnRh6O*@!uTAnB3uft21%< zJs<%OXOXvJV=FYwBjdgg0<&7En`@iVq)m$wdIgYPJK zEuxUXYr&u{SQ)TQw!`t?BwK=tT`4uY4r}-PR=P5KWfuuM=z#}iOAcI+q+jJLxz(qe zcs)L}8&QAg1W~`L=|a?3kal}$OUMFSpGCPo1y|KzK9C0)j77Qr?Oc*F{p*=7-|6p6A3mXI#nR{7eKSgE#p5vy6fh}DgB*UiF&nTIg`$HYoi+nZSF z7O@gytsCWaLaSz_XjNR%s%DEb3x3b!D=PKmknYL95{As|Sl%`H(Ph@;CrQ-Pf>&7m zupsZ-35mkhU#&a#5sy~8OemDBP)=P*3WWt1d^=RiZPZj*WGZm8S`M!YKQEMHl{|El zs(*_?LZnVoOJU40OF=l;*o{;t=o-702cb}*8&&|FcjIG2ia+J(sy0TI94TK3Dp_JS zrLx9@Uw|1YUtyjk=2T1aOR@FPYVT*@Zd;5NiU*jC3Vrg z*Mobc#$>}Yr0}s;uDYQq+<1ivyu(ItE*q|5+4K)w_ls ze&9bRyx3lwSlnNfc3k(EVcVh`wQ@|?p@asPG_Ph_rNi`U+O305s{^= zp47mEl>ekPZNq=NpiwElt0#Th5PVC+U~tiT$b2)3 z_(ILo>Zv~v@5>>fwC+I8C1WSkJlgeDrg{G9?A!^U-Qc7*c4@09>g8fyz)*IJV~3Ul;4q0 z;xx3QN2?Vq7WS_AfHtDy>&A7TyOqP^^v7G}@79R+e+?UU< ze@QToGK@vmh_T3eWh_#RJ%A{;gMU!h>Jo8txTPakcK_1unY2qA*o-O>nhd;@VWL{; z9-|oY_Zp=4e|in_7vC9VhBe5LnPH7`RC1J!d%H)eZ2v36-yP@Q?Ei9{AAXS>XG8Zm zD@G>Axw6MNGh{X~uf{uSy*ZVZLme5%8E4Xm?~);`wL?wm0lAprTCCNdQZAs}LQ32n zzv0U>&xdA+(~g+Xx-1pAs0oV2PYfIL%gaQ)6BwhKL|v80y}(LA)W?MCC1j;}iDDuE z?y`BXAPcDy$KVZS>`6d0Wyn)~!0p*fWNQ*q)paZK9qLA7iNl?3PF9~4?(UN!>2i_p zMza7yD7P)67;43*MGWiZ0_9GrBSW4J%h#@8%;L3TZlqNr&Y=F=XzmjPH+GcPZ!w2p zK|(dvrP?Z%u`CQr>!#MG((QHi`|PfM4>O-+9rozA@)?OQ>Cy8Vl|l9VogO{^DKshB zbA;gkSvAoR?1DJtg=!Htdx1AJZ+^6w^A09^0<}KSEU$LvV2_G4_S!^=S#PZ zIxDfxN_@vk6n24}cUp<9l9)tr(fC$&!=WF!)?t$er{koR{i2m}zmRBOlV7dXsE?5K?a|H0J73yIPC*aN7Ob#2;V*#bcQ z0cM`mkiaPYYC6*y{PfrjK+UIEOC%e>NmZicJ6~oK13~r}Z<^I0nI4&5%p-`9hy)#Y zRz{}JGjCEKjIHEk7rM%ND+D(pGru;c%d26C{L_2@Ng&U^0rmpb}F9{-U>6++kRFOZ};;iO|{9ZVXWZQ9A{PklsunkJ`L%bMkn|+RK7I+#7~W!<%U=&N}?4pbp0U<;H8qmXX`x1IDpmT@N=v^ovB3`P57B zAu70T-=aipGnpkQgN@J-Q^AvV@yHz88n}$xwnT=$2zJ$topqF0iw*5ShkuyowzZoT zC^E!eJR(CvkaByA?e*8$@=q|YnkSIiEoM3cThiWnhkQ62BIZ}F{nHTd33=aWo;etH z$=IS|P0(Ax1?wVEwCEZ1lJbbdB2{!zTo8;MZt<+uF@Q@(Z=lsd;6!_Mi8O+}%Zl5% zMrUcBx`El7mh>4DZmHa<7i}`X#UfuffUg034d5HOp(|{sb)P3)fVTJanRc$xX+LsN zhdKHW5G1$`HL$Do$Wv*98JTNcfG9+ZVW4LqDQUD~bD!7*1VS@9z?T^Vt8UEhK%yBw zzhzoxT3Y}A$KJa@MOF2GkiXuvBMj|*PV1t5^S1e7Ci=f=B$SDl@y&owAEhS)rMNctJA-&BRLzYBoA$m}RIX|Ic^t@0l5lE`ZV|5OCsdv@GEoxm}Mr-Xz4ke+;EU2}q z{eYzeYU@d^)0yZ}I8CenEdoTP6K(YUXsyx7KS%GR}LiT#0C ziGZzfmP^P2dYnr>#2QqH1|7XKu@0BciJ=ytSl?i#z0LUe2eLC#vUX(K>M0$qtKPt3 z70W4$7p<)jPc^({*=X!S3i$(>?ar~M%qetBd?>-7H>yuuxKM zDD(0ln^;R%vx#a1>*H_{bAPL_Ukq*JbJr0i%XZqFCf_1rgBlucbh7!;Fj_w*%x(Rj z%xx0p+>pKHS1OYCIiUSZ%@rV{AdYLaRmH z=&1f>twC+(aaIGWd9?fr2P0>84{kL3VlVH_e{BUFXf@Eec#JqUn@X;^Yabm5JrmYcn8;Nu zdoT#xI514$mVu$bxUww+g9ukY&=#-#=ti8!Rjg$}t6GmQooV~7K(?WkEU`F^BzR}+ z@2K7q-Awd(rjQTe70@Q?WR)+t+M340abcBcH0A}Ia{D&ZaJchGn zr<{uQfV>UM6D+h0J{CKrZ^6P-F`q9Ejq>YS>e;mv{j;fVv9y%&0?zY=`%B)W;a@!# zpP+k!8*M300>RRTtbL(zmM!~eg8%#+)TX8eTdd`4%u1YuzY|xF3P$Iu&Hp`Yv&5#R zAy{UO`Z6y3qWKnt51muwnR-+3A?;^ArSAC7AT2zC4~5@T(BKROJxAIdDgzuJ{sgY${@3(L1k%OPrPe>eA!v+RduZC?YXc>a_9@32uZ zz1$Rjd2ydukCWI_Uf?f|Lcl3o2+@hrA~xS+-IT{kq~Mb7PQq<06zTtSEOv2)-&l;5 zjNht{;uT@wk<--s#Igodh)2w~#NoIF1nNHo$Q1rNEe6oWz;Mg?G)iXdLej?uUo?eZ zTpS=Amx*N*j++rQ_z!t(_)qM)l=ovfmUfx26(ks^VxyOf&pF|dE(?we|Ea(~t_;r< z`BXepIvA54H)5$RX+w)S&SE=n20{aAQZ@&Vzf3-U)hHa%mV6_O{&_wYXtKyWPTwXJ zc^=D2L>aKiGJ*Uf^1caMOsJgE7L$IL@ornbg9@VYK01e5UH>inLNQbn8ZpR7UHxHH z9`Se3+|3$~;{8Q;6bV%A{)=e*1VsQZ9!%O(Rf9e&+M8Gte%$;GS@nv>0_Kx=-^;sd zQ!rjPXpvwy{MA?-YEam16$*npwtb?H6C}#RMVsD9&yV!*VZIW4jKqqsgB5y2m)?Yd z<-#&yz4L_9={>P@AR*yA-Z^dX&AJ}dgcwaYKN~u99s=f6yzV~DRSi~3(C9H%d9~^> zsaLRMSG1=dFOO={Sk|)2-PTw88BX`T;GzAjx}>QicCvQUYuDprT{We+Gp=$&C+$01 zUlfcQv6me;drF$=6Qv%9POq5msW0q{QWmpH6N7bxM`@`f=1(ZeKfHt_FWHfZxL4cG zTL)jcAr2aMsv;fl)md>k-D&mhFV>)qS!{q< zy9M*-vB4-^)jB+H-I7hw!F!A)SG7x)VwU=eMyJ%f=4MMnSgyI-?j!IJ(Y)qqOCaWD zizVn_8&A;D7EYkCjUw>2g%J4I1`+t$`V#or0thQ4(Df(L9*0wKX7ed+Ggx zayv?Wf8s#&bZriUuCVdsuhOhx-pN9zPdy40d>eHzDcJddja z*E_gA#PunzEx2lN?ZwrAs}a{JT<388gzHyalX1w&TwGbW9>lc@*IHb!nLspVMSW`EGfq$=cC-2{v^QxE;Z=jCToQGNexSinEa}5(8h9UA%llHrjy0F2 z)zjMOb2Df=V{K~qwI%pOtoxN{O;1nJKsTXeSq_ynyWxYpFz|O9gHSai&rze>Fz;{r zkO}BR8qkL{p%0mWs&WwZz(gNXV@jEu6p!vf*$i|Mt>`G=`OO#>RJdwETCFNb6J7TwG3OFSsEP`|Qu076UsN;)!$rg!$E8rt1K^exeB z*lWF>ObOZ2u6wzQx|jH9G>pFrI65T7+yb&ljmvOSP)`yK=z%jA8drLDdI?1oCJfDu<@dee(ZvIswF0m;*8y{ zN_eaGN&WxqGNzTWZW>LA81(R?ku7GxU2#(EV^$=vSPg-8kI4hWW!R4sUT5!*hwoi- z`+w?-@958IM!ABfS!tb&y+6J;i4yET^~dx7hjhRKN)cM>nOoO?BD&9z8}Vc zqb&!J0GJX$>f$Ay2E8B>=&dikFC$i%)UJ#E=RA}UFeH}L z0zGvUGvs0ma`Eakow|o~ZY?FEJq2ygI=TOj^<}tYtH3+h>D`Ma1IAKN6sfI(6(v+4 z6Rd4F!^af^tC3WskqB1}XjMY13R)OlhejA$iqO~q4X{u;pRAJ1QwVThA@%jvC4Y$T z_hyLgO>o@#Ti@95i<*#5p*cg#{}GrV>m3;FAGrr>HrCW&_Mt?KaYCQkWj(E%QHgLD zsf6-6bppu<+6pvq(rGk?=|}^;-ATc8^pK1z?+d|*H*M!D^r5nH;69{AUuv#(w3g6) z7xaOe7Kd183P1c1{&ssmtj930jB%G!G*XcBnYc%Luv0B2La4<}vpsG8-wj0oV5-5k z!=BFeF%xyKNF1HWxyNj#!zUu%sJhX%?F@Wd?b+8@H3K)#W65vuc5KQ{K{bT49)B}= zpoO~Th;9$gYVM6D2Hfwu)NA!aI4E@WT(|I35AQ%a*suxk3TA?T4}dXz#lCyQS-4f8 z#TFBfYUuCYfQ2lyN|h=| z*d#3v+!ea|ab=L7B#7n(k)`q^zBzlxMYg9Xzo?i+^;9&Go ztZAk!PQyB)X)VHlp@%s8RFDYBE!L_Odq9>WKT`&PEgt_7r~c4vMMrBWT(mV?pGUjB zoP59vOH?Lw_cXLHVp1oc5Bxy$MEK)0;HY=mTizeOmgp7h7~wk&_ndzke;7(w9Ny>t zi%91j>Qu=g5bQ0#?}8W;aot-U3-700qEGsNW zl4S(Gx3yfFvQ|1Q-j7`Dw55{{ilq^ed9u@k0@l9V>oJ;x5|33|Sp2?6gbGu4U92f^D^6_)`%^w>jrN`VktwUi3PRQ0` zOdp+~q8>ER?-5W0W zYDgJ3U`Neo)thK=FSyQf_|R$Ns>a~M#sLksAu+3>JdKvF##Q%rz+%4}W8JkYKZ7yD znD4ZY(%MSnm_}{Mo4C<_W{VBK7>$t)ZPb2r-$n4?OZC`d`7tUC5jKyjY3g8U@U2IF ziVfZ!iz6eB#D#xd(3`wB4n3M~#NiFv&uR%f7|Xhz_du+j_uWRKouYJ63PzI!oGGC% zhR?u(bfzva2wNJI@aZc8^1D%@xR-VR{a8G9U}G)+fStCRaBH>Aqg;+^JE!xuq-C%@3%??jg*Gyxa6g%OB=Is#Sc&eEy}* zcqMsQpMc|P_kHHQDD&6|jio`Do~0(Jw{@>Dh7&JqaD!?QWkuanu7|VM+wchBOkczX zDWMvWVW5c^Z)+k%mPwx0b*KWisW>oO_ww!YYjLTz()4{dR=Awxrpl{EHg zs7L5@umLsd#t=gkzWGF-L`R!5zlW;Z@(Ac+e5Ypb`JFU)zX=Zos%0Fs3xesDHlNnG zVLy`;D8*EkG-4Zw#x?=ywH%vPXh+KSO(S83K1V*ZPV9c7FSobV(Ko^d;Db?iTQs!M z2@PB`TuJnbf^B-ptWa(f)3uMFg<2Bv%ujF+#lxLC;X#eKn$STzG%DSseHgv3>wTuc zE_AKw+UX%(#r=p7U!ZoyW71CjW$n4k>DpO8rE5p*)c$LP&^_*d2h9AUofEiC+PVF% zHDsreO15@-;PYVE#$_7d)fe)(0iA*Xu?@}G$OWHs>fl!3?PTN8Q!>88&{^~^5pLN! zJxF$NJ}Sy1yx9ATQ9IiY;|D?LO$OCK@C9rl)%w)J9I%r`;Ot|+XlL)mh2C^f6Vj#6 zGeSSH)4fs-Jz=JfWDBH>%%1@N3hsm)!ix#KlSSf1czzz9>pbzTQ~#MS3D3H1hsnU_ zrHON}xdYc>T<|p)NsjE9Sc}18r}1J~@Glh>=6ID0Osq!`5sdrby)N)^5foj>n?UHd zAPAS>;2zvJ;GVA2xGq31FhK@8BtxKGc96}%jL!eZrzoU}-y`@4pzij4;BX5M|9Bs1 ze@-prP0+)jL(o2U9}%0qAJ<99C{i`JAa_IxPpCmSJ;#5KK-~p6=^sMQBRe5Z1&1+E za4lpKB8gn{+xbfvnMf}$yal1?^maKlf4xpYW*v%ljm@Nv2avQP{D1_QM#jL6M98-D%?I>_6!XcT7r? zAZ#wU?6$M{n?2-L-Oi)O17C$%xE45z>^%B!&fPoD+x*`m^OtO$Xet$%xclp%m*?!= zQB#224UO-~=H=NnJFn~doF}u_bs_%-?l^tv-9B}E6EY7sNU$cM`?@eT8>C@V-Hy}E zoA$whPWzsemOX`?J?kBL`03|45AU=k9^P?!&nB;WVK#C5&a3w4Tn>-@8-@NHuO>oU zV*SqR_w9v#V3Y7s7t$(yzO?gl;vV?fu;cXkcLP5W9-e?7-oKXJ&f|er9%`&LSjv4*-%WgOWF@HUyL1X12s6BrzYJvV7qbk#A-T{2PdV_ zX9sMzq?fcB1jB38rc1~61FSXNBt{Rk(KQ%6HEvdt(5e}*-8>{h(IZSZM<@nBCm7vC zUTS0Z+H{)~T!%M(gO|$wzZ9`HZE^M2(;MV=Ucsv5=^8h#N7wf8NZ}jlN z@{0B)O>nUjqNdrSorY&kvpY0uE_lI^_A|6W_ZSD%K*ysRPXdiKO-WO=5tHFKtcTh; z90t+v;xUM$q6g#V?|A};pis(L4p^J8A%JFRqcqm5@zjTV5JR9HX#t(V=HIe>Qa5_U zIAgl^Has1SutaIf9z+v0N<*vH&?t27H)9?v4zo^ZTG2twD0@EftEXaW^7F3PuI;|s zxD6jU!^z$F!r8WH?Ol7oR!`KUNNDfc8(dT98-rGKhgh1c-BG)5WHlzh@%@bLI1{_A zx_GQcDg1@YE!)iKkK=sY=9B1;C!i&ZPUzud@cMu8eB4fG za^pa^TiCljR6R$qvUkx$-%uT*Y%WBOcTvM#KNO?H&v_--Xc7Ru!eB&8^Ve zu%fCC_l2FpuNLaB6c^*(&D_Hp30oq|VqnsGD>;F}xE0O?(mnOSivvUt{Jl2+E0~_d zr!Ws#<4=<82x5HZD0jOLICr~uMK^?D^O@NXg9u(zMX1%WAV(UP#0H4dkufW8)RbY2 zwanNXSI|mhH+lZJ0deSBkLvw6qIIKiIAw&;=+#jkz#wd1JW@uh+p*UTy}`|h4nDwh z0>_Y;%Dlci06N*D*8VHoh#QS%%D$cT za0hxoRx1DrbES5ic@oA^YcY#KF9v)$al5gkPE*{BDl;0Vh}MG-*NwW?s$c$U5;>SC)WUnS>z%F>-VA+eX1X9fdFKM6p zCVH=kK%vfq0n+Jv)9^YN4 zUA~uI<=9T(`>oC0_gmxg)8f5Ua=~EkV_bfr`VqYG{fs5o423tr(EyttEGUWxnjxDn z^o+6WF#cX2jTiQS6Vbl4*vPD1Q9*AqBxSP>^*3HJcm?==i^`aSTy5Elg!38bDFQ1* z4Fla(kU$FJc@1KZQL^el3Zr>I_%8F!M*QiJ;SCX_SJ>6)d&=gc-E8Yj zA+mKMiFiLN9!7C2mm5W+6+w14Yvqkcp+8J&ZN4R68EhSve?{>nYsC>?Vws>~9(|?O zV7?V4^&O#DnuKx&F7$P0rJ`AAuGN zGf?hn1#ben2P+1`G3=Sf?c)PWal4t`?qw#7FUn7=V6el0{%{wQ6@Q71ab<>j>sXB1 z(1?lNI`dUA3eyn@b_97Iu0)G9VhN75@Q!anh=sc)XZl*MUa8TTyW#xCrCL!5TNl8= zk~6>CW{G2{3@ff;VWoESIqw1=Yn%{Ww~aMi+3kj}96{TsKr2tG4;8W-Ha%qSIGzkL z--50nIhCSdI>O#CUdx{=(kpwlKjqG|mV!7VxSGqrodMpi#|T)f|DF9C+A#5{>* zhdA+MrfGG?9y;j+mb63ikS<;`su6t}=ZyIo-?Fh>5=pkHxG$B6t61h@?G3jr@$+aH z#z56KB{UriixDG(7(6mro6*OBZs8JCrG}D+9&sQRE+eAGk0;Eb}qhlMv0$9x_Yblc;pTD?GS^G3!FOz3OE4}ToOF}(9Weqgc?KV{RgZK~_jpayh-`l{jS!=X zK%&|Uk$GSd?JF#@zJXNN2JO-Y$$6m+N~m_qX*{FkM>KDH547N@o01(G@!bO1+!bx? zP1S8}ZTMUaH-Xc&!@YbE^@476r@`G~pI=PckUIPg75zq}4-cVINMx?UU7k!PWLiol z6P~GP7l<~2sjNtE!dc(qn;lppkA(+D%cYSAx1p0-U3Rx8=0SC32^tB0=uYV9__`R~ zwkIJkF?qT{%bm3_5=C0>qDv!9VI+gJJfY>urqF*TEe*6ZtW_?xbjXglJTU6n5xvac z>qi>V7tq~`4Lx>&H~NDt(6kfypjXHO&vpV|k80L;hdx{koKw<4{p_ zp@df@v3|NM+db_>b7i}RNQ|z;zqFky3NcYq`EJJ_bix-ohbF zUWb*K^ll{&85Eo@4gJl{lwlI^5bku z26ik)J*EP1rcs$uSjO?OWDXg_$@&bPWqcAF8!y~3S-P9b5)!OGB4^QjP`u7^0Grs{ z+WdVmI6=-^jG9VHE$!x!9qFEu5%=D7Pw9($AG&v!_r7%RhI9I$?`Lr8~@D;ULp;UnL0zwJ(|Jgumd$|%U8cQEZ_4;aa-Gfx&haH51>{JEsp54 z13s7ouhbaKdh4a@sJr{^@@lPxCe7)TG;gz>fF`}qY#1cjW~JkmELebwCJn_&L}#AU zH;!I`9D(z#MYEeicK&5($n^mwB*JoY86A7=6qjNgixxHxBg1k+baxHiVzi$pVlicP z06uL^$64HBN$`scKbY@hF=;&Z#)UWJcgDvHU?3 zx#`QtB*dWks2y3o$rnX9{mzL2c*WPY4@SHCRu@;+Z6JLeLK9Cb2c{1UhzqWd*N#7w z7H8=lh;|#+ZXYV>o{ko@(h|S=MFpEujM#At-NKeQ3+y7j(TU!nLrg{dD8Vc>Sdsz`8k%?%@@dS4Xd^bfXqWGi#4D z3h(k7FwCF3{ZNytjT6fb4rnY4(SErXs}4yP>+uz~Fl%G>;8;2g8*0tIs@bD(rXG*_ z5FHRUpth}UKs~HE%%i(RNr3LvRwaCUJUUs%;xNVh1J>!GO4kG=dV#Fn&b^7~(@>1ghjUcSn0jAs|DByPiMT5Qhk zSa=b=)>t&l3;WVMnpVpFq`dGFhWl%Arcz^DZ8;3No9{4L@L3qRa4*U~c8W!J(cqKb z7Fzcd-9=Xzfm`hIi|!JDPIyU;9%t?qejQuVgw^cA6Z4Cz{hJ}iQusZE7a{IwDxf@llZ!}DCwbsaXoNV-81M;GdL>_26>p#-Joo&+&`{gw zq^-A_p;In|_+FW6UK&39wQ3meiwlShPNGFsLnk%rG0l*(^9ELr$K+XNWT2`}vUZ1Y zaw!ybB3u$SM($Z2hUS%ANJRS6$crEnmc`?U$oSCWhY}TY7Ty-{10xObki`To4@)V? zZo(H>abHpt;8{3H=oabqUeENTbB1B0NZ*eLwG)W+2PqtA{f&-pvcQ!x6CzHICTgrr zq*<02Wc_3~2FG^C=utRuB#Rz_4WiQv9r3orn@kpO8U2eURCi7ye2iI&EJ}Bl zxLGvTWdms3jl@pw6+eWn@xGhTMOCZ|VCgRgaB#so3JW_N<>GKmqAD5EP9jz&$gt?z z3y|3KmO&tY&v6bUhBk$6b30wpO_GATb^;`wJ+_m-a(2iPpCB#V(N0@*%%tF97nkBg zrT!~-IlF`GB(sLuZ+TcBWD#+eqGKkVUaU@u@769S2?uMbL;io2TGt&fDX8}ucQGDg zyu#S6hcp+&IF>PyF`u!N@fF4@#{G<^8GmKe(4nDF-5G~7Mlw!jOlN$UaV_Ik#=VS< zjOQ3z7;iA@dP@KLGmc`6W}M2H!T2!a6O1o0Rx)m6tY-X{@igNV#tw9dB~$=of5zdA zV;JKY?_)GGu3~(XaVz6~#wNyJ8GUY+{`F=Y#%N%i$~d1fhjAI>Dn=1#{I!HD#ncB? zQCa9FxZVX-^ZyBVd@S8bognSkCrNZQfA)wpH~k(dZ~a7aS97lX7HFj6s>79p)p~aW zr{bE1YdS891Eq`}S23<4)NWLlk{t}cE8TFBoUTOl=c~VQQ>X%rR>mlEltoI4k_TL< zPb$GncgizMJd9H@cXwa*=Ow;ngm@j=B1LzHyN^GFQWM z!bRTX!q2*xx4Q5jU3d#~D#N6Hb&73y*d&Z*t*rEm+g1MUh8<=;N z&MDQ*)$lYiSJMZzt*Dm+*?bW5fy@(JTUFz?C!xw`LK zzq!^=uI1acK5(s{T=Rbt`;U2Vq2|q-pInfZswST@Z(d4jT1pl$FS#HkIg5>f7noDQ zRgHP`%o&+vfHmgLOHVE^JM+b9DdyaKRR`{7sbt=~wB-4js{aK`GSkiTZe@?)kKnht z@R4?&XU-oz3gO95D=_D0rcfgYYTmrUoaC&`1vzQ5rJ+hGV)~r4CBhKhr>144nbXjE zQIyC?MHxF*L0d*qBB0R^*S&M4$=tlO{A7fmSP0BfehNk=4GFxArHnCf@=h>KwOcqG!_ov6D!IxT=(Hh zTAXf9o}ZOA7<_X}RgDF(oN07Xp2OIoX_W@l4Cm z|3gq@x;xA{(n{|LQj$adKL#l&B{y&Be@ZlpcVVhopPyzf%+C>zDJjOJBu$+@JywJQ za`{~CxuQM)dxt6itNB_!kTjJlNx9iMnR?1Y1(}p9k{pR7ON#SDrSQ4R@15JuL{fHg zPV$1Z)PG7;S95=xsN|m`UH_7Dj?`~wPECK8baplk^QlX7lCx1XGIKJ`nW#A)NXyr! z=jQ9xe4)yd&1prMW~c1$Yzpq!Gtd9%o!#7j6*!9bU()d(+;3Oj%^CS=$*E4oUQz2a zegFP?IPc&ox$}~jBxiCpYS$kdLA4m_%ju|B^wDVrDfyXsj_N_tkEd!QDl565z)7l4 zHtXf9#9404&re>;>B?42nJDV{DH%(hEDIC*sNC#4s-g8$<}bt>L?4HG%*o%R|O;Tny^P#UP=(IZWgMOqUU!Y4{Usgo>xc77s0_9sGarHLm&Hpq?{ zIg#-1(=-)+roeqlQA*pthSb?N3@0f;3ZiApSTj~*eQSklv{(x=Cj1GDovm!Z+bYPdo_9Qr0MM{XMXmlP!XGN(|! zS%izyJV&&~RM#Y`eVz8YisGv8=4t@^@ATBZm#cl1Q~7hYM`JB?dHoGSOX&X-lAmN> z^_$F-BEA1cQJ6Hi2uYDU#8vx*#7T9OVCO{1Jn5_ct4Q`pUv&q!cKdX%D%D>VN#E5! z)vPO$IW=twT{)R|mJ@eX6h^YA#?6_l`p!u9iIY<8xN<3V$W^YIb469IqAGWFUzPup zzH09O;eSqnAk+wjsAaMgS;qwB6lP^PsvF9;)oS0LTQ6kZ~s#l?#BUD%cD1QYX7LMo;1{`sUi&&B`P;R2JPE~ z{M?kZ0#x{>b?wK?*SPTM*QGq+4~fMrSMl*g(Z`5da(ux?PH z`i0V#$|R*ZJySx`BcwMK$tW+WGIE#f64Lk`xl7A&NRP&!ZsLpd`{5$}+i?*$GLK@O z037%L*Psnz%_GX^n+ zGKMi47)^|cjCqX3j2gyr#tOy_jMa>dj8;bFSsA`=jJ+9y7!8aWjKz%83UieB+#F>( z`kAvZwmLndP??aQsZ2{YE0CooqbmlvKzbxFS3m@2bL5?TP_UOT1fZ~QHxG9T^O;RV z`J0e7Ux`o7SB!c2z@^GW_&>2QOEDHMP^P8jDN``uJ2`i;5}lSJ9EKf?Z7wBy`Qiq; zu%D6vgd3Su|G|wkG7NV_#}RH77Vv-v+%%_U&rhTJkQCg<)N?-h;jB&m*|{-ZQ4(;G zijNa#cW~)Wf!&03k9xEYJvamZ$bB01aM&e%>7PSS`b#Dq zdSq@QLO|i30GH=MjR+;F1V*}#$8QAEG!_3#ox(#p^rO)gGDAP=WlJ40DNUOc z6UHdA@f(9@XwOy%H;r%2Ks=<{z~#7_v7WJsaUs$(1gTB!2~rP;k*p{@52+U6xr8W_ zL_5PFpN@PgRry+3uh_@mnCqUC`bmt{jJkDFUmd4QU|z23GwPma`;66$x))fVQ61+i zVqVVJ$k=|I?XTm1l&k4!(})d?9;l;u>KNcZ*;hxvRC_e~cN?b-!9Us0Li*Au35}LK zAi^TYn4I0a^?!?dly_;&jYhjD#?C%CS}p$+UO8@+0{fKfXtaq&hmw&$X?)c&eyXOy zKe#=l5 zXpCANm2Cf{9C4*=mWJ_8N{ja6l4&_@ce$B%e``mM(g{{+GI?*OwWyrij9VGp{UPC;fTlR0p|V2=N1|NgZ?bhzcuFNCFEvh zrYuE-IpEXN^0PB@M5yA^%o(}V{1Ijeoy||n$Xybhwm2<|CbOb5lNaRV7GNslFYd~e z!1QT+F6J=FmH(&I6wsqJ(f${NF@E`LumAVVgxvB!{Z?Sa!1dSO{Bn#ns6RL5zxz46 z@TlVWxnqpszxk>5^=dNwUyH@dA1zt&*uPemmaW1Ey`Olp{Hdp(S@Z1L=hi*{!iz7x z{K~5puf6`pn{U1SPUXArt$+W64?o)Q?~gy(_~~b#S8dw-#g;F(ZmX`@Ub|!GuHAKe z_I|bR>-`7n8x9`&=J2;i8jl`3e&V~6r<$5ipE>*exgV_OFW7$k>Eh2VmwvhY>u*vK>HnYQ|G!9o#ijP6T28J1g8oMK*IMv@Lk;M*-2QX>FO^z@5kBhX__@3NcR$s> zbA0~0_&VF5d(Gd}|2Mgj`=NG8H|%@RDNs&PyZa{ zwd3H*^rx@-hi7qPNO!b6ZOyRp^N+a2^nanpputmKg-L_^vmI;7W)AHA&b6Hjvg-;? zWPf1G7a^$GpE~&Ul)V9O{v7yV!`FE~pIX#6^1{pWsKKQuXLD}Ue!b9dOZshn?;P&o z9&=A%>%seHPTXF53=KBjlfx@DQ6ImztXID$4EtAI_T9f@#b96Cvf4F)AH?27p7#3g z*z{NTZA+egV*UC#yZxfQep}FeOT*NUKEFO>^^-|!XdoU_41TM==<((gAI*IC)w?>3 z{&>>drg|Zk6HpuXSXF~ohR}VL=8S=%~_olBM9{&3Ahpwf)Kkej_7hl(Jn09sB zuJwUyCdHreRF;R-UjMbe|I~ya?dO+&={l=xxA7+)ntSKB&-a|&+B@uN<=bwb{A<&a zw;o8o@%c>)lhL9++ojLy1+_EvebB01>2k66 z?22naEx&#}?yccHEAIbx%gX4?hum_m>Rz7NYiZ$|m>F#Tc+=Rz(np`^KJM#9V>XtZ zT=|M-LQH&y-`mIkeCnBjr@H=_5jr?}z;V&?u?_a2QGeS_5F0Hbn5kU zCHMTI{b1IbE}s@F>7NeKA2_)4$V1uV-s&Cy`PV1&UOpCK?Ed-Wz4iC6>w5M``542L z)8p1(&0P{d=-fv>qFI0IjP>3J6aC))YEbgbl_k9w*8IA%aIrOd*nzt$jIVfU_C$8r zTw$#{@WYJiYt0`=yZyZ3Vg0~@!2E{gSF6r`=;hzhFhBf(DRbZ4Khk)5N_Muk-!CI) zem}EoP2h7!Y(Xn*YY#06kGabs<6obg{IBYa8L!RF{J3oFO#|08vB}J-Wef#<6G0U*7#f?>~4VWw!2zub#7J9vKvwe|F=)-YdVi|D7XWD(v{cq^Hi`6|&@N&I>;ne7o;x zecDggGVhlzS+#-VEm!xv{FHCON6Xs09t(-ukXaZUGHk`Hw{<`Ftyc^W?EZd!m33ih zqwf24U)SxNQKp;m+3fmnwq8%X_0cMio-uJAckYSr`M$EV@9FBerLPt532zAb_xaZT z+4sFr7WQCc&!zkG7w;;1b<_7tCwr_p%azw_=f9bgmHW`y7k@Y!e)YB7`AhB|W!Yt& z9egtJ(YFPl|4yYPoW(-ti0k@ERD+dQ8?&t%?d9eI0&SLMo0w_b_T_U(5( z>Ym26zumn4^`7H0JduE@Hy+wL!gJ504L|+Xd~EV-PZk{aKKs(eDGNHi>i_PypMH9! z^V+b2r?$u1_B|Io@7Zx-+AE(trSEn}QNyY64K*kGNBYk?_0g(%-~YD6viFBK?wvZq zBVx)A<7sYD(S7&pRPUI%&Eq$IIDFJ6Z{)u5^|+h|f;RrXq-Iim-N6M-t(6}x2;cL; zCo}7^d|!U~S*6BwZ^Wvt6Mr3jbZWfy`29Qk^}g!8JZ9p;#`lgai)+@r^wx`oO6l0M zJ;-gv@bovjOP*WvQv>{JMz6fSzNp)fkM92C&gX1_{-MEfzMVZ@3mA4G)R5x=XTenIwy4wWz7-TI5ij@O%~R*V^-JMwP9W6_(B-gLkt zU}fHmV@^!9PW|}%$=7>y9PxJi*5)JYeWv_0|M3TZ+_b-+A^jVr#Q(K(g=hBtGGxSx z1(n)CNrtX&V|_~R-`6kX ztvi0+b8OYdTfZ1@jJx`6mX`$wXM3Gq_S17ycO=)&f3NY_%99sHRwZY&toibVeO(tG zu31eVc*g_iY90N+u2+V1|J7$)?BxS*zqRCz0bAdE^xoI^jmX#C{z&x8TdTTmubg~a z+@23czOrTWjrSMd)-P`OZC`hd-1yXuywOMJeDT z8<#$f{BZrI+T4qp-dp8M)bQSR!}6m`T8Bj(X}*8mh#wAnr`(5CJZH_fNX_v8%=dil5=tZw$I4==ds__*P(zH$2H zkCRWINEy1tbI_U>59cMX4Lq}bQ=xCSe#83oOhZ;pc}% z9qsi)@$17Qt}KdJ8vAI}yFVvcb5f4Z+IR1#x1Yb!H7oh<9lefke)iLsCtrFk;fJdy?tdhu zWALjZPQKiq{8MygU*0!%<{eAUguU@q#FSSKE+6*F?{AuSU#)%ahu#ar&dz)PCClZG z*M2NL{C>?nk^A?3^jO}wzAqoj4zcg!=6Yr-TB(HU7EC`636z~xaNv! z@Rzd&EgOE!Qdm)59NzV_<00jy1KAIIo*npng78!QbH}6fKs(6OTXFO3q_}zAq_}km zP~1D*qPTYqRNOToiihTQ#lt&N@$fMzo<36*Pv5zUr(dSxRhgPX#ov8$JxrfYzk#y`YO(>=<~yZdZ6?;d$>-T|dYO zK0Pr7Kalw|K_Fa zeIYy)@>>Vcj6UJrG^G!u*?g%_?X*Iy40KjppDHa|_!_Ao=Cl@sR48+ANtH0>qP~QlfjNy2k}@$D?NRUq<~06BDv`O! zuHZ?`X`KnF4CXY}M=Fnb7fF>O=G1&7Rm_~m_DGd7?6yQYDG`oy;?s%T@l!56s80yokAhc`D849rv~}zlY^5 z%+r{+GJk}*vQ0jJH?9CQ%-xv>F!x{{#N3m)I^XKWJdEWXn46e)WS+=e!#snzH*yeIP@=DnDQF~6C)iFt44iOl;j&tQHF^CISbnU^xxGhfHNAM;A){h3!W zAHckh`9S84%mbNQnGa&#%6u?$O^uBI5at2QhcXXh9?U$9`7q`t<{`|Jn2%td$NV?fc z$lSm@ig^O_XyzHrW0)5)pTNA7`CZJ{F^^?l$$TR7D&~`z*D;S{-pG71b1U;H%v+f! zFxS+|_)ldXz-x;~^M z^GcR`Gp}Oq!@Q2UAM-}$I_6g9U75Es@5WrSL#9V}<^jxmF%M$ihq;0IFy;x&!@odQX!g}EPdWv7f^FXlSt;mm`W zFJd0X90RFBnV5SrPh{Sac?NTD=0(hXn3poA@5_){$GjKwO6K9rtC+iS1Fw#`C-X+; z9hqC1doyok?!#QOOUB=ic>wcX%!8PRGdD1I;|5~_b8qHJ%zc>WG52F$%)A%#a^~U8 zE1A1-gR+XbH}g8?KFk}L_hN2k9?o3ZE#v2=r_@FJkGZ#AP({z&hj}RTaOMWp{y=Fy zLAB33Nwv>BPqojyShXJ{?U$?enOCUxnQu_d50mDrRrAd2RrAc7RP&+Ie2Z$Hxl$+N z@55Zj+-;OJuV?PfJXAH$+@P8dljajtIrAh{K32-}R5|lvRc?^-a#hZ}Lgmp?zCqtGfLgS3{5l)8@2#4GVquo{OHtv8b> zTbi``gcPmYlPJy6x-wGfSeZhKcJ)c5RiQW+iQS|1KBQqF z2$IuYQc{aKei>M&L@F66MkF7pV-kPwSLOEn@SF*u4U*o+4Gq z?wZ;EJa&iHe3GKoMiOQCY2P5JYH*XGJMNYocUIk!^|(t>u~~ z_H7WS{1KvzSJeIw<&zL4#=cII@<#~vzN-BZ$|oUM4XgHlXq^)A7;+3su3EawzAlsU zO9)ous{JX-H^iq(<0AirK+4%2yby8|R*FhR`HAGy?3`AC(OyldC_jZ@Rj6t|2QeZ( zh2@mLLa>TdmE%xJ@bPy4DZhn)spcu)5m)`E{72zY!%O*)(oc;)<;M`Drs{qHQk&#z zdQkohL1?*n!*_CTy4^j>uM|GjJ<7KfKdv4S8uD+3)AOSA9xwLMP!uU&lYeUZ(i%#7 zeyabJ&qEM8wZBREow%BQl<&zuHGY);Ltt9%&r&&{_{o*RvOEyKN4P>O;?(+q(#?@? zsGNkrmKrZI7Xn*GelA5KT^#%Yk?syo<;&STm9r4!QZ-(3RpkWxbE9%c@leB^E#et( zk0-4}bj}xY9bvpZ9jTnsdSW%7Q+Xw>mJ2Gk#8v;P{L*uAO$RE^&iQA)h_@qLRL-5l zN#)&{Q@L-?DclbGR1Y}wZ1MaZa;hJk@#6b{O74u561 z?y`ql4VU`7WPLl{DV*78LpVyV^gr63f29BM_Ig0|-%%fu|Bn17{g1NOchY}Hcxf#< zkC!U`!)!UTM{@x#|0n{8S{h4mhKhhsZ zd6ND_+vD%-Zu{p-=RY{!Ow!#cC`nRDcPBb=N{5;D_!RKUYc*#``?KwGX+H`vQr#o_ zQBL8KHBWo{YAqt$nUo%C{i~**TCSyk6YcIv|EAmXku-0#KUZl!&M7>U5ALz=50U28 z(x#TTNltPypJ1;?r1>c138|#}MyL2wd}r9pg_MtXv7g`~zuTTar2A^EN$ElEO|!qZ zB{wOSIbWT%RO0cWx3kEX=T2W<$5;n#awe#rl(p@2eDk8R}5hJ z>uf%V`BTipn5*@&iTNiiPh`HHxjN6Gwih#4Zee*S``?jy5zEzinlP4YSYFEVH<+(u z{x$PT=I=AFV!nrY9rLG|H!|PM+{%0>^H$~?nQM;8^!t{10P}k0LCn?mav1Y!mYbL# zVs7C0c{5LBc?0td=HD?dVt#~qDf7L|*D?Q;c_s52=2gtUVP41lP3DcvKVWWUeu{Z3 z^JkcAj?46DVjjT!3+6%0_cIS;-pt&@{5bPO<|mnFFh9z?i1{w&8ZN&Bn3u9Vjk$s4 z>b&SWmS?eC&+hu5 zA#=?MnZD}#J%IUVEDvJ7g}FKpsm{BGvAmS!>b&S3%uOs;=OGh0ynZZCWVzbU$l&;n zV0i}1&CILVegN|#mVd;&l)2hINMiGySiX+sQ<#Uce?iPES-z0DI?vvhc@@i(nP;&3 zotf9MoQL7;>lCysZ)Ewi%&pAtW?sbZ>6o{&TDS< z=G818!#s%Pk1{W2|GO|(=b6Vb4`cIX%uURvsrK3Z+n6V^T#l0A{0x={vOI(33z+Lz zK9G44%jGB<)^RX@g5@SQAIy9m%d?qRGB07?$mYe$SLju-{9i1uV|lpBS-ygK9?L_S zH?sU?=2qr%^bYHAm>05Kb5cI98O%$+l>8><0W4q3Jc#*p=3&gAV_weoyD~Sid^Pim zt%mmd{k}v)rF~5zA*WFJ-=t`8wv0Gp}U+D)TDlm8zWG@6NoA z<%!HUu)GKJMwZJ_1+1glD&senEMLyNk;B`Yc@fL+V_wQUhj|m5@56i@%jc<_{kxrcCCisFSJz$X znOCuVK65L(e+%wcV)jV?#=3&fF zGdD3WXP(IXHRc)2KW1LU{5|HS%qK8k$2^a@9G|6=0i=@s-DLDzo%wu>@;dWWp7l`s z+wvP8WJ21{u?wIE@EN9r>RHx#|MVONAixs{3*tMxCvrc^Mjipz{NylH*eI z;R5YFceo?xYiM*pDmh=1VozrZONiJHFXeLnCKaRIDyMLed(PZ(ZiyUEqkm^PoeI&O ztM5T|JUE+Yo#cEFohK@l9JisfM4kD3dw8W>ovl>Ig;MO#S;}dDzq9>Jd-$Y$q5U~a zp5x+vo;_bmxjGA{`bWF!o!xijb7}qodp?xoGL+j?^E3`arw=&u6g;!`+*RJ*KAkz> zY+jx{Lpstw$Mcqaf&KYOo?*{7l4s*A1ZVf?t3J+Loqbf@r|zG~P~U!m8$J{)1eRO3p`R+S60cAF8v?H1ACFb?R)ZoQJ0LR^5~1td9Jn zak+XdE`no*$(=8>do8CFesN;gj<ea=yb+UL{v&-{t&!2u9G=dRxw;EwHDT zocCH}FCTJ#$q{~a{zugz9)j9KO+PtLwuo05$oX_iPqjXg^N;FmJI!CyJe{LF%6UR{ zR-NQD|D^gxoa#aPmrBlWIm=~vbI6Hj+w-5C2c&K$$5B3c;vhR)hDusad4SqRj%5X zJi{Iy$>{_esU$D3*AJ4L?e9-H@9*$m+G^opiIz5YhN0z=_Po_auCa^T)P^zZxY;H0 zMt+P<+g`WDg*%*6I)6XN|G}@0bGW3nA}4aUb*PPFS&4?-7`R4QM8k=2T~3#!wxDVzcG1e0eXqllRR>iwKjR-DD=LSiI&z zf$^i36DDY0TtQfU|BaP|#pU-|2wN6@@dRN-iO*AnO`m@HG-1M&d20xjX&u)R*4KXU z9AV3wQ=cc)U;XU`!o1q&ULvgM5cUe;hMnKOD!AL`3POGJ%GU_1-DkWma6!K}2+OP_NJVUNB=sM|E@Z9+pyw|5Au*PRepf9;J*!QWf>E@Az)Di*99io zl#hw)rp^=Cl<}p&gwUHmA^oPxB7vcY4hyXK=(dfdUmy9TK>c!?z|bXkiTsh&?+t;5 z;u{jz%>Inbx6IuraKm?{?cP;F-(#`B(0~I14d(`LBK`90Vu2MO zHVQ=gY$pARd8GpL>Q4#W@W<#cNWcEdu}MSn<%%g|uAeWke4$04?uSnVhE6ykd8y}iGT-$6FoE?OrweR( zb(z58oHqq-(C-si{@G=Lc#gGXPyfVtiS?NRi?5UmOmeFd*wk`TVCYV-9U>kN4-;75 zdz$1GOC@?&2vibx2@HMwM}da7ZxZ!W=-Xie6CS@$V0q#Sfd=3A1m?YYK=OgV39Mh$ zYZv*K&^S(Dy^lV(e>_K^ z()@_PgsZO$%)7o*;D#Tp0$aZH-An!zKRjGu(vZ6aR%|U0m>0QLU`u6{K>dwl0z)JJ zl$hD~E3$8R#3(SYI7MJf)?)%UM86|>$2|h8Kf53>^lra>WG~@Jh{V__0uArv2@JjT zl;ndy5m+4ijX+)EZvvHR-M=RLNn`Gi*f~+4a-c|H-m>QfR&?Jiu>R#^0$aLV7Z_S_ z^M10AawIU}&wB+X4O%9!Y3xf94O;{j-*!S^eV0F^{>hsUkp24S!v$81zgJ-K(FX+P z-S>jP@`gK&>8za!*HBn$w(QJV&XEFrlO(+sr zT(MH1?w7R!L&M(|*z(|~0uwgw5SVoKkd*tK5m+7ki$GnJTLbxTn5vUFyRX2|#Nh&! zDG>teBjN;B47^uhp2tFg3CD^A8s1$gFez`Xz={!X2}J!MuzKxwfg2(l1h$+!B`|Nv zMS)40KLu7i<$I9AlhC!7z`WH#0(HtA0z((Y2&~^fO`sAyU-C!t1RB16SYT4u)dKZX zUldqT_O8IDO`l6Vy;I-@&u;`K^gS)G`nDE<`f)b|=0*F7_Sc5-y#zMh86;3QbhN;B z?K6a1s3-Lw_uu04al|*BhL6nD{ySpU!H2D#p4}gjnLR%|`tqTOS&^gPx^U005x(1+ z$7V$Bi0JaxuB5Nau135%ystiY{+|(Fe|fCRGUa@Pf9Uw18%veQ50(bM-PKbQx#Q5q z>K@N|MY_j+^TCpdA4GHxy`yE^;?9w^S32BvWPWo*w=MB~_qlb5-1hyIyPGawh_GGm zb&Kx<-jTcAtMkgmobne0@>5*FW;3Pd)O6{OeRi_8X%=*)&TRIpso! ziFK2@Mt-p6wO5Wsb&ni+;rEU2r1y#Z`H$bjnm)b~QQDHXYx?=i5ufZG+6WmhC~iqzhlcV z_u$CrwC^6O?{PKal{-9UB^mr9lWu-sjitda^5J)v4Nm)@N94QnUfmgA+9mSQ`VL7u zy7Y)_)!aIExc7+2G0nkec6RF#`NEWfKi~OcP-Mx9rca;z_0NcBedBk(IATC#SHGnu zYr)lsoWxz4E&FbXTsLv*)${)SBCBq_b+)0$h{)T+QUfgqZjLPe$zS^^Wv7GlgJ-`2r3qAPvFIByNWsxgi zK(*(F?&!sj8a3wDH?@6v8_%lv$2NQMue$#f-X5tCo!A z1J>?5zbeQY`K$k!VP8!j$M<+TZDV7i5AVBTd()L?hVv`duDiN#S0rC+eA+rTdNkiZ zuIx9rZ^rWPjFK(-=E!LNlViVFeroaOD_ny&&pR=Kf9LIi^9*xF^Re5q_XLOA^VS&` zoylSssU3s)_NRV*qaW$V8^35ivEwAlm+s^e8WPAKD_JzWMd!-*i!ymVwrLRmz>|R= zrYMK=s`MS~@X06k`s%3>H9T+Fy)~$A?HK;zwr^uTc8lbzw>|$ri-*@p{w4SPrBCz@<%`}P zJx#x7Fn=MA->}Cvf)DTZ>d*a~hVqUJb}qU)Z!{l$WI=oC;gNi6e6xGdr+xYSd6B0T z-;dxIG(Q=?DP|1szW3Cfjs+3?xvxSthq@?v%XgD@*LyWZ7BBkQEkWLo_xLKzJtMR~ z|9QLI|1AG&`f?6rA=W{n%gXGQ*GDQX=C^gG_LMh>_T@7()w%2yw>Kk|Zqdt}*Lp9Um{@ceU5LDQTQLinYF^AGzt--(>! z81Uod*b)5JHFoXF%VYV`$!D$Kz1WY>s(I$Mt;a|5b*2Z%nyKM@?=QRW%ci6FCkC!} zl`kC5w{h`R>ram5FSLEQKwjX&-*ni!To-~LJY4xjX2d&V_~J(gd^&vOU|#m~-(HN^ z63Ab6P`q;V_*h=`<$IHCNd*7>Ds}%4o*l%0H|Wj&-)$JkA5Ga@H)pLcZ`Hd05ILte ze{s+H^keF4kyGbpe)-snj>y#SR{6CJAH}bJoNtl8HI(;~C%%)CJ(~A_s=8h~?`Gt~ z1D@`g^wbFWu_j2f`*!3DgxWn8H=Or<)l>GP??`DJjpV1R9kvwPQ$8?u_oes4$MMd} z@ov`FBKU-!XSPh3NcoY5(aRhY#_-?zy}e-DgmAw2?0#<7v5|bha|6e0jtJ(JyLz}~ zdyM06$E~`$4@~|p8gH}^qbXNb}bsf?+6ZkXx7)R{E-oM1DlGPBE!cG&bCgN z!Y`ZJR`T+11Nlqp>5p%k(Vq`pKk>o@*8w~?$IC$xJdA9}oK)gb=lukPzVJU)@P%!xWO(>#EGr6GO8jI=1;v%BSp z&7_|E?&uRAXg-SKKO1p&y!R)=`PpMv&)-$3kDPdZd^g7%3cmI1MMvE;q5S0SHapsS z&fpi6-uN!&H=ch_bBQ>vo-EA|vv~D|QQPuUqIqk+OH;LG3U9a)U46@ADF4aH!fibd z&Ez#}O}CfS^y7O?_jmf>#CZPIm!q5>`fNO}Q}wS~Fmf`l4hk93@YpE+i?^ec2YdA8 zoBy`{k-zPX;(uCrest<5v-pLrE+x;|%;a5)i%y(*d^-Qm!jD{aXOz6tdySsSMHjemEj)xLw< zWWGZ^Z*XM+d3UIyJ*#(n)*X7_Yh#5x;SPOx!t{NbN!WJkGpViL9qQ0~qxG=;9V**7 zx&4!$Zd3CW?sfa)x9Kn3I~y{qZqt=-S<8n#dz)TTy-?(DxJ?Z|a7V{1yiL>m&#!-W z%x(IB@B%Fy{%+Il+uARl`Qa8l*2{RFJ9dkHo%Qxwb=57pebWPdns?oz;e%^q-&}W# z?$@VhRxG_mdz~7OzgyIQNk_rp;9JySPI=~l%Psox`;(TYGP>VUM zo4s2H-SPCc-yXkulU}pi{p6r;Zqi5iY|X7BH);JlhrO>?+@yW(_zrqt&rP~x__@gB zM{iQw-ws{8pt(us^lW@1YxYfg!meR@^r)NkY-V!QNX1QB7<6{n49A-^q&n~5w&ojj zSpr?cZ~gMb&|cLy=%IvF>c8dQpo_j&&b_?(23_M{U(Bt!L8oum z+K*p&gZdYa$KMTV4EX4YfrD>Q*^CX>zwC~1BZt2m^z3l?o}E{&(;?c`Jzo9lI+f*J zntk$v>-3y_{?C1@u2b>;*mZixywi2)*6UPX>(YKncb!(PyXgGx((Ban)0vK%=TDTx!g{_^i@91ec4XO+`);| z`|Y%>Wwra;743BIlU$qm#dbQqrri3&ws!jVF7v@)Lp$}|7LUJn>TQ?t0h!ZIzd5vj z!wciusj+Y~H+>-97hdhu&R3nu*|pQCP24Tp)@!uaoW*ndUARVDZ66)~Q2jN!kqh1R z`k`y|!z&htpvr4>$LKd#m%V(A))i)C&D?&Cx_MO>MYwf+A(B6cw*aC`lC;M?UEm_(xMAjK3v&wl`fP&Hz@n)Rr>6WsUIJF z=PETX=2qKQT%~2l`!vPBa+UrXm!ondQQaA)9E>eGJVXZv1P=|@LAz7KN8wu-~wRr>PG zZj{v-4W% z&9loj?NeImtl!F&!DCwK6TMIOTQQ`S&Rw*u{{clS^%h>(U*mRivpwQ_)llwc3$?E< zn()kTEmT%|>EPfCE%f8`vS-hpZlOPxKT^2oXbYV`pwC(Ldo9#^>}y_QUu&U3bsHl3 z7PZiXO}>KzUTmQ|9ael9^fca&$nuKFYN5YPK6-LVdJB!V|F-4nq!!vf@YRP-JlH}H zXAHU-5Ys}Zcv&jePidk14=!ACX-o^f(QDPOONX}5_vM48U+mLD-~8pLxOKiQH0Pv} z^K)&XYT1#2bzBS84$XUSOItIYaq@QF>|dJc3z;*_{VdJYs2ajKodySI&ANHCnR<*` zyz}~@W*Rk!tF(Eenc5z1*%eUQO!s+S8ZdreGcB^IU7~-knR?Y^d)8z((^)6-B5pj| zOh*J~)vD5(X)t$Yrn~x1!(TI9qVDm+TYNM9?%+!q z3&NY}Ku=>3H?Wyz+ziZmt9LWKJU=>fy-zbeYhLLx)w!8^DmeT#Q(f=FkJ?;oqBCnZ zjcdKqL=Clc?B6al(f6i@uDsgNM0;iBSK58vL>E~CI)XlGq62=N5;gtZCVKIm=9L>? zYogCT;vfHZNfX`YVe#YkHPOBKPi4;A-9*10l=FDa_9p7-o8{q~)kL?P>FKw9T@ziO zHsMLTlqSlBb*P?BY@)8g=?^`yq=_CqeXHQ@xlMHJXHISpMK{sDQ^TKjqfOMKZUp|C zXodOLCwB~PqQjLB{utM%iS~~!@l*Qaec{zaGv(Z&oA%hY-j-W$TWNm3*KhoK)k<^r zUS56mij{h(H_x}bWTnqfdB&vp+Dgal8?j*07gjp<+`%~yAGOkl$M4uyVz$z{SNdJK z{Fap-xDu)#T5Y8za;;NVv6ZSHuP*-j6)TN(9%ye@wY`*Y7_p|wHM}E=|_p(k58Ls zrE;6`_(Qy@o8S64!b<&@^*->d(n^0Cu&yI9#7Z4(WLZ%PD;>3A?#RBrh%daXG!s8W zZR22N_c8G5BtF+pT{%oC1`7X+pI^|45Uc#)FB~lMt`(h7B7Mm!oP( z9g&H3qc#X>#}UBhCL>Q(%5(u#OyKZ;g zhm+e9`DPc3Q@w-P4iln~Myxx@=}$-#6fLBIM*d@1FFVq!)sA=|KifnHvJeYhmqN)x zd>caW9>II}h9jLngmDROB;k?`S$4{nEIQ&!Vq=}joNy=N7-MVgrjw~T`)KDnO6(Q0qD)!8TmZDhnI)}Lg067mTYD?@#H*%2@NBCzu*Ut*K!CxoBCdx`8l zv;lr+_r78Gq=7c+y3IM+5GS;+Q<$AjwC$O#7TeQ~Scl-*rlEv1bmn3BV;JVSE^++u zV{qQwlf+f$qKqAi-vC8(2xUpkCJKC5z6=WJCiac4+xUrp!s?l`;fT!yv1_8s5)U5- z;$v_mKIn%&@Y5&E!xA@1{5(Dz@vlK_4rQ;!=;OA6o1l zFI4F@*kTLF4-cvN>%Zap!W0 zgU(*f>W95o!`|!fg#J5=xLTbpj`hM=RI^gB_f<0Z?L5{>9={s8;EmEv| z^5A;8kX{C7(ks!4^on&Py}}(xuP{Gr4~vhP^K&A8=+k~^Ge5MMAKJ_>%*Vpr?d$0C z;pp$^>wf*5NcUJd>5jhEUGHg?Tiohhbk1t#PXwz+ph=Pofb%y-Ipq8g|&KL`5NBHF(gS_zrpza14<~clxi5KY9 zLb~sAS2;?@zRtMocj8l;bw%Gm|J4iqwVvzV*$3S^`=FWiBVqhHpb#}tMDua)s@nw>0 zkV&e`q;fpZ8|vJ?Umd(;!kC6S>%ec2J@Gdzz`Q*lV>gC4OMN~Q8Hj$lz%TS2)V;SJ z{l-N`T=cf8`=!184MJL=&c5Q|X@_xu=fUh3$3bU52|0k{Jakm@`+jNJy@}_`L7i!F zJ!GWEe))a#{q`-KXFzx33jN&;e!IeN_`i$I{Wwm-7oYv&d??IWyKoF*bC+Ir-*o4j zaqNUT>*{`fp2qvX{GB+x4&ym_(Elx+JxHUVOFH6w=z;lAj``3X^PwB@+mHEBm?yD~ zKIw<@`JsG%yL_zTaj84z0Uyi*-X!3QF2@XEAH+>WT(qSh+R{(oIk&nIAEe`hbbNMU zZVhB}qZ``Im3ZpKc4PC1lAu4Zx(c&ZdU)e_n)rKP?QGX4z9Qs(=yGSf{$YLyI)^?B z;iMG*A-w<`-wZu4e|7Pj^CUhNyeIe!Uoq$TVO;oOT=>Pn=KvfJS^4(+SiLMB_3mbz zRjHj-V)=1AMp*(-mVlVw%kQUjCcVQSCcU{8L~ccy>YdD8k8z4_7^8jI+~~>r_&qW1 z&Y`R z;$=emHvZmc#ABK>=4U73u4mUB-H2T{eA1z=*!*qah#!7L*$H_%;W*NH-0=8A8yvcY zQ*bD((@){pBb?vr{6ZWs7kI-@FZk(6{F~(}mJYHYe>`7gEhxiGnF&RArp3uGkw-ey?%_mwg!f=` zz&-8bH;|npg^&PFao@JJ!cV^;j-+R7j;v?+PFYWGyG-oW;`vP!t{eS^p?}0aFY6t? zTh^P~DHF~pgxOq-qedK#q5Zk;jyNxyMBJ^ImQtx zc;Q^fE6iQT31bt-5Nr$Qyl%Lj4s%j+zIHf9+T$1rhIy;~hPaOCjkfNIw(fzp_8RWC z-^uD=v8%T+cb*rjPKyZa zy={prx1HEoZ0coZv3}(^zX}X-A|b6?iNdfA=Soiyd2BXu2!E0&M{{Hc6e1VP-gTRE zwk6Jq-H0>B0Xuv5^TM@3zSOSFccE`PiPsatxb?vJkrVeXoOLMw{Cmnj@6YAOX^+&G z#q-F{G98TO$j4CmKiHS&PUFbq(A~bwu6HKD-`;qp8qV3|`rpre?#{J%RFjhy*Sjc> zF!l`?+bF9o%6h-?H6V^7Nl@qbaukk%()nJPFlNl1{_7uvFVKhZpOItzY?qDFuRqSA zv^Z{Z4-lNe<4nU=X@gF{e5z;l$>tJIoUb~g&b7A0{sV-u>oJ+c&0tRl($j$PhCbO7 zeXwVkx5`nddq>ng&dIx+TMk^okzo+?gV9lFuY1SRErcsqa>R-_j6#06z88-maH`9T z-799BOTF04BtAsx}!sT+DPeW#iUGvxAegnJbz^=RNA%c4>>|P7IC!-jI zYa?98vupX9Q0xOK*s%~wL|SRkW6*YJ50np8LT^GJLMNfGp+=|~;=EQ-)iY$GW}myU-WGZS~*MI(hhEjNC>xS`W% zR;MLxkdo1B5~LmVnq*CUniQO-Nx}C}St4l~1D@flH>Cc)X-L0or=}b3W|z)x&Pn^P zi%&A7btbVcB~i#wydQ`P5nj1HQ8&UBcW21GWcLZ#t3u+(63hRq7bLbxwDT9s=_@fvVyMJ15@$+WCea{q zyTn3?ha{emXpz_|(TTM?UI7vZON^2jBXPOJ6p0%pZkH$|g8xq2#2At%#_J5}ddQzoYv^!UQrMfT2uB)uU$K6!r1Iy{maquzFYiZ(ta zFsDr1)fv`FS&w(hSkqg=~dKlQhfISFY6P zJ3Tokq+y6h<>=T0#TSw+7J_GOrzdNog$i0o$OTq|V%6a};%P`7zGB8&tx251<|H8x zvWo4=o^L)`ug6SA#_pN?hiYSPf47oOG*)HlhoM8CZ~;OGb29B!-wlnb z6Wk_q1zRj?KXEUXI(fimjxbpZtu8(X9=ScnO~DwLn4F54e~VyGNy2lN*&af^dxWsr z7*s51A=!m~gPKZ8oQPwJ3eSZn?{X-`_=ze)@|fpyCeLE_F%~*e#kvvsPOwwgtU(u= znUta#Pb_~3SxDNt%}P%;Bu!Xvz+>naCM9YnYUB09!>-eeO}+a*8PD>WoU%4apPI4; zLr5%48cOrJ&}R30tG3(BiL>H$cguZ*fd|-?KPk{;_C%c)NtfnrFD%I~EHq z$C5x#5$qU^*p5#Up@pPwbb?G2P6WgjnK3zL_T-tv!-Nxv&PnlKz_9%9hP;ULrOzHz ze6iw`_^Cb1M!ZfWWT-;Qv|VK9xtbJ;bD(=)_>@Ta%<|sQd$%~nz1#n+Eg90i-}&W# zH+`1wAIte)O|!G!jIW5F_jmf)NtRw$`|qXS5On|b4W0Y_zka{dk4|QW+}~e?QLGPR zo=EvKB0Kl{N2v=G(^vKpIq@IeQHm$_eDOMj1!WQ^Cw%hxKllak3LCd5BnaIQch zzWt~Fvu=w)Q4$aQgYm~p@!v%J&+{+k`;#B?8}pa^b^4Fx346@7C1mcoW#<&+p>wOw zh5b4Hmkr-quH$*GkBOe;=Zo|EZA`c;=eY@EdT!^t|Kb1k8esQR^|+tP)&_Q6OU2e{b*{%^e8ZoR z46K`CT!{OwyJ0gv4w+yx=0OKxcU=p{){>p;gJoo#OkgAI0K{Qzg(ksf^uj${7T$Fo z6)JWCwr*4i&hHS; zcofP$94xJ)sEL>^}4<+=H%z z-F00VTZ1+cx&)sYH$d&M8FM9@(FD1myzD;y!_ZRL2Jmwz7PbYv4QXMs4|-J)i^I4A z>${R+Yr!i}4Q$3{i1j5#t7Nx= zdm`~3;+VmDr~$SGoIeTe2%CLIGiM6w6SfN605Kmnf`t(CnXy)~8IMW!aqtJpZUoyU zn^7|r?S;>YGr=543ERjx4Q&KF3XFrIV5`A+XcBB4_%IX=y8`?aVm=%P{ich)1%M+U z79Ig!gIIrP2k8tkJPKSc*@<8o#L86xUX<)hVAw1|W+Ja}a6KfH9sEGDkAP0I#c&t! zL5S6}8axcKd@Z124%!RpD8a>$4z?QH24%p`0l$JWVYhBWtQNx=Z$TEMPvS8apc?F(0sbc0R&d91 zv3{Nc-%CIl5x*f3^{l}>h4>r6-YZZfge$;cE$SI|2)J(*`UY$x=(0w%88ac)MvVPa z#5xQC_oRw;E;#66(H;!GuNQ4I*gqX}HS!GsZ8K0Fr0)QJu>oy_@Pm(_Uu_h9jsRO8 zMPEgDEBNIm%(sYt3VbmW^9jQDf)lcEKONzdzz;WzejWj*Z4vF6;1>|n%8IC$X&v={74-~+iBYp|Dsn;@3X7I3#@GoFN)Z|A{)7e$+K9mL{41sWxru|~2P ze~|1(uwAkleP6=7%KQh{K`i|YaKkG&b|QQu7+8q87l_{fv*VHg56E%hp>IYk&rj+2yhd`{LchGf_5Xk4s3%|EPg2=o6Aw> zuup+O6=GZT1t&plU&chqW{jv5>y|MOV*R`jJOZ(N8EvY>eHkBzSlPFLKS}l#W><@O zT>)>^piZzac@6EgAIB2d3h)TT>V)y)>qHpu?clsO#59vZy9200#CHH2A#bd8WDI!| zbpo4lE7XWM*`Vw#aZK5Ob0Fqh9C!#~{+q#DkQV!rTI36{Is6is`8LKB!WqjT7QX^K zBiRk$f_E|A5l0Pn97Ml?%_x6Qj2~e}e}K#g&j1fVgPmoh7Q6*rLO3~$_Jte}t_FvE zfIf$CC8&Z}e_)*aAzVawG*}I>aa02~L9AS@VBHbXf5z00#Bd!r?qe~WF&$!UodLci z*?YmyAeK%&$bBN(HsF?Hs0;YUX!uktFXNQYP;Us21}jfsUV>c%Ui}H5MK+Pfv&*51bQ}zecK!4 z&Z2)}eW(p6KSzZA!#D%-M)*wdDTvh@<0**cWd(!37VSyk9?8xHPf0do)Oj%u;{nM& z4o>(MJ|O)`;EC_VdZ-7_evkS=_<1nlBI*!!BB;NNIIuT@BOB2+t{A`I0my>zS}^)2 zaV#(n_yy+^h%*@c`3lMg`wCe2E7}zC8Et-(#vrJWY{s)z^gYBm56*2uf5yIA@M(zE z`4#ZRX3Uug-wVFdBKmIxN3@DQM1XE>;(3uATn4dvQ-f8KT?5{hY;ui|F%V0C9JmEy z@fnXv_Hod&T@3dIH4tkTEqD}SWAHd=x-RxN#&(FsVNALq&Vk9`S%`(72V-xFX)u;Bye`OS{3(AU1E;gHz-PXK}!r5c5CV zgCn;+IbrS%!5X$@5Sw$dK_%7^vN(+UpmywQ1Z{mdp?(~|1gsBa$KS5&0@->&l|R;8 zV$C4qi;x!fUeGd_BLT1(R}Mj$Q72k(BgFDzREHu>#9@pc%8_i?jE5msheyCil%fwC z!H8j^&G-Vu(#Zw$!#I+QeG5VPaIsvBT1dzj)P-}zh&YV5MsTDCHW|s0B8VN2Okg{7 z1>uZFtS9V%Z3W9BIMM*S0u01DL7}d?tQll$2dl?%So0+l_!;a(*!5sRr08=YIDzNL z7KE!n-w7Nsz*c~7K)Ycx9-R!|U>^sUPk|q>6TwFyq29m)l3fdiO%>A*2QNabjjn*H zSUbr2whsIPYCt-tzz?xTumkoH@WJU~{pi4QXs`#$2xiShUxK{_{1IYp(Fi)s5^cs< z$!08+Y~5@QYhckQu&-`DN8VZhpJ9(%$dM9=`Qw81gO5VV2+srq7m4GdFZeyg;*iB0 ziGf((js7a;P=fm)=9>vzu?+2qG_~Mo&_USs;P!{aGCl>quI9)Q77j+m zbL0|iMm5Ba`;2YS6@<%|b3_5P!&ZY&Km_aivcWwX)CKHZ@MDPiRtNq733UY~u0a1s z9LC2X7QO}C1F`s4FmfeFCLvBHcmmSGt_Piy;6KK)0{juuA)Ktj8a}8FeN_SWhw5Ro z@7%HP(wUGZBl{j5vl-cU;h4?HzPmOU@fq27#j;^DvhOqW#kB(?`|bdX&&a-C!)$?) z&A#)&zE8oz8QB_qW(#ZalSP}Y!DZ{ySvVtG1I}zlwuYP8jBE`lvl-c%Rc14?^~}s> zWNTlU&B)eoGMn*llFim@%GZeDhO9|S(5I%Qt}qNqNL@1~K5b3t+F`*8tVBy%p-D3==$adnQYgk5^yz5^ zwrovGE$Hv0Hatk!4|}F*64J3oZ@sjQ5WVK%bmXE*ROyq}Vl~)GP1@ZzyS_a+1G}eA={>wW+H$`d~$R()fe~tU8PgUJ;+1rU_OIy_?Rsp?{srxS{v> zG;ZkK!eRGuLp$pY8-H$&KZodW+(o;EP;4t)kI3=rHWEzX;i7IR9&ho%`D9+HI~+tnoH|T zEv42{QYJ4`lqt)i%2Z|QGF@3_Sx%X;tftIdR$pc*vzC!^dAXuoSsqocDp!~5$}`Jz z%8liymP}kH0IclU`R4Naa!Yw*xwX8#oK!ee$SZs+LMoIM5fxDt(G{wSxC(VeMn!f- zZiTVJR8dn=TcNCqsEVqJu2NOSRjI4ARl2H-s?4hFs+_9aDr1$YN?z?-t*8#ER#rz; zM^#5xtE%Iw)z#W+U3ErvW_5OTPIYdzvD#E!Q(aqauCA-DuWqQeR5w;ztJ|wd4o7fH z2{%a`jB=x|QDF=*Dvc4wC}XryWsEbbjXGn7G1HiB%rWK~jYgBP##n1K8|#eq#s;Is z*l4sG+l?g8Ay1y?o2SSN$y4S<zdH6?PysCw5=~%Hw5h)ffkNN8^@uQwP@!Iv~+ec^AYE{Ky6iBRYO%{ zReO~KN*;nzN2A1Alr|eBHKCMsC}ATj9WEwuzJWgIfUoermV}f$U2~zo%iFo`Y#xPHRa-% zvtqo-D-;-OQ5a|H3SC8JMGi(;O@+ClzQR&rtss^1N=2ozGOAKlsjk#jW>)4@8Y^om z&6V|)mP%_SsghSIs+1UwDvU%OMqv&{U`>^|s=mrnWvwFBa*RYJMxhELP>0c%gOOK* zQCE)6Ka(x zaBYFNbr@$27-PN|Us_>~%)z*-&(~pw&c+;_i&@%)dAb%ebshSv1wGb^K1)g+Fk>Tu L|GfS!YvBI?uDJI8 literal 0 HcmV?d00001 diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta new file mode 100644 index 00000000..cb81e95f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta @@ -0,0 +1,52 @@ +fileFormatVersion: 2 +guid: 07d3660260d3a4e46944eeb95bb82783 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: x86_64 + DefaultValueInitialized: true + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Kcp/AckItem.cs b/Assets/GameScripts/ThirdParty/Kcp/AckItem.cs deleted file mode 100644 index 55195a07..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/AckItem.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace ET -{ - internal struct AckItem - { - internal uint serialNumber; - internal uint timestamp; - } -} diff --git a/Assets/GameScripts/ThirdParty/Kcp/AckItem.cs.meta b/Assets/GameScripts/ThirdParty/Kcp/AckItem.cs.meta deleted file mode 100644 index 446b59cb..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/AckItem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e04d94c974c041646839574cfe5ad538 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Kcp/Kcp.cs b/Assets/GameScripts/ThirdParty/Kcp/Kcp.cs deleted file mode 100644 index f04f837f..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Kcp.cs +++ /dev/null @@ -1,1244 +0,0 @@ -// Kcp based on https://github.com/skywind3000/kcp -// Kept as close to original as possible. -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace ET -{ - public partial class Kcp - { - // original Kcp has a define option, which is not defined by default: - // #define FASTACK_CONSERVE - - public const int RTO_NDL = 30; // no delay min rto - public const int RTO_MIN = 100; // normal min rto - public const int RTO_DEF = 200; // default RTO - public const int RTO_MAX = 60000; // maximum RTO - public const int CMD_PUSH = 81; // cmd: push data - public const int CMD_ACK = 82; // cmd: ack - public const int CMD_WASK = 83; // cmd: window probe (ask) - public const int CMD_WINS = 84; // cmd: window size (tell/insert) - public const int ASK_SEND = 1; // need to send CMD_WASK - public const int ASK_TELL = 2; // need to send CMD_WINS - public const int WND_SND = 32; // default send window - public const int WND_RCV = 128; // default receive window. must be >= max fragment size - public const int MTU_DEF = 1200; // default MTU (reduced to 1200 to fit all cases: https://en.wikipedia.org/wiki/Maximum_transmission_unit ; steam uses 1200 too!) - public const int ACK_FAST = 3; - public const int INTERVAL = 100; - public const int OVERHEAD = 24; - public const int FRG_MAX = byte.MaxValue; // kcp encodes 'frg' as byte. so we can only ever send up to 255 fragments. - public const int DEADLINK = 20; // default maximum amount of 'xmit' retransmissions until a segment is considered lost - public const int THRESH_INIT = 2; - public const int THRESH_MIN = 2; - public const int PROBE_INIT = 7000; // 7 secs to probe window size - public const int PROBE_LIMIT = 120000; // up to 120 secs to probe window - public const int FASTACK_LIMIT = 5; // max times to trigger fastack - public const int RESERVED_BYTE = 5; // 包头预留字节数 ä¾›et网络层使用 - - // kcp members. - internal int state; - readonly uint conv; // conversation - internal uint mtu; - internal uint mss; // maximum segment size := MTU - OVERHEAD - internal uint snd_una; // unacknowledged. e.g. snd_una is 9 it means 8 has been confirmed, 9 and 10 have been sent - internal uint snd_nxt; // forever growing send counter for sequence numbers - internal uint rcv_nxt; // forever growing receive counter for sequence numbers - internal uint ssthresh; // slow start threshold - internal int rx_rttval; // average deviation of rtt, used to measure the jitter of rtt - internal int rx_srtt; // smoothed round trip time (a weighted average of rtt) - internal int rx_rto; - internal int rx_minrto; - internal uint snd_wnd; // send window - internal uint rcv_wnd; // receive window - internal uint rmt_wnd; // remote window - internal uint cwnd; // congestion window - internal uint probe; - internal uint interval; - internal uint ts_flush; // last flush timestamp in milliseconds - internal uint xmit; - internal uint nodelay; // not a bool. original Kcp has '<2 else' check. - internal bool updated; - internal uint ts_probe; // probe timestamp - internal uint probe_wait; - internal uint dead_link; // maximum amount of 'xmit' retransmissions until a segment is considered lost - internal uint incr; - internal uint current; // current time (milliseconds). set by Update. - - internal int fastresend; - internal int fastlimit; - internal bool nocwnd; // congestion control, negated. heavily restricts send/recv window sizes. - internal readonly Queue snd_queue = new Queue(16); // send queue - internal readonly Queue rcv_queue = new Queue(16); // receive queue - // snd_buffer needs index removals. - // C# LinkedList allocates for each entry, so let's keep List for now. - internal readonly List snd_buf = new List(16); // send buffer - // rcv_buffer needs index insertions and backwards iteration. - // C# LinkedList allocates for each entry, so let's keep List for now. - internal readonly List rcv_buf = new List(16); // receive buffer - internal readonly List acklist = new List(16); - - private ArrayPool kcpSegmentArrayPool = ArrayPool.Create(); - - // memory buffer - // size depends on MTU. - // MTU can be changed at runtime, which resizes the buffer. - internal byte[] buffer; - - // output function of type - readonly Action output; - - // segment pool to avoid allocations in C#. - // this is not part of the original C code. - // readonly Pool SegmentPool = new Pool( - // // create new segment - // () => new Segment(), - // // reset segment before reuse - // (segment) => segment.Reset(), - // // initial capacity - // 32 - // ); - - // ikcp_create - // create a new kcp control object, 'conv' must equal in two endpoint - // from the same connection. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Kcp(uint conv, Action output) - { - this.conv = conv; - this.output = output; - snd_wnd = WND_SND; - rcv_wnd = WND_RCV; - rmt_wnd = WND_RCV; - mtu = MTU_DEF; - mss = mtu - OVERHEAD; - rx_rto = RTO_DEF; - rx_minrto = RTO_MIN; - interval = INTERVAL; - ts_flush = INTERVAL; - ssthresh = THRESH_INIT; - fastlimit = FASTACK_LIMIT; - dead_link = DEADLINK; - buffer = new byte[(mtu + OVERHEAD) * 3]; - } - - // ikcp_segment_new - // we keep the original function and add our pooling to it. - // this way we'll never miss it anywhere. - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // Segment SegmentNew() => SegmentPool.Take(); - - // ikcp_segment_delete - // we keep the original function and add our pooling to it. - // this way we'll never miss it anywhere. - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // void SegmentDelete(Segment seg) => SegmentPool.Return(seg); - - // calculate how many packets are waiting to be sent - public int WaitSnd => snd_buf.Count + snd_queue.Count; - - // ikcp_wnd_unused - // returns the remaining space in receive window (rcv_wnd - rcv_queue) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ushort WndUnused() - { - if (rcv_queue.Count < rcv_wnd) - return (ushort)(rcv_wnd - (uint)rcv_queue.Count); - return 0; - } - - public int Receive(Span data) - { - // kcp's ispeek feature is not supported. - // this makes 'merge fragment' code significantly easier because - // we can iterate while queue.Count > 0 and dequeue each time. - // if we had to consider ispeek then count would always be > 0 and - // we would have to remove only after the loop. - // - int len = data.Length; - - if (rcv_queue.Count == 0) - return -1; - - int peeksize = PeekSize(); - - if (peeksize < 0) - return -2; - - if (peeksize > len) - return -3; - - bool recover = rcv_queue.Count >= rcv_wnd; - - // merge fragment. - - len = 0; - ref byte dest = ref MemoryMarshal.GetReference(data); - - // original KCP iterates rcv_queue and deletes if !ispeek. - // removing from a c# queue while iterating is not possible, but - // we can change to 'while Count > 0' and remove every time. - // (we can remove every time because we removed ispeek support!) - - while (rcv_queue.Count > 0) - { - // unlike original kcp, we dequeue instead of just getting the - // entry. this is fine because we remove it in ANY case. - SegmentStruct seg = rcv_queue.Dequeue(); - - // copy segment data into our buffer - - ref byte source = ref MemoryMarshal.GetReference(seg.WrittenBuffer); - Unsafe.CopyBlockUnaligned(ref dest,ref source,(uint)seg.WrittenCount); - dest = ref Unsafe.Add(ref dest, (uint) seg.WrittenCount); - - len += seg.WrittenCount; - uint fragment = seg.SegHead.frg; - - // note: ispeek is not supported in order to simplify this loop - - // unlike original kcp, we don't need to remove seg from queue - // because we already dequeued it. - // simply delete it - seg.Dispose(); - if (fragment == 0) - break; - } - - - // move available data from rcv_buf -> rcv_queue - int removed = 0; -#if NET7_0_OR_GREATER - foreach (ref SegmentStruct seg in CollectionsMarshal.AsSpan(this.rcv_buf)) -#else - foreach (SegmentStruct seg in rcv_buf) -#endif - { - if (seg.SegHead.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) - { - // can't remove while iterating. remember how many to remove - // and do it after the loop. - // note: don't return segment. we only add it to rcv_queue - ++removed; - // add - rcv_queue.Enqueue(seg); - // increase sequence number for next segment - rcv_nxt++; - } - else - { - break; - } - } - rcv_buf.RemoveRange(0, removed); - - // fast recover - if (rcv_queue.Count < rcv_wnd && recover) - { - // ready to send back CMD_WINS in flush - // tell remote my window size - probe |= ASK_TELL; - } - - return len; - } - - // ikcp_peeksize - // check the size of next message in the recv queue. - // returns -1 if there is no message, or if the message is still incomplete. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int PeekSize() - { - int length = 0; - - // empty queue? - if (rcv_queue.Count == 0) return -1; - - // peek the first segment - SegmentStruct seq = rcv_queue.Peek(); - - // seg.frg is 0 if the message requires no fragmentation. - // in that case, the segment's size is the final message size. - if (seq.SegHead.frg == 0) return seq.WrittenCount; - - // check if all fragment parts were received yet. - // seg.frg is the n-th fragment, but in reverse. - // this way the first received segment tells us how many fragments there are for the message. - // for example, if a message contains 3 segments: - // first segment: .frg is 2 (index in reverse) - // second segment: .frg is 1 (index in reverse) - // third segment: .frg is 0 (index in reverse) - if (rcv_queue.Count < seq.SegHead.frg + 1) return -1; - - // recv_queue contains all the fragments necessary to reconstruct the message. - // sum all fragment's sizes to get the full message size. - foreach (SegmentStruct seg in rcv_queue) - { - length += seg.WrittenCount; - if (seg.SegHead.frg == 0) break; - } - - return length; - } - - // ikcp_send - // splits message into MTU sized fragments, adds them to snd_queue. - public int Send(ReadOnlySpan data) - { - // fragment count - int count; - int len = data.Length; - int offset = 0; - - // streaming mode: removed. we never want to send 'hello' and - // receive 'he' 'll' 'o'. we want to always receive 'hello'. - - // calculate amount of fragments necessary for 'len' - if (len <= mss) count = 1; - else count = (int)((len + mss - 1) / mss); - - // IMPORTANT kcp encodes 'frg' as 1 byte. - // so we can only support up to 255 fragments. - // (which limits max message size to around 288 KB) - // this is difficult to debug. let's make this 100% obvious. - if (count > FRG_MAX) - ThrowFrgCountException(len,count); - - // original kcp uses WND_RCV const instead of rcv_wnd runtime: - // https://github.com/skywind3000/kcp/pull/291/files - // which always limits max message size to 144 KB: - //if (count >= WND_RCV) return -2; - // using configured rcv_wnd uncorks max message size to 'any': - if (count >= rcv_wnd) return -2; - - if (count == 0) count = 1; - - ref byte dataRef = ref MemoryMarshal.GetReference(data); - - // fragment - for (int i = 0; i < count; i++) - { - int size = len > (int)mss ? (int)mss : len; - SegmentStruct seg = new SegmentStruct(size,this.kcpSegmentArrayPool); - - if (len > 0) - { - Unsafe.CopyBlockUnaligned(ref MemoryMarshal.GetReference(seg.FreeBuffer),ref dataRef,(uint)size); - dataRef = ref Unsafe.Add(ref dataRef, size); - seg.Advance(size); - } - - seg.SegHead.frg = (byte)(count - i - 1); - snd_queue.Enqueue(seg); - offset += size; - len -= size; - } - - return 0; - } - - // ikcp_update_ack - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void UpdateAck(int rtt) // round trip time - { - // https://tools.ietf.org/html/rfc6298 - if (rx_srtt == 0) - { - rx_srtt = rtt; - rx_rttval = rtt / 2; - } - else - { - int delta = rtt - rx_srtt; - if (delta < 0) delta = -delta; - rx_rttval = (3 * rx_rttval + delta) / 4; - rx_srtt = (7 * rx_srtt + rtt) / 8; - if (rx_srtt < 1) rx_srtt = 1; - } - int rto = rx_srtt + Math.Max((int)interval, 4 * rx_rttval); - rx_rto = Utils.Clamp(rto, rx_minrto, RTO_MAX); - } - - // ikcp_shrink_buf - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ShrinkBuf() - { - if (snd_buf.Count > 0) - { - SegmentStruct seg = snd_buf[0]; - snd_una = seg.SegHead.sn; - } - else - { - snd_una = snd_nxt; - } - } - - // ikcp_parse_ack - // removes the segment with 'sn' from send buffer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ParseAck(uint sn) - { - if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0) - return; - // for-int so we can erase while iterating - bool needRemove = false; - int removeIndex = 0; -#if NET7_0_OR_GREATER - foreach (ref var seg in CollectionsMarshal.AsSpan(snd_buf)) -#else - foreach (var seg in snd_buf) -#endif - { - // is this the segment? - if (sn == seg.SegHead.sn) - { - // remove and return - needRemove = true; - // SegmentDelete(seg); - seg.Dispose(); - break; - } - if (Utils.TimeDiff(sn, seg.SegHead.sn) < 0) - { - break; - } - - removeIndex++; - } - - if (needRemove) - { - snd_buf.RemoveAt(removeIndex); - } - } - - // ikcp_parse_una - // removes all unacknowledged segments with sequence numbers < una from send buffer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ParseUna(uint una) - { - int removed = 0; -#if NET7_0_OR_GREATER - foreach (ref SegmentStruct seg in CollectionsMarshal.AsSpan(snd_buf)) -#else - foreach (SegmentStruct seg in snd_buf) -#endif - { - // if (Utils.TimeDiff(una, seg.sn) > 0) - if (seg.SegHead.sn < una) - { - // can't remove while iterating. remember how many to remove - // and do it after the loop. - ++removed; - // SegmentDelete(seg); - seg.Dispose(); - } - else - { - break; - } - } - snd_buf.RemoveRange(0, removed); - } - - // ikcp_parse_fastack - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ParseFastack(uint sn, uint ts) // serial number, timestamp - { - // sn needs to be between snd_una and snd_nxt - // if !(snd_una <= sn && sn < snd_nxt) return; - - // if (Utils.TimeDiff(sn, snd_una) < 0) - if (sn < snd_una) - return; - - // if (Utils.TimeDiff(sn, snd_nxt) >= 0) - if (sn >= snd_nxt) - return; -#if NET7_0_OR_GREATER - foreach (ref var seg in CollectionsMarshal.AsSpan(snd_buf)) - { - // if (Utils.TimeDiff(sn, seg.sn) < 0) - if (sn < seg.SegHead.sn) - { - break; - } - else if (sn != seg.SegHead.sn) - { -#if !FASTACK_CONSERVE - seg.fastack++; -#else - if (Utils.TimeDiff(ts, seg.SegHead.ts) >= 0) - { - seg.fastack++; - } -#endif - } - } -#else - for (int i = 0; i < this.snd_buf.Count; i++) - { - SegmentStruct seg = this.snd_buf[i]; - // if (Utils.TimeDiff(sn, seg.sn) < 0) - if (sn < seg.SegHead.sn) - { - break; - } - else if (sn != seg.SegHead.sn) - { - #if !FASTACK_CONSERVE - seg.fastack++; - this.snd_buf[i] = seg; - #else - if (Utils.TimeDiff(ts, seg.SegHead.ts) >= 0) - { - seg.fastack++; - this.snd_buf[i] = seg; - } - #endif - } - } -#endif - - - - } - - // ikcp_ack_push - // appends an ack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void AckPush(uint sn, uint ts) // serial number, timestamp - { - acklist.Add(new AckItem{ serialNumber = sn, timestamp = ts }); - } - - // ikcp_parse_data - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void ParseData(ref SegmentStruct newseg) - { - uint sn = newseg.SegHead.sn; - - if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 || - Utils.TimeDiff(sn, rcv_nxt) < 0) - { - newseg.Dispose(); - return; - } - - InsertSegmentInReceiveBuffer(ref newseg); - MoveReceiveBufferReadySegmentsToQueue(); - } - - // inserts the segment into rcv_buf, ordered by seg.sn. - // drops the segment if one with the same seg.sn already exists. - // goes through receive buffer in reverse order for performance. - // - // note: see KcpTests.InsertSegmentInReceiveBuffer test! - // note: 'insert or delete' can be done in different ways, but let's - // keep consistency with original C kcp. - internal void InsertSegmentInReceiveBuffer(ref SegmentStruct newseg) - { - bool repeat = false; // 'duplicate' - - // original C iterates backwards, so we need to do that as well. - // note if rcv_buf.Count == 0, i becomes -1 and no looping happens. - int i; -#if NET7_0_OR_GREATER - Span arr = CollectionsMarshal.AsSpan(rcv_buf); - for (i = arr.Length-1; i>=0 ; i--) - { - ref SegmentStruct seg =ref arr[i]; -#else - for (i = rcv_buf.Count - 1; i >= 0; i--) - { - SegmentStruct seg = rcv_buf[i]; -#endif - if (seg.SegHead.sn == newseg.SegHead.sn) - { - // duplicate segment found. nothing will be added. - repeat = true; - break; - } - if (Utils.TimeDiff(newseg.SegHead.sn, seg.SegHead.sn) > 0) - { - // this entry's sn is < newseg.sn, so let's stop - break; - } - } - - // no duplicate? then insert. - if (!repeat) - { - rcv_buf.Insert(i + 1, newseg); - } - // duplicate. just delete it. - else - { - newseg.Dispose(); - } - } - - // move ready segments from rcv_buf -> rcv_queue. - // moves only the ready segments which are in rcv_nxt sequence order. - // some may still be missing an inserted later. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void MoveReceiveBufferReadySegmentsToQueue() - { - int removed = 0; -#if NET7_0_OR_GREATER - foreach (ref var seg in CollectionsMarshal.AsSpan(rcv_buf)) -#else - foreach (var seg in rcv_buf) -#endif - { - // move segments while they are in 'rcv_nxt' sequence order. - // some may still be missing and inserted later, in this case it stops immediately - // because segments always need to be received in the exact sequence order. - if (seg.SegHead.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) - { - // can't remove while iterating. remember how many to remove - // and do it after the loop. - ++removed; - rcv_queue.Enqueue(seg); - // increase sequence number for next segment - rcv_nxt++; - } - else - { - break; - } - } - rcv_buf.RemoveRange(0, removed); - } - - // ikcp_input - // used when you receive a low level packet (e.g. UDP packet) - // => original kcp uses offset=0, we made it a parameter so that high - // level can skip the channel byte more easily - public int Input(Span data) - { - int offset = 0; - int size = data.Length; - uint prev_una = snd_una; - uint maxack = 0; - uint latest_ts = 0; - int flag = 0; - - if (data == null || size < OVERHEAD) return -1; - - while (true) - { - // enough data left to decode segment (aka OVERHEAD bytes)? - if (size < OVERHEAD) break; - - var segHead = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(data.Slice(offset))); - offset += Unsafe.SizeOf(); - uint conv_ = segHead.conv; - byte cmd = segHead.cmd; - byte frg = segHead.frg; - ushort wnd = segHead.wnd; - uint ts = segHead.ts; - uint sn = segHead.sn; - uint una = segHead.una; - uint len = segHead.len; - - // reduce remaining size by what was read - size -= OVERHEAD; - - // enough remaining to read 'len' bytes of the actual payload? - // note: original kcp casts uint len to int for <0 check. - if (size < len || (int)len < 0) return -2; - - // validate command type - if (cmd != CMD_PUSH && cmd != CMD_ACK && - cmd != CMD_WASK && cmd != CMD_WINS) - return -3; - - rmt_wnd = wnd; - ParseUna(una); - ShrinkBuf(); - - if (cmd == CMD_ACK) - { - if (Utils.TimeDiff(current, ts) >= 0) - { - UpdateAck(Utils.TimeDiff(current, ts)); - } - ParseAck(sn); - ShrinkBuf(); - if (flag == 0) - { - flag = 1; - maxack = sn; - latest_ts = ts; - } - else - { - if (Utils.TimeDiff(sn, maxack) > 0) - { -#if !FASTACK_CONSERVE - maxack = sn; - latest_ts = ts; -#else - if (Utils.TimeDiff(ts, latest_ts) > 0) - { - maxack = sn; - latest_ts = ts; - } -#endif - } - } - } - else if (cmd == CMD_PUSH) - { - if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) < 0) - { - AckPush(sn, ts); - if (Utils.TimeDiff(sn, rcv_nxt) >= 0) - { - SegmentStruct seg = new SegmentStruct((int) len,this.kcpSegmentArrayPool); - seg.SegHead = new SegmentHead() - { - conv = conv_, - cmd = cmd, - frg = frg, - wnd = wnd, - ts = ts, - sn = sn, - una = una, - }; - if (len > 0) - { - data.Slice(offset,(int)len).CopyTo(seg.FreeBuffer); - seg.Advance((int)len); - } - ParseData(ref seg); - } - } - } - else if (cmd == CMD_WASK) - { - // ready to send back CMD_WINS in flush - // tell remote my window size - probe |= ASK_TELL; - } - else if (cmd == CMD_WINS) - { - // do nothing - } - else - { - return -3; - } - - offset += (int)len; - size -= (int)len; - } - - if (flag != 0) - { - ParseFastack(maxack, latest_ts); - } - - // cwnd update when packet arrived - if (Utils.TimeDiff(snd_una, prev_una) > 0) - { - if (cwnd < rmt_wnd) - { - if (cwnd < ssthresh) - { - cwnd++; - incr += mss; - } - else - { - if (incr < mss) incr = mss; - incr += (mss * mss) / incr + (mss / 16); - if ((cwnd + 1) * mss <= incr) - { - cwnd = (incr + mss - 1) / ((mss > 0) ? mss : 1); - } - } - if (cwnd > rmt_wnd) - { - cwnd = rmt_wnd; - incr = rmt_wnd * mss; - } - } - } - - return 0; - } - - // flush helper function - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void MakeSpace(ref int size, int space) - { - if (size - RESERVED_BYTE + space > mtu) - { - output(buffer, size); - size = RESERVED_BYTE; - } - } - - // flush helper function - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void FlushBuffer(int size) - { - // flush buffer up to 'offset' (<= MTU) - if (size > RESERVED_BYTE) - { - output(buffer, size); - } - } - - // ikcp_flush - // flush remain ack segments. - // flush may output multiple <= MTU messages from MakeSpace / FlushBuffer. - // the amount of messages depends on the sliding window. - // configured by send/receive window sizes + congestion control. - // with congestion control, the window will be extremely small(!). - public void Flush() - { - int size = RESERVED_BYTE; // amount of bytes to flush. 'buffer ptr' in C. - bool lost = false; // lost segments - - // update needs to be called before flushing - if (!updated) return; - - // kcp only stack allocates a segment here for performance, leaving - // its data buffer null because this segment's data buffer is never - // used. that's fine in C, but in C# our segment is a class so we - // need to allocate and most importantly, not forget to deallocate - // it before returning. - SegmentStruct seg = new SegmentStruct((int)this.mtu,this.kcpSegmentArrayPool); - seg.SegHead.conv = conv; - seg.SegHead.cmd = CMD_ACK; - seg.SegHead.wnd = WndUnused(); - seg.SegHead.una = rcv_nxt; - - // flush acknowledges -#if NET7_0_OR_GREATER - foreach (ref AckItem ack in CollectionsMarshal.AsSpan(acklist)) -#else - foreach (AckItem ack in acklist) -#endif - { - MakeSpace(ref size, OVERHEAD); - // ikcp_ack_get assigns ack[i] to seg.sn, seg.ts - seg.SegHead.sn = ack.serialNumber; - seg.SegHead.ts = ack.timestamp; - seg.Encode(buffer.AsSpan(size), ref size); - } - acklist.Clear(); - - // probe window size (if remote window size equals zero) - if (rmt_wnd == 0) - { - if (probe_wait == 0) - { - probe_wait = PROBE_INIT; - ts_probe = current + probe_wait; - } - else - { - if (Utils.TimeDiff(current, ts_probe) >= 0) - { - if (probe_wait < PROBE_INIT) - probe_wait = PROBE_INIT; - probe_wait += probe_wait / 2; - if (probe_wait > PROBE_LIMIT) - probe_wait = PROBE_LIMIT; - ts_probe = current + probe_wait; - probe |= ASK_SEND; - } - } - } - else - { - ts_probe = 0; - probe_wait = 0; - } - - // flush window probing commands - if ((probe & ASK_SEND) != 0) - { - seg.SegHead.cmd = CMD_WASK; - MakeSpace(ref size, OVERHEAD); - seg.Encode(buffer.AsSpan(size), ref size); - } - - // flush window probing commands - if ((probe & ASK_TELL) != 0) - { - seg.SegHead.cmd = CMD_WINS; - MakeSpace(ref size, OVERHEAD); - seg.Encode(buffer.AsSpan(size), ref size); - } - - probe = 0; - - // calculate the window size which is currently safe to send. - // it's send window, or remote window, whatever is smaller. - // for our max - uint cwnd_ = Math.Min(snd_wnd, rmt_wnd); - - // double negative: if congestion window is enabled: - // limit window size to cwnd. - // - // note this may heavily limit window sizes. - // for our max message size test with super large windows of 32k, - // 'congestion window' limits it down from 32.000 to 2. - if (!nocwnd) cwnd_ = Math.Min(cwnd, cwnd_); - - // move cwnd_ 'window size' messages from snd_queue to snd_buf - // 'snd_nxt' is what we want to send. - // 'snd_una' is what hasn't been acked yet. - // copy up to 'cwnd_' difference between them (sliding window) - while (Utils.TimeDiff(snd_nxt, snd_una + cwnd_) < 0) - { - if (snd_queue.Count == 0) break; - - SegmentStruct newseg = snd_queue.Dequeue(); - - newseg.SegHead.conv = conv; - newseg.SegHead.cmd = CMD_PUSH; - newseg.SegHead.wnd = seg.SegHead.wnd; - newseg.SegHead.ts = current; - newseg.SegHead.sn = snd_nxt; - snd_nxt += 1; // increase sequence number for next segment - newseg.SegHead.una = rcv_nxt; - newseg.resendts = current; - newseg.rto = rx_rto; - newseg.fastack = 0; - newseg.xmit = 0; - snd_buf.Add(newseg); - } - - // calculate resent - uint resent = fastresend > 0 ? (uint)fastresend : 0xffffffff; - uint rtomin = nodelay == 0 ? (uint)rx_rto >> 3 : 0; - - // flush data segments - int change = 0; -#if NET7_0_OR_GREATER - var sndBufArr = CollectionsMarshal.AsSpan(this.snd_buf); - for (int i = 0; i < sndBufArr.Length; i++) - { - ref SegmentStruct segment = ref sndBufArr[i]; -#else - for (int i = 0; i < this.snd_buf.Count; i++) - { - SegmentStruct segment = this.snd_buf[i]; -#endif - bool needsend = false; - - // initial transmit - if (segment.xmit == 0) - { - needsend = true; - segment.xmit++; - segment.rto = this.rx_rto; - segment.resendts = this.current + (uint) segment.rto + rtomin; - } - // RTO - else if (Utils.TimeDiff(this.current, segment.resendts) >= 0) - { - needsend = true; - segment.xmit++; - this.xmit++; - if (this.nodelay == 0) - { - segment.rto += Math.Max(segment.rto, this.rx_rto); - } - else - { - int step = (this.nodelay < 2)? segment.rto : this.rx_rto; - segment.rto += step / 2; - } - - segment.resendts = this.current + (uint) segment.rto; - lost = true; - } - // fast retransmit - else if (segment.fastack >= resent) - { - if (segment.xmit <= this.fastlimit || this.fastlimit <= 0) - { - needsend = true; - segment.xmit++; - segment.fastack = 0; - segment.resendts = this.current + (uint) segment.rto; - change++; - } - } - - if (needsend) - { - segment.SegHead.ts = this.current; - segment.SegHead.wnd = seg.SegHead.wnd; - segment.SegHead.una = this.rcv_nxt; - - int need = OVERHEAD + segment.WrittenCount; - this.MakeSpace(ref size, need); - - segment.Encode(this.buffer.AsSpan(size),ref size); - - if (segment.WrittenCount > 0) - { - segment.WrittenBuffer.CopyTo(this.buffer.AsSpan(size)); - - size += segment.WrittenCount; - } - - // dead link happens if a message was resent N times, but an - // ack was still not received. - if (segment.xmit >= this.dead_link) - { - this.state = -1; - } - } -#if !NET7_0_OR_GREATER - this.snd_buf[i] = segment; -#endif - } - - // kcp stackallocs 'seg'. our C# segment is a class though, so we - // need to properly delete and return it to the pool now that we are - // done with it. - // SegmentDelete(seg); - - seg.Dispose(); - - // flush remaining segments - FlushBuffer(size); - - // update ssthresh - // rate halving, https://tools.ietf.org/html/rfc6937 - if (change > 0) - { - uint inflight = snd_nxt - snd_una; - ssthresh = inflight / 2; - if (ssthresh < THRESH_MIN) - ssthresh = THRESH_MIN; - cwnd = ssthresh + resent; - incr = cwnd * mss; - } - - // congestion control, https://tools.ietf.org/html/rfc5681 - if (lost) - { - // original C uses 'cwnd', not kcp->cwnd! - ssthresh = cwnd_ / 2; - if (ssthresh < THRESH_MIN) - ssthresh = THRESH_MIN; - cwnd = 1; - incr = mss; - } - - if (cwnd < 1) - { - cwnd = 1; - incr = mss; - } - } - - // ikcp_update - // update state (call it repeatedly, every 10ms-100ms), or you can ask - // Check() when to call it again (without Input/Send calling). - // - // 'current' - current timestamp in millisec. pass it to Kcp so that - // Kcp doesn't have to do any stopwatch/deltaTime/etc. code - // - // time as uint, likely to minimize bandwidth. - // uint.max = 4294967295 ms = 1193 hours = 49 days - public void Update(uint currentTimeMilliSeconds) - { - current = currentTimeMilliSeconds; - - // not updated yet? then set updated and last flush time. - if (!updated) - { - updated = true; - ts_flush = current; - } - - // slap is time since last flush in milliseconds - int slap = Utils.TimeDiff(current, ts_flush); - - // hard limit: if 10s elapsed, always flush no matter what - if (slap >= 10000 || slap < -10000) - { - ts_flush = current; - slap = 0; - } - - // last flush is increased by 'interval' each time. - // so slap >= is a strange way to check if interval has elapsed yet. - if (slap >= 0) - { - // increase last flush time by one interval - ts_flush += interval; - - // if last flush is still behind, increase it to current + interval - // if (Utils.TimeDiff(current, ts_flush) >= 0) // original kcp.c - if (current >= ts_flush) // less confusing - { - ts_flush = current + interval; - } - Flush(); - } - } - - // ikcp_check - // Determine when should you invoke update - // Returns when you should invoke update in millisec, if there is no - // input/send calling. you can call update in that time, instead of - // call update repeatly. - // - // Important to reduce unnecessary update invoking. use it to schedule - // update (e.g. implementing an epoll-like mechanism, or optimize update - // when handling massive kcp connections). - public uint Check(uint current_) - { - uint ts_flush_ = ts_flush; - // int tm_flush = 0x7fffffff; original kcp: useless assignment - int tm_packet = 0x7fffffff; - - if (!updated) - { - return current_; - } - - if (Utils.TimeDiff(current_, ts_flush_) >= 10000 || - Utils.TimeDiff(current_, ts_flush_) < -10000) - { - ts_flush_ = current_; - } - - if (Utils.TimeDiff(current_, ts_flush_) >= 0) - { - return current_; - } - - int tm_flush = Utils.TimeDiff(ts_flush_, current_); -#if NET7_0_OR_GREATER - foreach (ref SegmentStruct seg in CollectionsMarshal.AsSpan(this.snd_buf)) -#else - foreach (SegmentStruct seg in snd_buf) -#endif - { - int diff = Utils.TimeDiff(seg.resendts, current_); - if (diff <= 0) - { - return current_; - } - if (diff < tm_packet) tm_packet = diff; - } - - uint minimal = (uint)(tm_packet < tm_flush ? tm_packet : tm_flush); - if (minimal >= interval) minimal = interval; - - return current_ + minimal; - } - - // ikcp_setmtu - // Change MTU (Maximum Transmission Unit) size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetMtu(uint mtu) - { - if (mtu < 50 || mtu < OVERHEAD) - this.ThrowMTUException(); - - buffer = new byte[(mtu + OVERHEAD) * 3]; - this.mtu = mtu; - mss = mtu - OVERHEAD; - } - - // ikcp_interval - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetInterval(uint interval) - { - // clamp interval between 10 and 5000 - if (interval > 5000) interval = 5000; - else if (interval < 10) interval = 10; - this.interval = interval; - } - - // ikcp_nodelay - // configuration: https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration - // nodelay : Whether nodelay mode is enabled, 0 is not enabled; 1 enabled. - // interval :Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms. - // resend :Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission) - // nc :Whether to turn off flow control, 0 represents “Do not turn off†by default, 1 represents “Turn offâ€. - // Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0); - // Turbo Mode: ikcp_nodelay(kcp, 1, 10, 2, 1); - public void SetNoDelay(uint nodelay, uint interval = INTERVAL, int resend = 0, bool nocwnd = false) - { - this.nodelay = nodelay; - if (nodelay != 0) - { - rx_minrto = RTO_NDL; - } - else - { - rx_minrto = RTO_MIN; - } - - // clamp interval between 10 and 5000 - if (interval > 5000) interval = 5000; - else if (interval < 10) interval = 10; - this.interval = interval; - - if (resend >= 0) - { - fastresend = resend; - } - - this.nocwnd = nocwnd; - } - - // ikcp_wndsize - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetWindowSize(uint sendWindow, uint receiveWindow) - { - if (sendWindow > 0) - { - snd_wnd = sendWindow; - } - - if (receiveWindow > 0) - { - // must >= max fragment size - rcv_wnd = Math.Max(receiveWindow, WND_RCV); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetMinrto(int minrto) - { - this.rx_minrto = minrto; - } - - public void InitArrayPool(int maxArrayLength, int maxArraysPerBucket) - { - this.kcpSegmentArrayPool = ArrayPool.Create(maxArrayLength,maxArraysPerBucket); - } - - [DoesNotReturn] - private void ThrowMTUException() - { - throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD"); - } - - [DoesNotReturn] - private void ThrowFrgCountException(int len, int count) - { - throw new Exception($"Send len={len} requires {count} fragments, but kcp can only handle up to {FRG_MAX} fragments."); - } - } -} diff --git a/Assets/GameScripts/ThirdParty/Kcp/Kcp.cs.meta b/Assets/GameScripts/ThirdParty/Kcp/Kcp.cs.meta deleted file mode 100644 index 94c965fa..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Kcp.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5ec0efad891a843b6bf70a38240a5d43 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs b/Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs deleted file mode 100644 index 420551d8..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace ET -{ - public partial class Kcp - { - public const int OneM = 1024 * 1024; - public const int InnerMaxWaitSize = 1024 * 1024; - public const int OuterMaxWaitSize = 1024 * 1024; - - public struct SegmentHead - { - public uint conv; - public byte cmd; - public byte frg; - public ushort wnd; - public uint ts; - public uint sn; - public uint una; - public uint len; - } - } - - -} \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs.meta b/Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs.meta deleted file mode 100644 index 7e1e57c0..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/KcpPatial.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d56686dda31caa1438db793a366b87d8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Kcp/Pool.cs b/Assets/GameScripts/ThirdParty/Kcp/Pool.cs deleted file mode 100644 index ff7bc441..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Pool.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Pool to avoid allocations (from libuv2k & Mirror) -using System; -using System.Buffers; -using System.Collections.Generic; - -namespace ET -{ - public class Pool - { - // Mirror is single threaded, no need for concurrent collections - readonly Stack objects = new Stack(); - - // some types might need additional parameters in their constructor, so - // we use a Func generator - readonly Func objectGenerator; - - // some types might need additional cleanup for returned objects - readonly Action objectResetter; - - public Pool(Func objectGenerator, Action objectResetter, int initialCapacity) - { - this.objectGenerator = objectGenerator; - this.objectResetter = objectResetter; - - // allocate an initial pool so we have fewer (if any) - // allocations in the first few frames (or seconds). - for (int i = 0; i < initialCapacity; ++i) - objects.Push(objectGenerator()); - } - - // take an element from the pool, or create a new one if empty - public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); - - // return an element to the pool - public void Return(T item) - { - if (this.Count > 1000) - { - return; - } - objectResetter(item); - objects.Push(item); - } - - // clear the pool - public void Clear() => objects.Clear(); - - // count to see how many objects are in the pool. useful for tests. - public int Count => objects.Count; - } -} diff --git a/Assets/GameScripts/ThirdParty/Kcp/Segment.cs b/Assets/GameScripts/ThirdParty/Kcp/Segment.cs deleted file mode 100644 index df50fef0..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Segment.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace ET -{ - // KCP Segment Definition - - internal struct SegmentStruct:IDisposable - { - public Kcp.SegmentHead SegHead; - public uint resendts; - public int rto; - public uint fastack; - public uint xmit; - - private byte[] buffer; - - private ArrayPool arrayPool; - - public bool IsNull => this.buffer == null; - - public int WrittenCount - { - get => (int) this.SegHead.len; - private set => this.SegHead.len = (uint) value; - } - - public Span WrittenBuffer => this.buffer.AsSpan(0, (int) this.SegHead.len); - - public Span FreeBuffer => this.buffer.AsSpan(WrittenCount); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SegmentStruct(int size, ArrayPool arrayPool) - { - this.arrayPool = arrayPool; - buffer = arrayPool.Rent(size); - this.SegHead = new Kcp.SegmentHead() { len = 0 }; - this.SegHead = default; - this.resendts = default; - this.rto = default; - this.fastack = default; - this.xmit = default; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Encode(Span data, ref int size) - { - Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data),this.SegHead); - size += Unsafe.SizeOf(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int count) - { - this.WrittenCount += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - arrayPool.Return(this.buffer); - } - } - - // internal class Segment - // { - // internal uint conv; // conversation - // internal byte cmd; // command, e.g. Kcp.CMD_ACK etc. - // // fragment (sent as 1 byte). - // // 0 if unfragmented, otherwise fragment numbers in reverse: N,..,32,1,0 - // // this way the first received segment tells us how many fragments there are. - // internal byte frg; - // internal ushort wnd; // window size that the receive can currently receive - // internal uint ts; // timestamp - // internal uint sn; // sequence number - // internal uint una; - // internal uint resendts; // resend timestamp - // internal int rto; - // internal uint fastack; - // internal uint xmit; // retransmit count - // - // internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF); - // - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // internal int Encode(byte[] ptr, int offset) - // { - // int previousPosition = offset; - // - // var segHead = new Kcp.SegmentHead() - // { - // conv = this.conv, - // cmd = (byte) this.cmd, - // frg = (byte) frg, - // wnd = (ushort) this.wnd, - // ts = this.ts, - // sn = this.sn, - // una = this.una, - // len = (uint) this.data.Position, - // }; - // - // Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(ptr.AsSpan(offset)),segHead); - // offset+=Unsafe.SizeOf(); - // - // int written = offset - previousPosition; - // return written; - // } - // - // // reset to return a fresh segment to the pool - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // internal void Reset() - // { - // conv = 0; - // cmd = 0; - // frg = 0; - // wnd = 0; - // ts = 0; - // sn = 0; - // una = 0; - // rto = 0; - // xmit = 0; - // resendts = 0; - // fastack = 0; - // - // // keep buffer for next pool usage, but reset length (= bytes written) - // data.SetLength(0); - // } - // } -} diff --git a/Assets/GameScripts/ThirdParty/Kcp/Segment.cs.meta b/Assets/GameScripts/ThirdParty/Kcp/Segment.cs.meta deleted file mode 100644 index 35c21cd6..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Segment.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 024416c725996b340bd8342fb1064e52 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Kcp/Utils.cs b/Assets/GameScripts/ThirdParty/Kcp/Utils.cs deleted file mode 100644 index d1d17963..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Utils.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace ET -{ - public static partial class Utils - { - // Clamp so we don't have to depend on UnityEngine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Clamp(int value, int min, int max) - { - if (value < min) return min; - if (value > max) return max; - return value; - } - - // // encode 8 bits unsigned int - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Encode8u(byte[] p, int offset, byte value) - // { - // p[0 + offset] = value; - // return 1; - // } - // - // // decode 8 bits unsigned int - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Decode8u(byte[] p, int offset, out byte value) - // { - // value = p[0 + offset]; - // return 1; - // } - // - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Decode8u(ReadOnlySpan data,int offset,out byte value) - // { - // value = data[offset]; - // return 1; - // } - // - // // encode 16 bits unsigned int (lsb) - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Encode16U(byte[] p, int offset, ushort value) - // { - // p[0 + offset] = (byte)(value >> 0); - // p[1 + offset] = (byte)(value >> 8); - // return 2; - // } - // - // // decode 16 bits unsigned int (lsb) - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Decode16U(byte[] p, int offset, out ushort value) - // { - // ushort result = 0; - // result |= p[0 + offset]; - // result |= (ushort)(p[1 + offset] << 8); - // value = result; - // return 2; - // } - // - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Decode16U(ReadOnlySpan data, int offset, out ushort value) - // { - // value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(data.Slice(offset))); - // return 2; - // } - // - // // encode 32 bits unsigned int (lsb) - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Encode32U(byte[] p, int offset, uint value) - // { - // p[0 + offset] = (byte)(value >> 0); - // p[1 + offset] = (byte)(value >> 8); - // p[2 + offset] = (byte)(value >> 16); - // p[3 + offset] = (byte)(value >> 24); - // return 4; - // } - // - // // decode 32 bits unsigned int (lsb) - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Decode32U(byte[] p, int offset, out uint value) - // { - // uint result = 0; - // result |= p[0 + offset]; - // result |= (uint)(p[1 + offset] << 8); - // result |= (uint)(p[2 + offset] << 16); - // result |= (uint)(p[3 + offset] << 24); - // value = result; - // return 4; - // } - // - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // public static int Decode32U(ReadOnlySpan data, int offset, out uint value) - // { - // value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(data.Slice(offset))); - // return 4; - // } - - // timediff was a macro in original Kcp. let's inline it if possible. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TimeDiff(uint later, uint earlier) - { - return (int)(later - earlier); - } - } -} diff --git a/Assets/GameScripts/ThirdParty/Kcp/Utils.cs.meta b/Assets/GameScripts/ThirdParty/Kcp/Utils.cs.meta deleted file mode 100644 index 0b4ca3fe..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp/Utils.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 065051fee12dc95449d2eb1dd337adbe -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Protobuf-net.meta b/Assets/GameScripts/ThirdParty/Protobuf-net.meta new file mode 100644 index 00000000..3787b64e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/Protobuf-net.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6962933a8da958249a243c71feddbd6e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Protobuf-net/BclHelpers.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/BclHelpers.cs new file mode 100644 index 00000000..bcff9a4e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/BclHelpers.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/BclHelpers.cs.meta new file mode 100644 index 00000000..da3a37a7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/BufferExtension.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/BufferExtension.cs new file mode 100644 index 00000000..ea428dd9 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/BufferExtension.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/BufferExtension.cs.meta new file mode 100644 index 00000000..4a395911 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/BufferPool.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/BufferPool.cs new file mode 100644 index 00000000..8ad3d1ab --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/BufferPool.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/BufferPool.cs.meta new file mode 100644 index 00000000..2870b8c7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/CallbackAttributes.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/CallbackAttributes.cs new file mode 100644 index 00000000..1adb8e5a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/CallbackAttributes.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/CallbackAttributes.cs.meta new file mode 100644 index 00000000..7cf81a42 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler.meta new file mode 100644 index 00000000..9de78a67 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2cdd9eb2afa3ed24480a6035f507aad4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerContext.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerContext.cs new file mode 100644 index 00000000..6100200e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerContext.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerContext.cs.meta new file mode 100644 index 00000000..b40174bd --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerDelegates.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerDelegates.cs new file mode 100644 index 00000000..e7f0508f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerDelegates.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/CompilerDelegates.cs.meta new file mode 100644 index 00000000..c9fedb05 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/Local.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/Local.cs new file mode 100644 index 00000000..fd3dfa9a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/Local.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Compiler/Local.cs.meta new file mode 100644 index 00000000..2767c29b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/DataFormat.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/DataFormat.cs new file mode 100644 index 00000000..4d97b4fc --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/DataFormat.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/DataFormat.cs.meta new file mode 100644 index 00000000..644abad1 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.Serializable.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.Serializable.cs new file mode 100644 index 00000000..0fd671fe --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.Serializable.cs.meta new file mode 100644 index 00000000..f6163312 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.cs new file mode 100644 index 00000000..7cc8cf8e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/DiscriminatedUnion.cs.meta new file mode 100644 index 00000000..3268148b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Extensible.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Extensible.cs new file mode 100644 index 00000000..6bd528bc --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Extensible.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Extensible.cs.meta new file mode 100644 index 00000000..ac4ec367 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ExtensibleUtil.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ExtensibleUtil.cs new file mode 100644 index 00000000..9cc16139 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ExtensibleUtil.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ExtensibleUtil.cs.meta new file mode 100644 index 00000000..ea420c6f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/GlobalSuppressions.cs b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Helpers.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Helpers.cs.meta new file mode 100644 index 00000000..d67edef1 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IExtensible.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/IExtensible.cs new file mode 100644 index 00000000..b7c0b578 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IExtensible.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/IExtensible.cs.meta new file mode 100644 index 00000000..3c8f29a5 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IExtension.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/IExtension.cs new file mode 100644 index 00000000..0a137aca --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IExtension.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/IExtension.cs.meta new file mode 100644 index 00000000..d5da3406 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoInputT.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoInputT.cs new file mode 100644 index 00000000..6eaa0ce2 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoInputT.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoInputT.cs.meta new file mode 100644 index 00000000..a80bc62c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoOutputT.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoOutputT.cs new file mode 100644 index 00000000..1c7dd420 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoOutputT.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/IProtoOutputT.cs.meta new file mode 100644 index 00000000..a6e7d866 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ImplicitFields.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ImplicitFields.cs new file mode 100644 index 00000000..211abdd0 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ImplicitFields.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ImplicitFields.cs.meta new file mode 100644 index 00000000..6da3beff --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/KeyValuePairProxy.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/KeyValuePairProxy.cs new file mode 100644 index 00000000..0da5761f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/KeyValuePairProxy.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/KeyValuePairProxy.cs.meta new file mode 100644 index 00000000..c74b2841 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta.meta new file mode 100644 index 00000000..5f17bddd --- /dev/null +++ b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a70a85c13dddce74d9a6395c440c9156 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/AttributeMap.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/AttributeMap.cs new file mode 100644 index 00000000..5bab9422 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/AttributeMap.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/AttributeMap.cs.meta new file mode 100644 index 00000000..d92ef3ad --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/BasicList.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/BasicList.cs new file mode 100644 index 00000000..d1308f3e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/BasicList.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/BasicList.cs.meta new file mode 100644 index 00000000..3304e307 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/CallbackSet.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/CallbackSet.cs new file mode 100644 index 00000000..8b085850 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/CallbackSet.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/CallbackSet.cs.meta new file mode 100644 index 00000000..0c6da409 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/MetaType.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/MetaType.cs new file mode 100644 index 00000000..8d9bed66 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/MetaType.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/MetaType.cs.meta new file mode 100644 index 00000000..edc2cad7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ProtoSyntax.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ProtoSyntax.cs new file mode 100644 index 00000000..ab90d5b8 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ProtoSyntax.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ProtoSyntax.cs.meta new file mode 100644 index 00000000..23200250 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/RuntimeTypeModel.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/RuntimeTypeModel.cs new file mode 100644 index 00000000..05dfcf18 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/RuntimeTypeModel.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/RuntimeTypeModel.cs.meta new file mode 100644 index 00000000..231a0285 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/SubType.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/SubType.cs new file mode 100644 index 00000000..72c81265 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/SubType.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/SubType.cs.meta new file mode 100644 index 00000000..fb7fe45a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeAddedEventArgs.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeAddedEventArgs.cs new file mode 100644 index 00000000..399c638a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeAddedEventArgs.cs.meta new file mode 100644 index 00000000..8ac9b8f4 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeFormatEventArgs.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeFormatEventArgs.cs new file mode 100644 index 00000000..3db09993 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeFormatEventArgs.cs.meta new file mode 100644 index 00000000..a21c2abf --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.InputOutput.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.InputOutput.cs new file mode 100644 index 00000000..9b023a6e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.InputOutput.cs.meta new file mode 100644 index 00000000..80015e59 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.cs new file mode 100644 index 00000000..1867cf2e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/TypeModel.cs.meta new file mode 100644 index 00000000..cc869c39 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ValueMember.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ValueMember.cs new file mode 100644 index 00000000..9566312c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ValueMember.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Meta/ValueMember.cs.meta new file mode 100644 index 00000000..d3eeb789 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/NetObjectCache.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/NetObjectCache.cs new file mode 100644 index 00000000..8e83549a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/NetObjectCache.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/NetObjectCache.cs.meta new file mode 100644 index 00000000..862acc01 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/PrefixStyle.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/PrefixStyle.cs new file mode 100644 index 00000000..0ebef04f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/PrefixStyle.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/PrefixStyle.cs.meta new file mode 100644 index 00000000..a955c1fb --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoContractAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoContractAttribute.cs new file mode 100644 index 00000000..e2e8054a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoContractAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoContractAttribute.cs.meta new file mode 100644 index 00000000..d0006881 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoConverterAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoConverterAttribute.cs new file mode 100644 index 00000000..b75bb803 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoConverterAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoConverterAttribute.cs.meta new file mode 100644 index 00000000..323a3a46 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoEnumAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoEnumAttribute.cs new file mode 100644 index 00000000..1d826454 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoEnumAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoEnumAttribute.cs.meta new file mode 100644 index 00000000..a5cada98 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoException.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoException.cs new file mode 100644 index 00000000..f502527b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoException.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoException.cs.meta new file mode 100644 index 00000000..28099e55 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIgnoreAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIgnoreAttribute.cs new file mode 100644 index 00000000..775674e9 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIgnoreAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIgnoreAttribute.cs.meta new file mode 100644 index 00000000..00afe26e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIncludeAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIncludeAttribute.cs new file mode 100644 index 00000000..bb83ef78 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIncludeAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoIncludeAttribute.cs.meta new file mode 100644 index 00000000..edebcb5d --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMapAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMapAttribute.cs new file mode 100644 index 00000000..e85441a0 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMapAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMapAttribute.cs.meta new file mode 100644 index 00000000..cf765aef --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMemberAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMemberAttribute.cs new file mode 100644 index 00000000..e5ab8962 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMemberAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoMemberAttribute.cs.meta new file mode 100644 index 00000000..2f3dfc97 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoReader.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoReader.cs new file mode 100644 index 00000000..3ea9bf9e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoReader.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoReader.cs.meta new file mode 100644 index 00000000..0826a162 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoWriter.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoWriter.cs new file mode 100644 index 00000000..23fa42d5 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoWriter.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ProtoWriter.cs.meta new file mode 100644 index 00000000..5b91b672 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/SerializationContext.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/SerializationContext.cs new file mode 100644 index 00000000..80b76afa --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/SerializationContext.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/SerializationContext.cs.meta new file mode 100644 index 00000000..9bd8dccb --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializer.cs new file mode 100644 index 00000000..8a4c38af --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializer.cs.meta new file mode 100644 index 00000000..63cf57db --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers.meta new file mode 100644 index 00000000..569acba6 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90bd17a736284764ca22da41661472de +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ArrayDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ArrayDecorator.cs new file mode 100644 index 00000000..cad005f1 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ArrayDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ArrayDecorator.cs.meta new file mode 100644 index 00000000..6958590e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BlobSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BlobSerializer.cs new file mode 100644 index 00000000..40b2b89e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BlobSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BlobSerializer.cs.meta new file mode 100644 index 00000000..49dd403c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BooleanSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BooleanSerializer.cs new file mode 100644 index 00000000..c64886ae --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BooleanSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/BooleanSerializer.cs.meta new file mode 100644 index 00000000..f9823840 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ByteSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ByteSerializer.cs new file mode 100644 index 00000000..e44a83c0 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ByteSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ByteSerializer.cs.meta new file mode 100644 index 00000000..23a58b1c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CharSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CharSerializer.cs new file mode 100644 index 00000000..3bc30d04 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CharSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CharSerializer.cs.meta new file mode 100644 index 00000000..0424efcf --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CompiledSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CompiledSerializer.cs new file mode 100644 index 00000000..1ec30273 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CompiledSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/CompiledSerializer.cs.meta new file mode 100644 index 00000000..ddef8756 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DateTimeSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DateTimeSerializer.cs new file mode 100644 index 00000000..9755df9e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DateTimeSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DateTimeSerializer.cs.meta new file mode 100644 index 00000000..6757f0c0 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DecimalSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DecimalSerializer.cs new file mode 100644 index 00000000..1edc6219 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DecimalSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DecimalSerializer.cs.meta new file mode 100644 index 00000000..f8e097a8 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DefaultValueDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DefaultValueDecorator.cs new file mode 100644 index 00000000..895d0c4b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DefaultValueDecorator.cs.meta new file mode 100644 index 00000000..7cbd6ed3 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DoubleSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DoubleSerializer.cs new file mode 100644 index 00000000..8b25523a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DoubleSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/DoubleSerializer.cs.meta new file mode 100644 index 00000000..cdba0a78 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/EnumSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/EnumSerializer.cs new file mode 100644 index 00000000..78cb78a2 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/EnumSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/EnumSerializer.cs.meta new file mode 100644 index 00000000..b58d8661 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/FieldDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/FieldDecorator.cs new file mode 100644 index 00000000..26c0452b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/FieldDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/FieldDecorator.cs.meta new file mode 100644 index 00000000..63065a7d --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/GuidSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/GuidSerializer.cs new file mode 100644 index 00000000..27556d5b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/GuidSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/GuidSerializer.cs.meta new file mode 100644 index 00000000..7eeb096c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoSerializer.cs new file mode 100644 index 00000000..59e8cc2e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoSerializer.cs.meta new file mode 100644 index 00000000..40d402dd --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoTypeSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoTypeSerializer.cs new file mode 100644 index 00000000..da1439bb --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/IProtoTypeSerializer.cs.meta new file mode 100644 index 00000000..d4c96cf2 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ISerializerProxy.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ISerializerProxy.cs new file mode 100644 index 00000000..3ab2cb87 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ISerializerProxy.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ISerializerProxy.cs.meta new file mode 100644 index 00000000..aa3cdfa4 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs new file mode 100644 index 00000000..918d1fd9 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ImmutableCollectionDecorator.cs.meta new file mode 100644 index 00000000..f8d90124 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int16Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int16Serializer.cs new file mode 100644 index 00000000..eac4eb42 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int16Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int16Serializer.cs.meta new file mode 100644 index 00000000..546159fe --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int32Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int32Serializer.cs new file mode 100644 index 00000000..204880ef --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int32Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int32Serializer.cs.meta new file mode 100644 index 00000000..1be4c7e3 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int64Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int64Serializer.cs new file mode 100644 index 00000000..2791a1e5 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int64Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/Int64Serializer.cs.meta new file mode 100644 index 00000000..8dbba5ea --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ListDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ListDecorator.cs new file mode 100644 index 00000000..82bb128e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ListDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ListDecorator.cs.meta new file mode 100644 index 00000000..a5980a26 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MapDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MapDecorator.cs new file mode 100644 index 00000000..033cf26a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MapDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MapDecorator.cs.meta new file mode 100644 index 00000000..51c45254 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs new file mode 100644 index 00000000..3ee80a55 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/MemberSpecifiedDecorator.cs.meta new file mode 100644 index 00000000..f2d61875 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NetObjectSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NetObjectSerializer.cs new file mode 100644 index 00000000..c3d685b8 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NetObjectSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NetObjectSerializer.cs.meta new file mode 100644 index 00000000..f53dfadc --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NullDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NullDecorator.cs new file mode 100644 index 00000000..52db14ce --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NullDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/NullDecorator.cs.meta new file mode 100644 index 00000000..4fb2a35c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ParseableSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ParseableSerializer.cs new file mode 100644 index 00000000..9a4bb07c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ParseableSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ParseableSerializer.cs.meta new file mode 100644 index 00000000..9ee5ec19 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/PropertyDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/PropertyDecorator.cs new file mode 100644 index 00000000..8b0a0147 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/PropertyDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/PropertyDecorator.cs.meta new file mode 100644 index 00000000..5aca94d0 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ProtoDecoratorBase.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ProtoDecoratorBase.cs new file mode 100644 index 00000000..e7f2b34b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ProtoDecoratorBase.cs.meta new file mode 100644 index 00000000..92acdfcf --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ReflectedUriDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ReflectedUriDecorator.cs new file mode 100644 index 00000000..44edef07 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/ReflectedUriDecorator.cs.meta new file mode 100644 index 00000000..2441cdf0 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SByteSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SByteSerializer.cs new file mode 100644 index 00000000..81d233e1 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SByteSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SByteSerializer.cs.meta new file mode 100644 index 00000000..7d71fefa --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SingleSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SingleSerializer.cs new file mode 100644 index 00000000..c5ade137 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SingleSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SingleSerializer.cs.meta new file mode 100644 index 00000000..ee64e5ac --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/StringSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/StringSerializer.cs new file mode 100644 index 00000000..399b4bb4 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/StringSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/StringSerializer.cs.meta new file mode 100644 index 00000000..697d8c29 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SubItemSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SubItemSerializer.cs new file mode 100644 index 00000000..58015aad --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SubItemSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SubItemSerializer.cs.meta new file mode 100644 index 00000000..aaeac6f7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SurrogateSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SurrogateSerializer.cs new file mode 100644 index 00000000..86275eb8 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SurrogateSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SurrogateSerializer.cs.meta new file mode 100644 index 00000000..39cf4192 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SystemTypeSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SystemTypeSerializer.cs new file mode 100644 index 00000000..4b1656da --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/SystemTypeSerializer.cs.meta new file mode 100644 index 00000000..4f43a1fe --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TagDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TagDecorator.cs new file mode 100644 index 00000000..509b8a0f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TagDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TagDecorator.cs.meta new file mode 100644 index 00000000..e522eda7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TimeSpanSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TimeSpanSerializer.cs new file mode 100644 index 00000000..4c8b828e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TimeSpanSerializer.cs.meta new file mode 100644 index 00000000..013bd6ef --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TupleSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TupleSerializer.cs new file mode 100644 index 00000000..b6f9c696 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TupleSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TupleSerializer.cs.meta new file mode 100644 index 00000000..df99bbe6 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TypeSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TypeSerializer.cs new file mode 100644 index 00000000..d851b47f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TypeSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/TypeSerializer.cs.meta new file mode 100644 index 00000000..0b9bc498 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt16Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt16Serializer.cs new file mode 100644 index 00000000..ff9f89b7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt16Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt16Serializer.cs.meta new file mode 100644 index 00000000..3e94120a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt32Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt32Serializer.cs new file mode 100644 index 00000000..08b4f4bb --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt32Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt32Serializer.cs.meta new file mode 100644 index 00000000..342cdec7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt64Serializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt64Serializer.cs new file mode 100644 index 00000000..8577eddc --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt64Serializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UInt64Serializer.cs.meta new file mode 100644 index 00000000..72452d9c --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UriDecorator.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UriDecorator.cs new file mode 100644 index 00000000..d34ac2df --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UriDecorator.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/Serializers/UriDecorator.cs.meta new file mode 100644 index 00000000..0095ee9f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel.meta new file mode 100644 index 00000000..36e1d951 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af34a7ba57dbd6340b8d3fa0bfdbd0a1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs new file mode 100644 index 00000000..928207e7 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorAttribute.cs.meta new file mode 100644 index 00000000..1facc701 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs new file mode 100644 index 00000000..56edb792 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoBehaviorExtensionElement.cs.meta new file mode 100644 index 00000000..7850781f --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs new file mode 100644 index 00000000..9bcfb995 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoEndpointBehavior.cs.meta new file mode 100644 index 00000000..23ab7835 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs new file mode 100644 index 00000000..9d5f02c6 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/ProtoOperationBehavior.cs.meta new file mode 100644 index 00000000..3bd6fe4e --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/XmlProtoSerializer.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/XmlProtoSerializer.cs new file mode 100644 index 00000000..23959eaf --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/ServiceModel/XmlProtoSerializer.cs.meta new file mode 100644 index 00000000..d564f13b --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/SubItemToken.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/SubItemToken.cs new file mode 100644 index 00000000..51f4a24a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/SubItemToken.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/SubItemToken.cs.meta new file mode 100644 index 00000000..75435a12 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/WireType.cs b/Assets/GameScripts/ThirdParty/Protobuf-net/WireType.cs new file mode 100644 index 00000000..ab4fa205 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/WireType.cs.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/WireType.cs.meta new file mode 100644 index 00000000..25660263 --- /dev/null +++ b/Assets/GameScripts/ThirdParty/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/Assets/GameScripts/ThirdParty/Protobuf-net/protobuf-net.csproj.meta b/Assets/GameScripts/ThirdParty/Protobuf-net/protobuf-net.csproj.meta new file mode 100644 index 00000000..0950983a --- /dev/null +++ b/Assets/GameScripts/ThirdParty/Protobuf-net/protobuf-net.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b9128b665b538746a11489aee369030 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Recast/Recast.cs b/Assets/GameScripts/ThirdParty/Recast/Recast.cs index ae074b3a..ac863f30 100644 --- a/Assets/GameScripts/ThirdParty/Recast/Recast.cs +++ b/Assets/GameScripts/ThirdParty/Recast/Recast.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; -namespace ET +namespace TEngine { public static class Recast { diff --git a/DotNet/App/NLog.config b/DotNet/App/NLog.config new file mode 100644 index 00000000..0d952925 --- /dev/null +++ b/DotNet/App/NLog.config @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DotNet/App/NLog.xsd b/DotNet/App/NLog.xsd new file mode 100644 index 00000000..63c9a0cc --- /dev/null +++ b/DotNet/App/NLog.xsd @@ -0,0 +1,3483 @@ + + + + + + + + + + + + + + + Watch config file for changes and reload automatically. + + + + + Print internal NLog messages to the console. Default value is: false + + + + + Print internal NLog messages to the console error output. Default value is: false + + + + + Write internal NLog messages to the specified file. + + + + + Log level threshold for internal log messages. Default value is: Info. + + + + + Global log level threshold for application log messages. Messages below this level won't be logged. + + + + + Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production! + + + + + Throw an exception when there is a configuration error. If not set, determined by throwExceptions. + + + + + Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false. + + + + + Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false. + + + + + Write timestamps for internal NLog messages. Default value is: true. + + + + + Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false. + + + + + Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty. + + + + + + + + + + + + + + Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). + + + + + + + + + + + + + + + + + Prefix for targets/layout renderers/filters/conditions loaded from this assembly. + + + + + Load NLog extensions from the specified file (*.dll) + + + + + Load NLog extensions from the specified assembly. Assembly name should be fully qualified. + + + + + + + + + + Filter on the name of the logger. May include wildcard characters ('*' or '?'). + + + + + Comma separated list of levels that this rule matches. + + + + + Minimum level that this rule matches. + + + + + Maximum level that this rule matches. + + + + + Level that this rule matches. + + + + + Comma separated list of target names. + + + + + Ignore further rules if this one matches. + + + + + Enable this rule. Note: disabled rules aren't available from the API. + + + + + Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName. + + + + + Loggers matching will be restricted to specified minimum level for following rules. + + + + + + + + + + + + + + + Default action if none of the filters match. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file. + + + + + Ignore any errors in the include file. + + + + + + + + Variable value. Note, the 'value' attribute has precedence over this one. + + + + + + Variable name. + + + + + Variable value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Action to be taken when the lazy writer thread request queue count exceeds the set limit. + + + + + Limit on the number of requests in the lazy writer thread request queue. + + + + + Number of log events that should be processed in a batch by the lazy writer thread. + + + + + Whether to use the locking queue, instead of a lock-free concurrent queue + + + + + Number of batches of P:NLog.Targets.Wrappers.AsyncTargetWrapper.BatchSize to write before yielding into P:NLog.Targets.Wrappers.AsyncTargetWrapper.TimeToSleepBetweenBatches + + + + + Time in milliseconds to sleep between batches. (1 or less means trigger on new activity) + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Delay the flush until the LogEvent has been confirmed as written + + + + + Condition expression. Log events who meet this condition will cause a flush on the wrapped target. + + + + + Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events to be buffered. + + + + + Action to take if the buffer overflows. + + + + + Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. + + + + + Indicates whether to use sliding timeout. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Separator for T:NLog.ScopeContext operation-states-stack. + + + + + Stack separator for log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Whether to include the contents of the T:NLog.ScopeContext properties-dictionary. + + + + + Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Instance of T:NLog.Layouts.Log4JXmlEventLayout that is used to format log messages. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Action that should be taken, when more connections than P:NLog.Targets.NetworkTarget.MaxConnections. + + + + + SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Action that should be taken, when more pending messages than P:NLog.Targets.NetworkTarget.MaxQueueSize. + + + + + Action that should be taken if the message is larger than P:NLog.Targets.NetworkTarget.MaxMessageSize + + + + + Maximum queue size for a single connection. Requires P:NLog.Targets.NetworkTarget.KeepConnection = true + + + + + Network address. + + + + + Indicates whether to keep connection open whenever possible. + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Size of the connection cache (number of connections which are kept alive). Requires P:NLog.Targets.NetworkTarget.KeepConnection = true + + + + + Maximum simultaneous connections. Requires P:NLog.Targets.NetworkTarget.KeepConnection = false + + + + + Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes. + + + + + Skip compression when protocol payload is below limit to reduce overhead in cpu-usage and additional headers + + + + + Maximum message size in bytes. On limit breach then P:NLog.Targets.NetworkTarget.OnOverflow action is activated. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message P:NLog.Targets.NetworkTarget.NewLine. + + + + + Indicates whether to append newline at the end of log message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Viewer parameter name. + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + Enables output using ANSI Color Codes + + + + + The encoding for writing messages to the T:System.Console. + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + Indicates whether to auto-flush after M:System.Console.WriteLine + + + + + Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true + + + + + Indicates whether to use default row highlighting rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Background color. + + + + + Condition that must be met in order to set the specified foreground and background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + Background color. + + + + + Compile the P:NLog.Targets.ConsoleWordHighlightingRule.Regex? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + + + + + Condition that must be met before scanning the row for highlight of words + + + + + Foreground color. + + + + + Indicates whether to ignore case when comparing texts. + + + + + Regular expression to be matched. You must specify either text or regex. + + + + + Text to be matched. You must specify either text or regex. + + + + + Indicates whether to match whole words only. + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-flush after M:System.Console.WriteLine + + + + + Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + The encoding for writing messages to the T:System.Console. + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + Whether to activate internal buffering to allow batch writing, instead of using M:System.Console.WriteLine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. + + + + + Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. + + + + + Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. + + + + + Name of the connection string (as specified in <connectionStrings> configuration section. + + + + + Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. + + + + + Indicates whether to keep the database connection open between the log events. + + + + + Name of the database provider. + + + + + Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. + + + + + Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. + + + + + Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance. + + + + + Text of the SQL command to be run on each log level. + + + + + Type of the SQL command to be run on each log level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Convert format of the property value + + + + + Culture used for parsing property string-value for type-conversion + + + + + Value to assign on the object-property + + + + + Name for the object-property + + + + + Type of the object-property + + + + + + + + + + + + + + Type of the command. + + + + + Connection string to run the command against. If not provided, connection string from the target is used. + + + + + Indicates whether to ignore failures. + + + + + Command text. + + + + + + + + + + + + + + + + + + + + Database parameter name. + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Database parameter DbType. + + + + + Database parameter size. + + + + + Database parameter precision. + + + + + Database parameter scale. + + + + + Type of the parameter. + + + + + Fallback value when result value is not available + + + + + Convert format of the database parameter value. + + + + + Culture used for parsing parameter string-value for type-conversion + + + + + Whether empty value should translate into DbNull. Requires database column to allow NULL values. + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Layout that renders event Category. + + + + + Optional entry type. When not set, or when not convertible to T:System.Diagnostics.EventLogEntryType then determined by T:NLog.LogLevel + + + + + Layout that renders event ID. + + + + + Name of the Event Log to write to. This can be System, Application or any user-defined name. + + + + + Name of the machine on which Event Log service is running. + + + + + Maximum Event log size in kilobytes. + + + + + Message length limit to write to the Event Log. + + + + + Value to be used as the event Source. + + + + + Action to take if the message is larger than the P:NLog.Targets.EventLogTarget.MaxMessageLength option. + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to return to the first target after any successful write. + + + + + Whether to enable batching, but fallback will be handled individually + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Name of the file to write to. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether the footer should be written only when the file is archived. + + + + + Maximum number of archive files that should be kept. + + + + + Maximum days of archive files that should be kept. + + + + + Value of the file size threshold to archive old log file on startup. + + + + + Indicates whether to archive old log file on startup. + + + + + Indicates whether to compress archive files into the zip archive format. + + + + + Name of the file to be used for an archive. + + + + + Is the P:NLog.Targets.FileTarget.ArchiveFileName an absolute or relative path? + + + + + Indicates whether to automatically archive log files every time the specified time passes. + + + + + Value specifying the date format to use when archiving files. + + + + + Size in bytes above which log files will be automatically archived. + + + + + Way file archives are numbered. + + + + + Indicates whether to create directories if they do not exist. + + + + + Indicates whether file creation calls should be synchronized by a system global mutex. + + + + + Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. + + + + + Is the P:NLog.Targets.FileTarget.FileName an absolute or relative path? + + + + + File attributes (Windows only). + + + + + Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. + + + + + Indicates whether to write BOM (byte order mark) in created files. Defaults to true for UTF-16 and UTF-32 + + + + + Indicates whether to enable log file(s) to be deleted. + + + + + Indicates whether to delete old log file on startup. + + + + + File encoding. + + + + + Indicates whether to replace file contents on each write instead of appending log message at the end. + + + + + Line ending mode. + + + + + Number of times the write is appended on the file before NLog discards the log message. + + + + + Delay in milliseconds to wait before attempting to write to the file again. + + + + + Maximum number of seconds before open files are flushed. Zero or negative means disabled. + + + + + Maximum number of seconds that files are kept open. Zero or negative means disabled. + + + + + Indicates whether concurrent writes to the log file by multiple processes on different network hosts. + + + + + Log file buffer size in bytes. + + + + + Indicates whether to automatically flush the file buffers after each log message. + + + + + Indicates whether to keep log file open instead of opening and closing it on each logging event. + + + + + Indicates whether concurrent writes to the log file by multiple processes on the same host. + + + + + Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write + + + + + Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Condition expression. Log events who meet this condition will be forwarded to the wrapped target. + + + + + + + + + + + + + + + Name of the target. + + + + + Identifier to perform group-by + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Windows domain name to change context to. + + + + + Required impersonation level. + + + + + Type of the logon provider. + + + + + Logon Type. + + + + + User account password. + + + + + Indicates whether to revert to the credentials of the process instead of impersonating another user. + + + + + Username to change context to. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Interval in which messages will be written up to the P:NLog.Targets.Wrappers.LimitingTargetWrapper.MessageLimit number of messages. + + + + + Maximum allowed number of messages written per P:NLog.Targets.Wrappers.LimitingTargetWrapper.Interval. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether NewLine characters in the body should be replaced with tags. + + + + + Priority used for sending mails. + + + + + Encoding to be used for sending e-mail. + + + + + BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Indicates whether to add new lines between log entries. + + + + + Indicates whether to send message as HTML instead of plain text. + + + + + Sender's email address (e.g. joe@domain.com). + + + + + Mail message body (repeated for each log message send in one mail). + + + + + Mail subject. + + + + + Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Specifies how outgoing email messages will be handled. + + + + + SMTP Server to be used for sending. + + + + + SMTP Authentication mode. + + + + + Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. + + + + + Port number that SMTP Server is listening on. + + + + + Indicates whether the default Settings from System.Net.MailSettings should be used. + + + + + Folder where applications save mail messages to be processed by the local SMTP server. + + + + + Indicates the SMTP client timeout. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Max number of items to have in memory + + + + + + + + + + + + + + + + + Name of the target. + + + + + Class name. + + + + + Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. + + + + + + + + + + + + + + + Name of the parameter. + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Fallback value when result value is not available + + + + + Type of the parameter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Action that should be taken, when more pending messages than P:NLog.Targets.NetworkTarget.MaxQueueSize. + + + + + Action that should be taken if the message is larger than P:NLog.Targets.NetworkTarget.MaxMessageSize + + + + + Maximum queue size for a single connection. Requires P:NLog.Targets.NetworkTarget.KeepConnection = true + + + + + Action that should be taken, when more connections than P:NLog.Targets.NetworkTarget.MaxConnections. + + + + + Indicates whether to keep connection open whenever possible. + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Size of the connection cache (number of connections which are kept alive). Requires P:NLog.Targets.NetworkTarget.KeepConnection = true + + + + + Network address. + + + + + Maximum simultaneous connections. Requires P:NLog.Targets.NetworkTarget.KeepConnection = false + + + + + Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes. + + + + + Skip compression when protocol payload is below limit to reduce overhead in cpu-usage and additional headers + + + + + Maximum message size in bytes. On limit breach then P:NLog.Targets.NetworkTarget.OnOverflow action is activated. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message P:NLog.Targets.NetworkTarget.NewLine. + + + + + Indicates whether to append newline at the end of log message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Separator for T:NLog.ScopeContext operation-states-stack. + + + + + Stack separator for log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Whether to include the contents of the T:NLog.ScopeContext properties-dictionary. + + + + + Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Instance of T:NLog.Layouts.Log4JXmlEventLayout that is used to format log messages. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Action that should be taken, when more connections than P:NLog.Targets.NetworkTarget.MaxConnections. + + + + + SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Action that should be taken, when more pending messages than P:NLog.Targets.NetworkTarget.MaxQueueSize. + + + + + Action that should be taken if the message is larger than P:NLog.Targets.NetworkTarget.MaxMessageSize + + + + + Maximum queue size for a single connection. Requires P:NLog.Targets.NetworkTarget.KeepConnection = true + + + + + Network address. + + + + + Indicates whether to keep connection open whenever possible. + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Size of the connection cache (number of connections which are kept alive). Requires P:NLog.Targets.NetworkTarget.KeepConnection = true + + + + + Maximum simultaneous connections. Requires P:NLog.Targets.NetworkTarget.KeepConnection = false + + + + + Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes. + + + + + Skip compression when protocol payload is below limit to reduce overhead in cpu-usage and additional headers + + + + + Maximum message size in bytes. On limit breach then P:NLog.Targets.NetworkTarget.OnOverflow action is activated. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message P:NLog.Targets.NetworkTarget.NewLine. + + + + + Indicates whether to append newline at the end of log message. + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to perform layout calculation. + + + + + + + + + + + + + + + + Name of the target. + + + + + Default filter to be applied when no specific rule matches. + + + + + + + + + + + + + Condition to be tested. + + + + + Resulting filter to be applied when the condition matches. + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + Name of the target. + + + + + Number of times to repeat each log message. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Whether to enable batching, and only apply single delay when a whole batch fails + + + + + Number of retries that should be attempted on the wrapped target in case of a failure. + + + + + Time to wait between retries in milliseconds. + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Forward F:NLog.LogLevel.Fatal to M:System.Diagnostics.Trace.Fail(System.String) (Instead of M:System.Diagnostics.Trace.TraceError(System.String)) + + + + + Force use M:System.Diagnostics.Trace.WriteLine(System.String) independent of T:NLog.LogLevel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in P:NLog.Targets.WebServiceTarget.Headers parameters) + + + + + Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) + + + + + Value whether escaping be done according to the old NLog style (Very non-standard) + + + + + Value of the User-agent HTTP header. + + + + + Web service URL. + + + + + Proxy configuration when calling web service + + + + + Custom proxy address, include port separated by a colon + + + + + Protocol to be used when calling web service. + + + + + Web service namespace. Only used with Soap. + + + + + Web service method name. Only used with Soap. + + + + + Should we include the BOM (Byte-order-mark) for UTF? Influences the P:NLog.Targets.WebServiceTarget.Encoding property. This will only work for UTF-8. + + + + + Encoding. + + + + + Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see P:NLog.Targets.WebServiceTarget.Protocol and F:NLog.Targets.WebServiceProtocol.XmlPost). + + + + + (optional) root namespace of the XML document, if POST of XML document chosen. (see P:NLog.Targets.WebServiceTarget.Protocol and F:NLog.Targets.WebServiceProtocol.XmlPost). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). + + + + + Column delimiter. + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + Quote Character. + + + + + Quoting mode. + + + + + Indicates whether CVS should include header. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the column. + + + + + Layout of the column. + + + + + Override of Quoting mode + + + + + + + + + + + + + + Option to render the empty object value {} + + + + + Option to suppress the extra spaces in the output json + + + + + + + + + + + + + + + + + + + + + + + Option to include all properties from the log event (as JSON) + + + + + Indicates whether to include contents of the T:NLog.GlobalDiagnosticsContext dictionary. + + + + + Whether to include the contents of the T:NLog.ScopeContext dictionary. + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Option to exclude null/empty properties from the log event (as JSON) + + + + + List of property names to exclude when P:NLog.Layouts.JsonLayout.IncludeAllProperties is true + + + + + How far should the JSON serializer follow object references before backing off + + + + + Option to render the empty object value {} + + + + + Option to suppress the extra spaces in the output json + + + + + + + + + + + + + + + + + + + Name of the attribute. + + + + + Layout that will be rendered as the attribute's value. + + + + + Fallback value when result value is not available + + + + + Determines whether or not this attribute will be Json encoded. + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Indicates whether to escape non-ascii characters + + + + + Whether an attribute with empty value should be included in the output + + + + + Result value type, for conversion of layout rendering output + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + + + + + + + + + + + + + + + + + + + Option to include all properties from the log events + + + + + Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context. + + + + + Whether to include the contents of the T:NLog.ScopeContext properties-dictionary. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Log4j:event logger-xml-attribute (Default ${logger}) + + + + + Whether the log4j:throwable xml-element should be written as CDATA + + + + + + + + + + + + + + Layout text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the root XML element + + + + + Value inside the root XML element + + + + + Whether to include the contents of the T:NLog.ScopeContext dictionary. + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + List of property names to exclude when P:NLog.Layouts.XmlElementBase.IncludeAllProperties is true + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + Option to include all properties from the log event (as XML) + + + + + + + + + + + + + + + + + Name of the attribute. + + + + + Layout that will be rendered as the attribute's value. + + + + + Fallback value when result value is not available + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Whether an attribute with empty value should be included in the output + + + + + Result value type, for conversion of layout rendering output + + + + + + + + + + + + + + + + + + + + + + + + Name of the element + + + + + Whether to include the contents of the T:NLog.ScopeContext dictionary. + + + + + Value inside the element + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + List of property names to exclude when P:NLog.Layouts.XmlElementBase.IncludeAllProperties is true + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + Option to include all properties from the log event (as XML) + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Condition expression. + + + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Append FilterCount to the P:NLog.LogEventInfo.Message when an event is no longer filtered + + + + + Insert FilterCount value into P:NLog.LogEventInfo.Properties when an event is no longer filtered + + + + + Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout. + + + + + Layout to be used to filter log messages. + + + + + Max length of filter values, will truncate if above limit + + + + + How long before a filter expires, and logging is accepted again + + + + + Default number of unique filter values to expect, will automatically increase if needed + + + + + Max number of unique filter values to expect simultaneously + + + + + Default buffer size for the internal buffers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DotNet/App/Program.cs b/DotNet/App/Program.cs new file mode 100644 index 00000000..6b365b94 --- /dev/null +++ b/DotNet/App/Program.cs @@ -0,0 +1,32 @@ +using TEngine; +using TEngine.Core; +using TEngine.Logic; + +try +{ + App.Init(); + + AssemblySystem.Init(); + + ConfigTableSystem.Bind(); + + App.Start().Coroutine(); + + Entry.Start().Coroutine(); + + while(true) + { + Thread.Sleep(1); + ThreadSynchronizationContext.Main.Update(); + SingletonSystem.Update(); + } +} +catch (Exception e) +{ + Log.Error(e); +} + + + + + diff --git a/DotNet/App/ProgramInfo.cs b/DotNet/App/ProgramInfo.cs new file mode 100644 index 00000000..8e52fc03 --- /dev/null +++ b/DotNet/App/ProgramInfo.cs @@ -0,0 +1,62 @@ +using TEngine; +using TEngine.Core; +using TEngine.Logic; + +public class ProgramInfo +{ + /// + /// å¯åŠ¨è¯´æ˜Žã€‚ + /// + public void Temp() + { + try + { + // 框架å¯åŠ¨éœ€è¦åœ¨å‘½ä»¤è¡ŒåŽé¢æ·»åР傿•°æ‰ä¼šæ­£å¸¸ä½¿ç”¨åˆ†åˆ«æ˜¯: + // 例如demoçš„æœåС噍傿•°: --Mode Develop --AppType Game --AppId 100 + // Mode有两ç§: + // Develop:开呿¨¡å¼ 这个是所有在é…置定义的æœåŠ¡å™¨éƒ½ä¼šå¯åŠ¨å¹¶ä¸”åœ¨åŒä¸€ä¸ªè¿›ç¨‹ä¸‹ã€æ–¹ä¾¿å¼€å‘调试。 + // 当然如果游æˆä½“é‡ä¸å¤§ä¹Ÿå¯ä»¥ç”¨è¿™ä¸ªæ¨¡å¼å‘å¸ƒï¼ŒåŽæœŸæ”¹æˆRelease模å¼ä¹Ÿæ˜¯æ²¡æœ‰é—®é¢˜çš„ + // Release:å‘布模å¼åªä¼šå¯åЍå¯åЍ傿•°ä¼ é€’çš„Serverã€ä¹Ÿå°±æ˜¯åªä¼šå¯åŠ¨ä¸€ä¸ªServer + // 您å¯ä»¥åšä¸€ä¸ªServer专门用于管ç†å¯åŠ¨æ‰€æœ‰Server的工具或脚本ã€ä¸€èˆ¬éƒ½æ˜¯è¿ç»´åŒå­¦æ¥åš + // AppType有两ç§: + // Game:æ¸¸æˆæœåС噍 + // Export:导出é…置表工具 + // 例如我è¦å¯åŠ¨å¯¼è¡¨å·¥å…·å‚æ•°å°±åº”该是--AppType Exportå°±å¯ä»¥äº†Modeå’ŒAppId都å¯ä»¥ä¸ç”¨è®¾ç½® + // AppId:告诉框架应该å¯åŠ¨å“ªä¸ªæœåС噍ã€å¯¹åº”ServerConfig.xlsçš„Id 如果Mode使用的Developçš„è¯ã€è¿™ä¸ªIdä¸ç”Ÿæ•ˆ + // åˆå§‹åŒ–框架 + App.Init(); + + // 演示的框架创建了Modelå’ŒHotfix两个工程所以需è¦AssemblyManager.Loadæ¥åŠ è½½è¿™ä¸¤ä¸ªç¨‹åºé›† + // 这个看个人而定ã€ä½ ä¹Ÿå¯ä»¥ä¸æŒ‰ç…§æ¼”示框架这样创建2个程åºé›†ã€æ€»ä¹‹æœ‰å‡ ä¸ªç¨‹åºé›†å°±AssemblyManager.Load一下加载到框架中 + // 因为App这个工程就ä¸éœ€è¦äº†ã€å› ä¸ºè¿™é‡Œæ²¡æœ‰é€»è¾‘ã€å…·ä½“看AssemblyLoadHelper的逻辑ã€è‡ªå·±å†™ä¸€ä¸‹ + // 加载需è¦çš„程åºé›†ã€è¿™é‡Œå› ä¸ºæ¯ä¸ªäººéƒ½æ¡†æž¶è§„划都ä¸ä¸€æ ·ã€æ‰€ä»¥è¿™å—开放出自己定义 + AssemblySystem.Init(); + + // 绑定框架需è¦çš„é…置文件 + // 框架å¯åЍæœåŠ¡å™¨éœ€è¦é…置文件æ‰å¯ä»¥å¯åŠ¨ã€æ¯”如需è¦å¯åŠ¨ä»€ä¹ˆæœåŠ¡å™¨ã€æœåŠ¡å™¨çš„ç›‘å¬åœ°å€æ˜¯ä»€ä¹ˆç­‰ç­‰ã€æ‰€ä»¥è¦æå‰ç»‘定一下 + ConfigTableSystem.Bind(); + + // å¯åŠ¨æ¡†æž¶ + // å¯åŠ¨æ¡†æž¶ä¼šåŠ è½½Demo下Config/Excel/Server里四个文件é…ç½® + // 因为上é¢ConfigTableHelper.Bindå·²ç»ç»‘å®šå¥½äº†ã€æ‰€ä»¥æ¡†æž¶å¯ä»¥ç›´æŽ¥è¯»å–è¿™4个é…置文件进行å¯åЍ + App.Start().Coroutine(); + + // 框架å¯åЍåŽéœ€è¦æ‰§è¡Œçš„逻辑ã€çŽ°åœ¨æ˜¯æˆ‘æ˜¯å†™çš„å¯åЍæœåŠ¡å™¨çš„é€»è¾‘ã€åŒä¸Šè¿™é‡Œä¹Ÿå¼€æ”¾å‡ºæ¥è‡ªå®šä¹‰ + // 但这里一定是异步的ã€ä¸ç„¶æ¡†æž¶éƒ¨åˆ†åŠŸèƒ½å¯èƒ½ä¸ä¼šæ­£å¸¸ã€å› ä¸ºé˜»å¡žåˆ°è¿™é‡Œã€éœ€è¦Update需è¦ä¸‹é¢çš„æ‰å¯ä»¥é©±åЍ + // 这个入å£ä»£ç å¯ä»¥ä¸ç”¨è°ƒç”¨ã€è¿™é‡Œåªæ˜¯æ¼”ç¤ºä¸‹å¦‚æžœè°ƒç”¨åº”è¯¥æ€Žä¹ˆå¤„ç† + Entry.Start().Coroutine(); + + while (true) + { + Thread.Sleep(1); + ThreadSynchronizationContext.Main.Update(); + SingletonSystem.Update(); + } + } + catch + (Exception e) + { + Log.Error(e.ToString()); + } + } +} \ No newline at end of file diff --git a/DotNet/App/TEngineSettings.json b/DotNet/App/TEngineSettings.json new file mode 100644 index 00000000..8994754d --- /dev/null +++ b/DotNet/App/TEngineSettings.json @@ -0,0 +1,64 @@ +{ + "Export": { + "ProtoBufTemplatePath": { + "Value": "../Config/Template/ProtoTemplate.txt", + "Comment": "ProtoBuf生æˆä»£ç æ¨¡æ¿çš„ä½ç½®" + }, + "ProtoBufDirectory": { + "Value": "../Config/ProtoBuf/", + "Comment": "ProtoBuf文件所在的ä½ç½®æ–‡ä»¶å¤¹ä½ç½®" + }, + "ProtoBufServerDirectory": { + "Value": "../../Assets/GameScripts/DotNet/Logic/Generate~/NetworkProtocol/", + "Comment": "ProtoBuf生æˆåˆ°æœåŠ¡ç«¯çš„æ–‡ä»¶å¤¹ä½ç½®" + }, + "ProtoBufClientDirectory": { + "Value": "../../Assets/GameScripts/HotFix/GameProto/GameProtocol/", + "Comment": "ProtoBuf生æˆåˆ°å®¢æˆ·ç«¯çš„æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelProgramPath": { + "Value": "../Config/Excel/", + "Comment": "Excelé…置文件根目录" + }, + "ExcelVersionFile": { + "Value": ".../Config/Excel/Version.txt", + "Comment": "Excel版本文件的ä½ç½®" + }, + "ExcelServerFileDirectory": { + "Value": "../../Server/Hotfix/Generate/ConfigTable/Entity/", + "Comment": "Excelç”ŸæˆæœåС噍代ç çš„æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelClientFileDirectory": { + "Value": "../../Client/Unity/Assets/Scripts/Hotfix/Generate/ConfigTable/Entity/", + "Comment": "Excel生æˆå®¢æˆ·ç«¯ä»£ç æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelServerBinaryDirectory": { + "Value": "../Config/Binary/", + "Comment": "Excelç”ŸæˆæœåŠ¡å™¨äºŒè¿›åˆ¶æ•°æ®æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelClientBinaryDirectory": { + "Value": "../../Client/Unity/Assets/Bundles/Config/", + "Comment": "Excel生æˆå®¢æˆ·ç«¯äºŒè¿›åˆ¶æ•°æ®æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelServerJsonDirectory": { + "Value": "../Config/Json/Server/", + "Comment": "Excelç”ŸæˆæœåС噍Jsonæ•°æ®æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelClientJsonDirectory": { + "Value": "../Config/Json/Client/", + "Comment": "Excel生æˆå®¢æˆ·ç«¯Jsonæ•°æ®æ–‡ä»¶å¤¹ä½ç½®" + }, + "ExcelTemplatePath": { + "Value": "../Config/Template/ExcelTemplate.txt", + "Comment": "Excel生æˆä»£ç æ¨¡æ¿çš„ä½ç½®" + }, + "ServerCustomExportDirectory": { + "Value": "../../Server/Hotfix/Generate/CustomExport/", + "Comment": "æœåŠ¡å™¨è‡ªå®šä¹‰å¯¼å‡ºä»£ç æ–‡ä»¶å¤¹ä½ç½®" + }, + "ClientCustomExportDirectory": { + "Value": "../../Client/Unity/Assets/Scripts/Hotfix/Generate/CustomExport/", + "Comment": "å®¢æˆ·ç«¯è‡ªå®šä¹‰å¯¼å‡ºä»£ç æ–‡ä»¶å¤¹ä½ç½®" + } + } +} \ No newline at end of file diff --git a/DotNet/Config/Binary/MachineConfigData.bytes b/DotNet/Config/Binary/MachineConfigData.bytes new file mode 100644 index 00000000..43938728 --- /dev/null +++ b/DotNet/Config/Binary/MachineConfigData.bytes @@ -0,0 +1,2 @@ + +' 127.0.0.1 127.0.0.1" 127.0.0.1( œ \ No newline at end of file diff --git a/DotNet/Config/Binary/SceneConfigData.bytes b/DotNet/Config/Binary/SceneConfigData.bytes new file mode 100644 index 0000000000000000000000000000000000000000..cc9400955740840c2652f96552b76c9f0f42ba1b GIT binary patch literal 140 zcmd;5;$ReLXlPjAAkn~~z@WwAo>-D<1SYMRy`2Lb7R+Jf(&k_SsX5TVDA6DQRK)F= zl2VjfT%4GclWN3+#4&VW;F9KG0V#gaz$DQi0aVQFn^<7P0wh3^G90WR$%cbKNd=H~ N&KZd%Mqtu`0RU5)C0PIf literal 0 HcmV?d00001 diff --git a/DotNet/Config/Binary/ServerConfigData.bytes b/DotNet/Config/Binary/ServerConfigData.bytes new file mode 100644 index 0000000000000000000000000000000000000000..79df9546bce3f844312057b80b9011fea53cc887 GIT binary patch literal 48 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 literal 0 HcmV?d00001 diff --git a/DotNet/Config/Excel/Server/SceneConfig.xlsx b/DotNet/Config/Excel/Server/SceneConfig.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2a177c1f28a32b1805fc3bfd5fd1ecb8bc35e6c8 GIT binary patch literal 11335 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 literal 0 HcmV?d00001 diff --git a/DotNet/Config/Excel/Server/WorldConfig.xlsx b/DotNet/Config/Excel/Server/WorldConfig.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6ae74a3266328ec133695dfc38011f5170fff2f0 GIT binary patch literal 9772 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 f`)k$e|3mlBY6(?^7e5ax8mQz51|)|<{;c List { get; set; } = new List<(ConfigName)>(); + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); + private static (ConfigName)Data _instance; + + public static (ConfigName)Data Instance + { + get { return _instance ??= ConfigTableManage.Load<(ConfigName)Data>(); } + private set => _instance = value; + } + + public (ConfigName) Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"(ConfigName) not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out (ConfigName) config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + for (var i = 0; i < List.Count; i++) + { + (ConfigName) config = List[i]; + _configs.Add(config.Id, config); + config.AfterDeserialization(); + } + + base.AfterDeserialization(); + } + + public void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class (ConfigName) : AProto + {(Fields) + } +} \ No newline at end of file diff --git a/DotNet/Config/Template/ProtoTemplate.txt b/DotNet/Config/Template/ProtoTemplate.txt new file mode 100644 index 00000000..b302b518 --- /dev/null +++ b/DotNet/Config/Template/ProtoTemplate.txt @@ -0,0 +1,20 @@ +#if SERVER +using ProtoBuf; +using Unity.Mathematics; +using System.Collections.Generic; +using TEngine.Core.Network; +#pragma warning disable CS8618 + +namespace TEngine +{ +#else +using ProtoBuf; +using Unity.Mathematics; +using System.Collections.Generic; +using TEngine.Core.Network; +#pragma warning disable CS8618 + +namespace TEngine +{ +#endif +(Content)} \ No newline at end of file diff --git a/DotNet/DotNet.sln b/DotNet/DotNet.sln deleted file mode 100644 index 6debe2ec..00000000 --- a/DotNet/DotNet.sln +++ /dev/null @@ -1,43 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.App", "App\DotNet.App.csproj", "{4948005C-3806-4C92-BA41-D27319A2B4B4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Loader", "Loader\DotNet.Loader.csproj", "{1D5E890A-C9D5-45DF-B098-73DBE39EB311}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Core", "Core\DotNet.Core.csproj", "{E49D926E-53B5-4F16-BA8A-816694BD7C92}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Hotfix", "Hotfix\DotNet.Hotfix.csproj", "{0EEA95E0-0040-4B3C-B7FD-0E4F38F90A07}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Model", "Model\DotNet.Model.csproj", "{49855D78-0347-462B-AB98-90E4138C2AED}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.ThirdParty", "ThirdParty\DotNet.ThirdParty.csproj", "{19A11666-4891-4E73-B826-1B24D7A62080}" -EndProject -Global -GlobalSection(SolutionConfigurationPlatforms) = preSolution -Debug|Any CPU = Debug|Any CPU -Release|Any CPU = Release|Any CPU -EndGlobalSection -GlobalSection(ProjectConfigurationPlatforms) = postSolution -{4948005C-3806-4C92-BA41-D27319A2B4B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU -{4948005C-3806-4C92-BA41-D27319A2B4B4}.Debug|Any CPU.Build.0 = Debug|Any CPU -{4948005C-3806-4C92-BA41-D27319A2B4B4}.Release|Any CPU.ActiveCfg = Release|Any CPU -{4948005C-3806-4C92-BA41-D27319A2B4B4}.Release|Any CPU.Build.0 = Release|Any CPU -{E49D926E-53B5-4F16-BA8A-816694BD7C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU -{E49D926E-53B5-4F16-BA8A-816694BD7C92}.Debug|Any CPU.Build.0 = Debug|Any CPU -{E49D926E-53B5-4F16-BA8A-816694BD7C92}.Release|Any CPU.ActiveCfg = Release|Any CPU -{E49D926E-53B5-4F16-BA8A-816694BD7C92}.Release|Any CPU.Build.0 = Release|Any CPU -{0EEA95E0-0040-4B3C-B7FD-0E4F38F90A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU -{0EEA95E0-0040-4B3C-B7FD-0E4F38F90A07}.Debug|Any CPU.Build.0 = Debug|Any CPU -{0EEA95E0-0040-4B3C-B7FD-0E4F38F90A07}.Release|Any CPU.ActiveCfg = Release|Any CPU -{0EEA95E0-0040-4B3C-B7FD-0E4F38F90A07}.Release|Any CPU.Build.0 = Release|Any CPU -{49855D78-0347-462B-AB98-90E4138C2AED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU -{49855D78-0347-462B-AB98-90E4138C2AED}.Debug|Any CPU.Build.0 = Debug|Any CPU -{49855D78-0347-462B-AB98-90E4138C2AED}.Release|Any CPU.ActiveCfg = Release|Any CPU -{49855D78-0347-462B-AB98-90E4138C2AED}.Release|Any CPU.Build.0 = Release|Any CPU -{19A11666-4891-4E73-B826-1B24D7A62080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU -{19A11666-4891-4E73-B826-1B24D7A62080}.Debug|Any CPU.Build.0 = Debug|Any CPU -{19A11666-4891-4E73-B826-1B24D7A62080}.Release|Any CPU.ActiveCfg = Release|Any CPU -{19A11666-4891-4E73-B826-1B24D7A62080}.Release|Any CPU.Build.0 = Release|Any CPU - {1D5E890A-C9D5-45DF-B098-73DBE39EB311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D5E890A-C9D5-45DF-B098-73DBE39EB311}.Debug|Any CPU.Build.0 = Debug|Any CPU -EndGlobalSection -EndGlobal diff --git a/DotNet/Directory.Build.props b/DotNet/Server.Build.props similarity index 100% rename from DotNet/Directory.Build.props rename to DotNet/Server.Build.props diff --git a/DotNet/DotNet.sln.DotSettings b/DotNet/Server.sln.DotSettings similarity index 100% rename from DotNet/DotNet.sln.DotSettings rename to DotNet/Server.sln.DotSettings diff --git a/DotNet/ThirdParty/DotNet.ThirdParty.csproj b/DotNet/ThirdParty/DotNet.ThirdParty.csproj deleted file mode 100644 index b4296dd4..00000000 --- a/DotNet/ThirdParty/DotNet.ThirdParty.csproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - net7.0 - disable - 11 - ET - ThirdParty - - - - false - en - - - - DOTNET;UNITY_DOTSPLAYER - ..\..\Bin\ - true - true - true - - - - DOTNET;UNITY_DOTSPLAYER - ..\..\Bin\ - true - true - - - - - - TrueSync/%(RecursiveDir)%(FileName)%(Extension) - - - - ETTask/%(RecursiveDir)%(FileName)%(Extension) - - - - Kcp/%(RecursiveDir)%(FileName)%(Extension) - - - - Recast\Recast.cs - - - - Unity.Mathematics/$([System.String]::new(%(RecursiveDir)).Substring($([System.String]::new(%(RecursiveDir)).Indexof("Unity.Mathematics"))).Replace("Unity.Mathematics", ""))/%(FileName)%(Extension) - - - - - - - runtimes\osx\native\libRecastDll.dylib - PreserveNewest - - - runtimes\linux\native\libRecastDll.so - PreserveNewest - - - runtimes\win\native\RecastDll.dll - PreserveNewest - - - - - - - - - - - - - - - diff --git a/DotNet/ThirdParty/KCPLibrary/kcp.dll b/DotNet/ThirdParty/KCPLibrary/kcp.dll new file mode 100644 index 0000000000000000000000000000000000000000..cf4dbf638b93e8ad4c3c8a14138191991bb91d05 GIT binary patch literal 146432 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 z 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 literal 0 HcmV?d00001 diff --git a/DotNet/ThirdParty/KCPLibrary/kcp.dylib b/DotNet/ThirdParty/KCPLibrary/kcp.dylib new file mode 100644 index 0000000000000000000000000000000000000000..729b398b5aec4cd412321ec4bf8bcadb4797e4db GIT binary patch literal 135808 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 literal 0 HcmV?d00001 diff --git a/DotNet/ThirdParty/KCPLibrary/kcp.so b/DotNet/ThirdParty/KCPLibrary/kcp.so new file mode 100644 index 0000000000000000000000000000000000000000..c5b7cc9d5861d315cc2ee6bfc35ceee2ffc57a4d GIT binary patch literal 31360 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 KG#`U6YWy$yT?uRe literal 0 HcmV?d00001